commit
a362cb4b91
28 changed files with 2626 additions and 870 deletions
|
|
@ -22,6 +22,7 @@ import { AppUserDevice } from "./build/src/services/storage/entity/AppUserDevice
|
|||
import { UserAccess } from "./build/src/services/storage/entity/UserAccess.js"
|
||||
import { AdminSettings } from "./build/src/services/storage/entity/AdminSettings.js"
|
||||
import { TransactionSwap } from "./build/src/services/storage/entity/TransactionSwap.js"
|
||||
import { InvoiceSwap } from "./build/src/services/storage/entity/InvoiceSwap.js"
|
||||
|
||||
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
|
||||
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
|
||||
|
|
@ -47,6 +48,8 @@ import { TxSwap1762890527098 } from './build/src/services/storage/migrations/176
|
|||
import { TxSwapAddress1764779178945 } from './build/src/services/storage/migrations/1764779178945-tx_swap_address.js'
|
||||
import { ClinkRequester1765497600000 } from './build/src/services/storage/migrations/1765497600000-clink_requester.js'
|
||||
import { TrackedProviderHeight1766504040000 } from './build/src/services/storage/migrations/1766504040000-tracked_provider_height.js'
|
||||
import { SwapsServiceUrl1768413055036 } from './build/src/services/storage/migrations/1768413055036-swaps_service_url.js'
|
||||
import { InvoiceSwaps1769529793283 } from './build/src/services/storage/migrations/1769529793283-invoice_swaps.js'
|
||||
|
||||
export default new DataSource({
|
||||
type: "better-sqlite3",
|
||||
|
|
@ -56,11 +59,11 @@ export default new DataSource({
|
|||
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
||||
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611,
|
||||
AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098,
|
||||
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000],
|
||||
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, InvoiceSwaps1769529793283],
|
||||
|
||||
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],
|
||||
TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings, TransactionSwap, InvoiceSwap],
|
||||
// synchronize: true,
|
||||
})
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/swaps_service_url -d ./datasource.js
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/invoice_swaps_fixes -d ./datasource.js
|
||||
|
|
@ -93,6 +93,11 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [MessagingToken](#MessagingToken)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- GetAdminInvoiceSwapQuotes
|
||||
- auth type: __Admin__
|
||||
- input: [InvoiceSwapRequest](#InvoiceSwapRequest)
|
||||
- output: [InvoiceSwapQuoteList](#InvoiceSwapQuoteList)
|
||||
|
||||
- GetAdminTransactionSwapQuotes
|
||||
- auth type: __Admin__
|
||||
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||
|
|
@ -243,20 +248,25 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- ListAdminSwaps
|
||||
- ListAdminInvoiceSwaps
|
||||
- auth type: __Admin__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
- output: [InvoiceSwapsList](#InvoiceSwapsList)
|
||||
|
||||
- ListAdminTxSwaps
|
||||
- auth type: __Admin__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [TxSwapsList](#TxSwapsList)
|
||||
|
||||
- ListChannels
|
||||
- auth type: __Admin__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [LndChannels](#LndChannels)
|
||||
|
||||
- ListSwaps
|
||||
- ListTxSwaps
|
||||
- auth type: __User__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
- output: [TxSwapsList](#TxSwapsList)
|
||||
|
||||
- LndGetInfo
|
||||
- auth type: __Admin__
|
||||
|
|
@ -290,10 +300,15 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [PayAddressRequest](#PayAddressRequest)
|
||||
- output: [PayAddressResponse](#PayAddressResponse)
|
||||
|
||||
- PayAdminInvoiceSwap
|
||||
- auth type: __Admin__
|
||||
- input: [PayAdminInvoiceSwapRequest](#PayAdminInvoiceSwapRequest)
|
||||
- output: [AdminInvoiceSwapResponse](#AdminInvoiceSwapResponse)
|
||||
|
||||
- PayAdminTransactionSwap
|
||||
- auth type: __Admin__
|
||||
- input: [PayAdminTransactionSwapRequest](#PayAdminTransactionSwapRequest)
|
||||
- output: [AdminSwapResponse](#AdminSwapResponse)
|
||||
- output: [AdminTxSwapResponse](#AdminTxSwapResponse)
|
||||
|
||||
- PayInvoice
|
||||
- auth type: __User__
|
||||
|
|
@ -305,6 +320,11 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- This methods has an __empty__ __request__ body
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- RefundAdminInvoiceSwap
|
||||
- auth type: __Admin__
|
||||
- input: [RefundAdminInvoiceSwapRequest](#RefundAdminInvoiceSwapRequest)
|
||||
- output: [AdminInvoiceSwapResponse](#AdminInvoiceSwapResponse)
|
||||
|
||||
- ResetDebit
|
||||
- auth type: __User__
|
||||
- input: [DebitOperation](#DebitOperation)
|
||||
|
|
@ -540,6 +560,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [MessagingToken](#MessagingToken)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- GetAdminInvoiceSwapQuotes
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/invoice/quote__
|
||||
- input: [InvoiceSwapRequest](#InvoiceSwapRequest)
|
||||
- output: [InvoiceSwapQuoteList](#InvoiceSwapQuoteList)
|
||||
|
||||
- GetAdminTransactionSwapQuotes
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
|
|
@ -743,7 +770,7 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- GetTransactionSwapQuotes
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
- http route: __/api/user/swap/quote__
|
||||
- http route: __/api/user/swap/transaction/quote__
|
||||
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||
- output: [TransactionSwapQuoteList](#TransactionSwapQuoteList)
|
||||
|
||||
|
|
@ -834,12 +861,19 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- ListAdminSwaps
|
||||
- ListAdminInvoiceSwaps
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/list__
|
||||
- http route: __/api/admin/swap/invoice/list__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
- output: [InvoiceSwapsList](#InvoiceSwapsList)
|
||||
|
||||
- ListAdminTxSwaps
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/transaction/list__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [TxSwapsList](#TxSwapsList)
|
||||
|
||||
- ListChannels
|
||||
- auth type: __Admin__
|
||||
|
|
@ -848,12 +882,12 @@ 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
|
||||
- ListTxSwaps
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
- http route: __/api/user/swap/list__
|
||||
- http route: __/api/user/swap/transaction/list__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
- output: [TxSwapsList](#TxSwapsList)
|
||||
|
||||
- LndGetInfo
|
||||
- auth type: __Admin__
|
||||
|
|
@ -899,12 +933,19 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [PayAddressRequest](#PayAddressRequest)
|
||||
- output: [PayAddressResponse](#PayAddressResponse)
|
||||
|
||||
- PayAdminInvoiceSwap
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/invoice/pay__
|
||||
- input: [PayAdminInvoiceSwapRequest](#PayAdminInvoiceSwapRequest)
|
||||
- output: [AdminInvoiceSwapResponse](#AdminInvoiceSwapResponse)
|
||||
|
||||
- PayAdminTransactionSwap
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/transaction/pay__
|
||||
- input: [PayAdminTransactionSwapRequest](#PayAdminTransactionSwapRequest)
|
||||
- output: [AdminSwapResponse](#AdminSwapResponse)
|
||||
- output: [AdminTxSwapResponse](#AdminTxSwapResponse)
|
||||
|
||||
- PayAppUserInvoice
|
||||
- auth type: __App__
|
||||
|
|
@ -927,6 +968,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- This methods has an __empty__ __request__ body
|
||||
- This methods has an __empty__ __response__ body
|
||||
|
||||
- RefundAdminInvoiceSwap
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/invoice/refund__
|
||||
- input: [RefundAdminInvoiceSwapRequest](#RefundAdminInvoiceSwapRequest)
|
||||
- output: [AdminInvoiceSwapResponse](#AdminInvoiceSwapResponse)
|
||||
|
||||
- RequestNPubLinkingToken
|
||||
- auth type: __App__
|
||||
- http method: __post__
|
||||
|
|
@ -1098,7 +1146,10 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### AdminSwapResponse
|
||||
### AdminInvoiceSwapResponse
|
||||
- __tx_id__: _string_
|
||||
|
||||
### AdminTxSwapResponse
|
||||
- __network_fee__: _number_
|
||||
- __tx_id__: _string_
|
||||
|
||||
|
|
@ -1331,6 +1382,35 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __token__: _string_
|
||||
- __url__: _string_
|
||||
|
||||
### InvoiceSwapOperation
|
||||
- __failure_reason__: _string_ *this field is optional
|
||||
- __invoice_paid__: _string_
|
||||
- __operation_payment__: _[UserOperation](#UserOperation)_ *this field is optional
|
||||
- __swap_operation_id__: _string_
|
||||
- __tx_id__: _string_
|
||||
|
||||
### InvoiceSwapQuote
|
||||
- __address__: _string_
|
||||
- __chain_fee_sats__: _number_
|
||||
- __invoice__: _string_
|
||||
- __invoice_amount_sats__: _number_
|
||||
- __service_fee_sats__: _number_
|
||||
- __service_url__: _string_
|
||||
- __swap_fee_sats__: _number_
|
||||
- __swap_operation_id__: _string_
|
||||
- __transaction_amount_sats__: _number_
|
||||
- __tx_id__: _string_
|
||||
|
||||
### InvoiceSwapQuoteList
|
||||
- __quotes__: ARRAY of: _[InvoiceSwapQuote](#InvoiceSwapQuote)_
|
||||
|
||||
### InvoiceSwapRequest
|
||||
- __amount_sats__: _number_
|
||||
|
||||
### InvoiceSwapsList
|
||||
- __quotes__: ARRAY of: _[InvoiceSwapQuote](#InvoiceSwapQuote)_
|
||||
- __swaps__: ARRAY of: _[InvoiceSwapOperation](#InvoiceSwapOperation)_
|
||||
|
||||
### LatestBundleMetricReq
|
||||
- __limit__: _number_ *this field is optional
|
||||
|
||||
|
|
@ -1534,6 +1614,11 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __service_fee__: _number_
|
||||
- __txId__: _string_
|
||||
|
||||
### PayAdminInvoiceSwapRequest
|
||||
- __no_claim__: _boolean_ *this field is optional
|
||||
- __sat_per_v_byte__: _number_
|
||||
- __swap_operation_id__: _string_
|
||||
|
||||
### PayAdminTransactionSwapRequest
|
||||
- __address__: _string_
|
||||
- __swap_operation_id__: _string_
|
||||
|
|
@ -1584,6 +1669,10 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
### ProvidersDisruption
|
||||
- __disruptions__: ARRAY of: _[ProviderDisruption](#ProviderDisruption)_
|
||||
|
||||
### RefundAdminInvoiceSwapRequest
|
||||
- __sat_per_v_byte__: _number_
|
||||
- __swap_operation_id__: _string_
|
||||
|
||||
### RelaysMigration
|
||||
- __relays__: ARRAY of: _string_
|
||||
|
||||
|
|
@ -1640,16 +1729,6 @@ 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
|
||||
- __quotes__: ARRAY of: _[TransactionSwapQuote](#TransactionSwapQuote)_
|
||||
- __swaps__: ARRAY of: _[SwapOperation](#SwapOperation)_
|
||||
|
||||
### TransactionSwapQuote
|
||||
- __chain_fee_sats__: _number_
|
||||
- __invoice_amount_sats__: _number_
|
||||
|
|
@ -1665,6 +1744,16 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
### TransactionSwapRequest
|
||||
- __transaction_amount_sats__: _number_
|
||||
|
||||
### TxSwapOperation
|
||||
- __address_paid__: _string_
|
||||
- __failure_reason__: _string_ *this field is optional
|
||||
- __operation_payment__: _[UserOperation](#UserOperation)_ *this field is optional
|
||||
- __swap_operation_id__: _string_
|
||||
|
||||
### TxSwapsList
|
||||
- __quotes__: ARRAY of: _[TransactionSwapQuote](#TransactionSwapQuote)_
|
||||
- __swaps__: ARRAY of: _[TxSwapOperation](#TxSwapOperation)_
|
||||
|
||||
### UpdateChannelPolicyRequest
|
||||
- __policy__: _[ChannelPolicy](#ChannelPolicy)_
|
||||
- __update__: _[UpdateChannelPolicyRequest_update](#UpdateChannelPolicyRequest_update)_
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ type Client struct {
|
|||
EncryptionExchange func(req EncryptionExchangeRequest) error
|
||||
EnrollAdminToken func(req EnrollAdminTokenRequest) error
|
||||
EnrollMessagingToken func(req MessagingToken) error
|
||||
GetAdminInvoiceSwapQuotes func(req InvoiceSwapRequest) (*InvoiceSwapQuoteList, error)
|
||||
GetAdminTransactionSwapQuotes func(req TransactionSwapRequest) (*TransactionSwapQuoteList, error)
|
||||
GetApp func() (*Application, error)
|
||||
GetAppUser func(req GetAppUserRequest) (*AppUser, error)
|
||||
|
|
@ -114,19 +115,22 @@ type Client struct {
|
|||
HandleLnurlWithdraw func(query HandleLnurlWithdraw_Query) error
|
||||
Health func() error
|
||||
LinkNPubThroughToken func(req LinkNPubThroughTokenRequest) error
|
||||
ListAdminSwaps func() (*SwapsList, error)
|
||||
ListAdminInvoiceSwaps func() (*InvoiceSwapsList, error)
|
||||
ListAdminTxSwaps func() (*TxSwapsList, error)
|
||||
ListChannels func() (*LndChannels, error)
|
||||
ListSwaps func() (*SwapsList, error)
|
||||
ListTxSwaps func() (*TxSwapsList, error)
|
||||
LndGetInfo func(req LndGetInfoRequest) (*LndGetInfoResponse, error)
|
||||
NewAddress func(req NewAddressRequest) (*NewAddressResponse, error)
|
||||
NewInvoice func(req NewInvoiceRequest) (*NewInvoiceResponse, error)
|
||||
NewProductInvoice func(query NewProductInvoice_Query) (*NewInvoiceResponse, error)
|
||||
OpenChannel func(req OpenChannelRequest) (*OpenChannelResponse, error)
|
||||
PayAddress func(req PayAddressRequest) (*PayAddressResponse, error)
|
||||
PayAdminTransactionSwap func(req PayAdminTransactionSwapRequest) (*AdminSwapResponse, error)
|
||||
PayAdminInvoiceSwap func(req PayAdminInvoiceSwapRequest) (*AdminInvoiceSwapResponse, error)
|
||||
PayAdminTransactionSwap func(req PayAdminTransactionSwapRequest) (*AdminTxSwapResponse, error)
|
||||
PayAppUserInvoice func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error)
|
||||
PayInvoice func(req PayInvoiceRequest) (*PayInvoiceResponse, error)
|
||||
PingSubProcesses func() error
|
||||
RefundAdminInvoiceSwap func(req RefundAdminInvoiceSwapRequest) (*AdminInvoiceSwapResponse, error)
|
||||
RequestNPubLinkingToken func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error)
|
||||
ResetDebit func(req DebitOperation) error
|
||||
ResetManage func(req ManageOperation) error
|
||||
|
|
@ -667,6 +671,35 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
GetAdminInvoiceSwapQuotes: func(req InvoiceSwapRequest) (*InvoiceSwapQuoteList, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/invoice/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 := InvoiceSwapQuoteList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
GetAdminTransactionSwapQuotes: func(req TransactionSwapRequest) (*TransactionSwapQuoteList, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
|
|
@ -1324,7 +1357,7 @@ func NewClient(params ClientParams) *Client {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/user/swap/quote"
|
||||
finalRoute := "/api/user/swap/transaction/quote"
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1643,12 +1676,12 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
ListAdminSwaps: func() (*SwapsList, error) {
|
||||
ListAdminInvoiceSwaps: func() (*InvoiceSwapsList, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/list"
|
||||
finalRoute := "/api/admin/swap/invoice/list"
|
||||
body := []byte{}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
|
|
@ -1662,7 +1695,33 @@ func NewClient(params ClientParams) *Client {
|
|||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := SwapsList{}
|
||||
res := InvoiceSwapsList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
ListAdminTxSwaps: func() (*TxSwapsList, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/transaction/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 := TxSwapsList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1691,12 +1750,12 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return &res, nil
|
||||
},
|
||||
ListSwaps: func() (*SwapsList, error) {
|
||||
ListTxSwaps: func() (*TxSwapsList, error) {
|
||||
auth, err := params.RetrieveUserAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/user/swap/list"
|
||||
finalRoute := "/api/user/swap/transaction/list"
|
||||
body := []byte{}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
|
|
@ -1710,7 +1769,7 @@ func NewClient(params ClientParams) *Client {
|
|||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := SwapsList{}
|
||||
res := TxSwapsList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1892,7 +1951,36 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return &res, nil
|
||||
},
|
||||
PayAdminTransactionSwap: func(req PayAdminTransactionSwapRequest) (*AdminSwapResponse, error) {
|
||||
PayAdminInvoiceSwap: func(req PayAdminInvoiceSwapRequest) (*AdminInvoiceSwapResponse, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/invoice/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 := AdminInvoiceSwapResponse{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
PayAdminTransactionSwap: func(req PayAdminTransactionSwapRequest) (*AdminTxSwapResponse, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1914,7 +2002,7 @@ func NewClient(params ClientParams) *Client {
|
|||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := AdminSwapResponse{}
|
||||
res := AdminTxSwapResponse{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -2000,6 +2088,35 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
RefundAdminInvoiceSwap: func(req RefundAdminInvoiceSwapRequest) (*AdminInvoiceSwapResponse, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/invoice/refund"
|
||||
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 := AdminInvoiceSwapResponse{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
RequestNPubLinkingToken: func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error) {
|
||||
auth, err := params.RetrieveAppAuth()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,10 @@ type AddProductRequest struct {
|
|||
Name string `json:"name"`
|
||||
Price_sats int64 `json:"price_sats"`
|
||||
}
|
||||
type AdminSwapResponse struct {
|
||||
type AdminInvoiceSwapResponse struct {
|
||||
Tx_id string `json:"tx_id"`
|
||||
}
|
||||
type AdminTxSwapResponse struct {
|
||||
Network_fee int64 `json:"network_fee"`
|
||||
Tx_id string `json:"tx_id"`
|
||||
}
|
||||
|
|
@ -356,6 +359,35 @@ type HttpCreds struct {
|
|||
Token string `json:"token"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
type InvoiceSwapOperation struct {
|
||||
Failure_reason string `json:"failure_reason"`
|
||||
Invoice_paid string `json:"invoice_paid"`
|
||||
Operation_payment *UserOperation `json:"operation_payment"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
Tx_id string `json:"tx_id"`
|
||||
}
|
||||
type InvoiceSwapQuote struct {
|
||||
Address string `json:"address"`
|
||||
Chain_fee_sats int64 `json:"chain_fee_sats"`
|
||||
Invoice string `json:"invoice"`
|
||||
Invoice_amount_sats int64 `json:"invoice_amount_sats"`
|
||||
Service_fee_sats int64 `json:"service_fee_sats"`
|
||||
Service_url string `json:"service_url"`
|
||||
Swap_fee_sats int64 `json:"swap_fee_sats"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
Transaction_amount_sats int64 `json:"transaction_amount_sats"`
|
||||
Tx_id string `json:"tx_id"`
|
||||
}
|
||||
type InvoiceSwapQuoteList struct {
|
||||
Quotes []InvoiceSwapQuote `json:"quotes"`
|
||||
}
|
||||
type InvoiceSwapRequest struct {
|
||||
Amount_sats int64 `json:"amount_sats"`
|
||||
}
|
||||
type InvoiceSwapsList struct {
|
||||
Quotes []InvoiceSwapQuote `json:"quotes"`
|
||||
Swaps []InvoiceSwapOperation `json:"swaps"`
|
||||
}
|
||||
type LatestBundleMetricReq struct {
|
||||
Limit int64 `json:"limit"`
|
||||
}
|
||||
|
|
@ -559,6 +591,11 @@ type PayAddressResponse struct {
|
|||
Service_fee int64 `json:"service_fee"`
|
||||
Txid string `json:"txId"`
|
||||
}
|
||||
type PayAdminInvoiceSwapRequest struct {
|
||||
No_claim bool `json:"no_claim"`
|
||||
Sat_per_v_byte int64 `json:"sat_per_v_byte"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
}
|
||||
type PayAdminTransactionSwapRequest struct {
|
||||
Address string `json:"address"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
|
|
@ -609,6 +646,10 @@ type ProviderDisruption struct {
|
|||
type ProvidersDisruption struct {
|
||||
Disruptions []ProviderDisruption `json:"disruptions"`
|
||||
}
|
||||
type RefundAdminInvoiceSwapRequest struct {
|
||||
Sat_per_v_byte int64 `json:"sat_per_v_byte"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
}
|
||||
type RelaysMigration struct {
|
||||
Relays []string `json:"relays"`
|
||||
}
|
||||
|
|
@ -665,16 +706,6 @@ 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 {
|
||||
Quotes []TransactionSwapQuote `json:"quotes"`
|
||||
Swaps []SwapOperation `json:"swaps"`
|
||||
}
|
||||
type TransactionSwapQuote struct {
|
||||
Chain_fee_sats int64 `json:"chain_fee_sats"`
|
||||
Invoice_amount_sats int64 `json:"invoice_amount_sats"`
|
||||
|
|
@ -690,6 +721,16 @@ type TransactionSwapQuoteList struct {
|
|||
type TransactionSwapRequest struct {
|
||||
Transaction_amount_sats int64 `json:"transaction_amount_sats"`
|
||||
}
|
||||
type TxSwapOperation 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 TxSwapsList struct {
|
||||
Quotes []TransactionSwapQuote `json:"quotes"`
|
||||
Swaps []TxSwapOperation `json:"swaps"`
|
||||
}
|
||||
type UpdateChannelPolicyRequest struct {
|
||||
Policy *ChannelPolicy `json:"policy"`
|
||||
Update *UpdateChannelPolicyRequest_update `json:"update"`
|
||||
|
|
|
|||
|
|
@ -545,12 +545,12 @@ 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' )
|
||||
case 'ListTxSwaps':
|
||||
if (!methods.ListTxSwaps) {
|
||||
throw new Error('method ListTxSwaps not found' )
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.ListSwaps({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
const res = await methods.ListTxSwaps({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
|
|
@ -869,6 +869,28 @@ 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.GetAdminInvoiceSwapQuotes) throw new Error('method: GetAdminInvoiceSwapQuotes is not implemented')
|
||||
app.post('/api/admin/swap/invoice/quote', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetAdminInvoiceSwapQuotes', 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.GetAdminInvoiceSwapQuotes) throw new Error('method: GetAdminInvoiceSwapQuotes 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.InvoiceSwapRequestValidate(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.GetAdminInvoiceSwapQuotes({rpcName:'GetAdminInvoiceSwapQuotes', 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.GetAdminTransactionSwapQuotes) throw new Error('method: GetAdminTransactionSwapQuotes is not implemented')
|
||||
app.post('/api/admin/swap/transaction/quote', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetAdminTransactionSwapQuotes', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
@ -1362,7 +1384,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
} 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.GetTransactionSwapQuotes) throw new Error('method: GetTransactionSwapQuotes is not implemented')
|
||||
app.post('/api/user/swap/quote', async (req, res) => {
|
||||
app.post('/api/user/swap/transaction/quote', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetTransactionSwapQuotes', 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 = {}
|
||||
|
|
@ -1607,20 +1629,39 @@ 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.ListAdminSwaps) throw new Error('method: ListAdminSwaps is not implemented')
|
||||
app.post('/api/admin/swap/list', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'ListAdminSwaps', batch: false, nostr: false, batchSize: 0}
|
||||
if (!opts.allowNotImplementedMethods && !methods.ListAdminInvoiceSwaps) throw new Error('method: ListAdminInvoiceSwaps is not implemented')
|
||||
app.post('/api/admin/swap/invoice/list', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'ListAdminInvoiceSwaps', 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.ListAdminSwaps) throw new Error('method: ListAdminSwaps is not implemented')
|
||||
if (!methods.ListAdminInvoiceSwaps) throw new Error('method: ListAdminInvoiceSwaps is not implemented')
|
||||
const authContext = await opts.AdminAuthGuard(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.ListAdminSwaps({rpcName:'ListAdminSwaps', ctx:authContext })
|
||||
const response = await methods.ListAdminInvoiceSwaps({rpcName:'ListAdminInvoiceSwaps', 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.ListAdminTxSwaps) throw new Error('method: ListAdminTxSwaps is not implemented')
|
||||
app.post('/api/admin/swap/transaction/list', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'ListAdminTxSwaps', 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.ListAdminTxSwaps) throw new Error('method: ListAdminTxSwaps is not implemented')
|
||||
const authContext = await opts.AdminAuthGuard(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.ListAdminTxSwaps({rpcName:'ListAdminTxSwaps', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res.json({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
|
|
@ -1645,20 +1686,20 @@ 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}
|
||||
if (!opts.allowNotImplementedMethods && !methods.ListTxSwaps) throw new Error('method: ListTxSwaps is not implemented')
|
||||
app.post('/api/user/swap/transaction/list', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'ListTxSwaps', 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')
|
||||
if (!methods.ListTxSwaps) throw new Error('method: ListTxSwaps 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 })
|
||||
const response = await methods.ListTxSwaps({rpcName:'ListTxSwaps', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res.json({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
|
|
@ -1793,6 +1834,28 @@ 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.PayAdminInvoiceSwap) throw new Error('method: PayAdminInvoiceSwap is not implemented')
|
||||
app.post('/api/admin/swap/invoice/pay', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'PayAdminInvoiceSwap', 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.PayAdminInvoiceSwap) throw new Error('method: PayAdminInvoiceSwap 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.PayAdminInvoiceSwapRequestValidate(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.PayAdminInvoiceSwap({rpcName:'PayAdminInvoiceSwap', 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.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}
|
||||
|
|
@ -1878,6 +1941,28 @@ 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.RefundAdminInvoiceSwap) throw new Error('method: RefundAdminInvoiceSwap is not implemented')
|
||||
app.post('/api/admin/swap/invoice/refund', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'RefundAdminInvoiceSwap', 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.RefundAdminInvoiceSwap) throw new Error('method: RefundAdminInvoiceSwap 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.RefundAdminInvoiceSwapRequestValidate(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.RefundAdminInvoiceSwap({rpcName:'RefundAdminInvoiceSwap', 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.RequestNPubLinkingToken) throw new Error('method: RequestNPubLinkingToken is not implemented')
|
||||
app.post('/api/app/user/npub/token', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'RequestNPubLinkingToken', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
|
|||
|
|
@ -273,6 +273,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetAdminInvoiceSwapQuotes: async (request: Types.InvoiceSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.InvoiceSwapQuoteList)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/invoice/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.InvoiceSwapQuoteListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetAdminTransactionSwapQuotes: async (request: Types.TransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.TransactionSwapQuoteList)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
|
|
@ -620,7 +634,7 @@ export default (params: ClientParams) => ({
|
|||
GetTransactionSwapQuotes: async (request: Types.TransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.TransactionSwapQuoteList)> => {
|
||||
const auth = await params.retrieveUserAuth()
|
||||
if (auth === null) throw new Error('retrieveUserAuth() returned null')
|
||||
let finalRoute = '/api/user/swap/quote'
|
||||
let finalRoute = '/api/user/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') {
|
||||
|
|
@ -781,16 +795,30 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListAdminSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
ListAdminInvoiceSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.InvoiceSwapsList)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/list'
|
||||
let finalRoute = '/api/admin/swap/invoice/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)
|
||||
const error = Types.InvoiceSwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListAdminTxSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.TxSwapsList)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/transaction/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.TxSwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
|
|
@ -809,16 +837,16 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
ListTxSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.TxSwapsList)> => {
|
||||
const auth = await params.retrieveUserAuth()
|
||||
if (auth === null) throw new Error('retrieveUserAuth() returned null')
|
||||
let finalRoute = '/api/user/swap/list'
|
||||
let finalRoute = '/api/user/swap/transaction/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)
|
||||
const error = Types.TxSwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
|
|
@ -909,7 +937,21 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAdminTransactionSwap: async (request: Types.PayAdminTransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminSwapResponse)> => {
|
||||
PayAdminInvoiceSwap: async (request: Types.PayAdminInvoiceSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminInvoiceSwapResponse)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/invoice/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.AdminInvoiceSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAdminTransactionSwap: async (request: Types.PayAdminTransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminTxSwapResponse)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/transaction/pay'
|
||||
|
|
@ -918,7 +960,7 @@ export default (params: ClientParams) => ({
|
|||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.AdminSwapResponseValidate(result)
|
||||
const error = Types.AdminTxSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
|
|
@ -962,6 +1004,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
RefundAdminInvoiceSwap: async (request: Types.RefundAdminInvoiceSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminInvoiceSwapResponse)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/invoice/refund'
|
||||
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.AdminInvoiceSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
RequestNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise<ResultError | ({ status: 'OK' }& Types.RequestNPubLinkingTokenResponse)> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
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' }
|
||||
},
|
||||
GetAdminInvoiceSwapQuotes: async (request: Types.InvoiceSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.InvoiceSwapQuoteList)> => {
|
||||
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:'GetAdminInvoiceSwapQuotes',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.InvoiceSwapQuoteListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetAdminTransactionSwapQuotes: async (request: Types.TransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.TransactionSwapQuoteList)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
|
|
@ -666,16 +681,30 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListAdminSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
ListAdminInvoiceSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.InvoiceSwapsList)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'ListAdminSwaps',authIdentifier:auth, ...nostrRequest })
|
||||
const data = await send(params.pubDestination, {rpcName:'ListAdminInvoiceSwaps',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)
|
||||
const error = Types.InvoiceSwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListAdminTxSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.TxSwapsList)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'ListAdminTxSwaps',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.TxSwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
|
|
@ -694,16 +723,16 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
ListTxSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.TxSwapsList)> => {
|
||||
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 })
|
||||
const data = await send(params.pubDestination, {rpcName:'ListTxSwaps',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)
|
||||
const error = Types.TxSwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
|
|
@ -798,7 +827,22 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAdminTransactionSwap: async (request: Types.PayAdminTransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminSwapResponse)> => {
|
||||
PayAdminInvoiceSwap: async (request: Types.PayAdminInvoiceSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminInvoiceSwapResponse)> => {
|
||||
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:'PayAdminInvoiceSwap',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.AdminInvoiceSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAdminTransactionSwap: async (request: Types.PayAdminTransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminTxSwapResponse)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
|
|
@ -808,7 +852,7 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.AdminSwapResponseValidate(result)
|
||||
const error = Types.AdminTxSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
|
|
@ -839,6 +883,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
RefundAdminInvoiceSwap: async (request: Types.RefundAdminInvoiceSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminInvoiceSwapResponse)> => {
|
||||
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:'RefundAdminInvoiceSwap',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.AdminInvoiceSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ResetDebit: async (request: Types.DebitOperation): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -427,12 +427,12 @@ 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')
|
||||
case 'ListTxSwaps':
|
||||
if (!methods.ListTxSwaps) {
|
||||
throw new Error('method not defined: ListTxSwaps')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.ListSwaps({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
const res = await methods.ListTxSwaps({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
|
|
@ -687,6 +687,22 @@ 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 'GetAdminInvoiceSwapQuotes':
|
||||
try {
|
||||
if (!methods.GetAdminInvoiceSwapQuotes) throw new Error('method: GetAdminInvoiceSwapQuotes 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.InvoiceSwapRequestValidate(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.GetAdminInvoiceSwapQuotes({rpcName:'GetAdminInvoiceSwapQuotes', 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 'GetAdminTransactionSwapQuotes':
|
||||
try {
|
||||
if (!methods.GetAdminTransactionSwapQuotes) throw new Error('method: GetAdminTransactionSwapQuotes is not implemented')
|
||||
|
|
@ -1122,14 +1138,27 @@ 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 'ListAdminSwaps':
|
||||
case 'ListAdminInvoiceSwaps':
|
||||
try {
|
||||
if (!methods.ListAdminSwaps) throw new Error('method: ListAdminSwaps is not implemented')
|
||||
if (!methods.ListAdminInvoiceSwaps) throw new Error('method: ListAdminInvoiceSwaps is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.ListAdminSwaps({rpcName:'ListAdminSwaps', ctx:authContext })
|
||||
const response = await methods.ListAdminInvoiceSwaps({rpcName:'ListAdminInvoiceSwaps', 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 'ListAdminTxSwaps':
|
||||
try {
|
||||
if (!methods.ListAdminTxSwaps) throw new Error('method: ListAdminTxSwaps is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.ListAdminTxSwaps({rpcName:'ListAdminTxSwaps', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
|
|
@ -1148,14 +1177,14 @@ 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':
|
||||
case 'ListTxSwaps':
|
||||
try {
|
||||
if (!methods.ListSwaps) throw new Error('method: ListSwaps is not implemented')
|
||||
if (!methods.ListTxSwaps) throw new Error('method: ListTxSwaps 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 })
|
||||
const response = await methods.ListTxSwaps({rpcName:'ListTxSwaps', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
|
|
@ -1254,6 +1283,22 @@ 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 'PayAdminInvoiceSwap':
|
||||
try {
|
||||
if (!methods.PayAdminInvoiceSwap) throw new Error('method: PayAdminInvoiceSwap 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.PayAdminInvoiceSwapRequestValidate(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.PayAdminInvoiceSwap({rpcName:'PayAdminInvoiceSwap', 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 'PayAdminTransactionSwap':
|
||||
try {
|
||||
if (!methods.PayAdminTransactionSwap) throw new Error('method: PayAdminTransactionSwap is not implemented')
|
||||
|
|
@ -1299,6 +1344,22 @@ 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 'RefundAdminInvoiceSwap':
|
||||
try {
|
||||
if (!methods.RefundAdminInvoiceSwap) throw new Error('method: RefundAdminInvoiceSwap 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.RefundAdminInvoiceSwapRequestValidate(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.RefundAdminInvoiceSwap({rpcName:'RefundAdminInvoiceSwap', 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 'ResetDebit':
|
||||
try {
|
||||
if (!methods.ResetDebit) throw new Error('method: ResetDebit is not implemented')
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export type RequestMetric = AuthContext & RequestInfo & RequestStats & { error?:
|
|||
export type AdminContext = {
|
||||
admin_id: string
|
||||
}
|
||||
export type AdminMethodInputs = AddApp_Input | AddPeer_Input | AuthApp_Input | BanUser_Input | CloseChannel_Input | CreateOneTimeInviteLink_Input | GetAdminTransactionSwapQuotes_Input | GetInviteLinkState_Input | GetSeed_Input | ListAdminSwaps_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 | GetAdminTransactionSwapQuotes_Output | GetInviteLinkState_Output | GetSeed_Output | ListAdminSwaps_Output | ListChannels_Output | LndGetInfo_Output | OpenChannel_Output | PayAdminTransactionSwap_Output | UpdateChannelPolicy_Output
|
||||
export type AdminMethodInputs = AddApp_Input | AddPeer_Input | AuthApp_Input | BanUser_Input | CloseChannel_Input | CreateOneTimeInviteLink_Input | GetAdminInvoiceSwapQuotes_Input | GetAdminTransactionSwapQuotes_Input | GetInviteLinkState_Input | GetSeed_Input | ListAdminInvoiceSwaps_Input | ListAdminTxSwaps_Input | ListChannels_Input | LndGetInfo_Input | OpenChannel_Input | PayAdminInvoiceSwap_Input | PayAdminTransactionSwap_Input | RefundAdminInvoiceSwap_Input | UpdateChannelPolicy_Input
|
||||
export type AdminMethodOutputs = AddApp_Output | AddPeer_Output | AuthApp_Output | BanUser_Output | CloseChannel_Output | CreateOneTimeInviteLink_Output | GetAdminInvoiceSwapQuotes_Output | GetAdminTransactionSwapQuotes_Output | GetInviteLinkState_Output | GetSeed_Output | ListAdminInvoiceSwaps_Output | ListAdminTxSwaps_Output | ListChannels_Output | LndGetInfo_Output | OpenChannel_Output | PayAdminInvoiceSwap_Output | PayAdminTransactionSwap_Output | RefundAdminInvoiceSwap_Output | UpdateChannelPolicy_Output
|
||||
export type AppContext = {
|
||||
app_id: string
|
||||
}
|
||||
|
|
@ -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 | GetTransactionSwapQuotes_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 | GetTransactionSwapQuotes_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 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 | GetTransactionSwapQuotes_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | ListTxSwaps_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 | GetTransactionSwapQuotes_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | ListTxSwaps_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}
|
||||
|
|
@ -99,6 +99,9 @@ export type EnrollAdminToken_Output = ResultError | { status: 'OK' }
|
|||
export type EnrollMessagingToken_Input = {rpcName:'EnrollMessagingToken', req: MessagingToken}
|
||||
export type EnrollMessagingToken_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type GetAdminInvoiceSwapQuotes_Input = {rpcName:'GetAdminInvoiceSwapQuotes', req: InvoiceSwapRequest}
|
||||
export type GetAdminInvoiceSwapQuotes_Output = ResultError | ({ status: 'OK' } & InvoiceSwapQuoteList)
|
||||
|
||||
export type GetAdminTransactionSwapQuotes_Input = {rpcName:'GetAdminTransactionSwapQuotes', req: TransactionSwapRequest}
|
||||
export type GetAdminTransactionSwapQuotes_Output = ResultError | ({ status: 'OK' } & TransactionSwapQuoteList)
|
||||
|
||||
|
|
@ -238,14 +241,17 @@ export type Health_Output = ResultError | { status: 'OK' }
|
|||
export type LinkNPubThroughToken_Input = {rpcName:'LinkNPubThroughToken', req: LinkNPubThroughTokenRequest}
|
||||
export type LinkNPubThroughToken_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type ListAdminSwaps_Input = {rpcName:'ListAdminSwaps'}
|
||||
export type ListAdminSwaps_Output = ResultError | ({ status: 'OK' } & SwapsList)
|
||||
export type ListAdminInvoiceSwaps_Input = {rpcName:'ListAdminInvoiceSwaps'}
|
||||
export type ListAdminInvoiceSwaps_Output = ResultError | ({ status: 'OK' } & InvoiceSwapsList)
|
||||
|
||||
export type ListAdminTxSwaps_Input = {rpcName:'ListAdminTxSwaps'}
|
||||
export type ListAdminTxSwaps_Output = ResultError | ({ status: 'OK' } & TxSwapsList)
|
||||
|
||||
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 ListTxSwaps_Input = {rpcName:'ListTxSwaps'}
|
||||
export type ListTxSwaps_Output = ResultError | ({ status: 'OK' } & TxSwapsList)
|
||||
|
||||
export type LndGetInfo_Input = {rpcName:'LndGetInfo', req: LndGetInfoRequest}
|
||||
export type LndGetInfo_Output = ResultError | ({ status: 'OK' } & LndGetInfoResponse)
|
||||
|
|
@ -268,8 +274,11 @@ export type OpenChannel_Output = ResultError | ({ status: 'OK' } & OpenChannelRe
|
|||
export type PayAddress_Input = {rpcName:'PayAddress', req: PayAddressRequest}
|
||||
export type PayAddress_Output = ResultError | ({ status: 'OK' } & PayAddressResponse)
|
||||
|
||||
export type PayAdminInvoiceSwap_Input = {rpcName:'PayAdminInvoiceSwap', req: PayAdminInvoiceSwapRequest}
|
||||
export type PayAdminInvoiceSwap_Output = ResultError | ({ status: 'OK' } & AdminInvoiceSwapResponse)
|
||||
|
||||
export type PayAdminTransactionSwap_Input = {rpcName:'PayAdminTransactionSwap', req: PayAdminTransactionSwapRequest}
|
||||
export type PayAdminTransactionSwap_Output = ResultError | ({ status: 'OK' } & AdminSwapResponse)
|
||||
export type PayAdminTransactionSwap_Output = ResultError | ({ status: 'OK' } & AdminTxSwapResponse)
|
||||
|
||||
export type PayAppUserInvoice_Input = {rpcName:'PayAppUserInvoice', req: PayAppUserInvoiceRequest}
|
||||
export type PayAppUserInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResponse)
|
||||
|
|
@ -280,6 +289,9 @@ export type PayInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResp
|
|||
export type PingSubProcesses_Input = {rpcName:'PingSubProcesses'}
|
||||
export type PingSubProcesses_Output = ResultError | { status: 'OK' }
|
||||
|
||||
export type RefundAdminInvoiceSwap_Input = {rpcName:'RefundAdminInvoiceSwap', req: RefundAdminInvoiceSwapRequest}
|
||||
export type RefundAdminInvoiceSwap_Output = ResultError | ({ status: 'OK' } & AdminInvoiceSwapResponse)
|
||||
|
||||
export type RequestNPubLinkingToken_Input = {rpcName:'RequestNPubLinkingToken', req: RequestNPubLinkingTokenRequest}
|
||||
export type RequestNPubLinkingToken_Output = ResultError | ({ status: 'OK' } & RequestNPubLinkingTokenResponse)
|
||||
|
||||
|
|
@ -357,6 +369,7 @@ export type ServerMethods = {
|
|||
EncryptionExchange?: (req: EncryptionExchange_Input & {ctx: GuestContext }) => Promise<void>
|
||||
EnrollAdminToken?: (req: EnrollAdminToken_Input & {ctx: UserContext }) => Promise<void>
|
||||
EnrollMessagingToken?: (req: EnrollMessagingToken_Input & {ctx: UserContext }) => Promise<void>
|
||||
GetAdminInvoiceSwapQuotes?: (req: GetAdminInvoiceSwapQuotes_Input & {ctx: AdminContext }) => Promise<InvoiceSwapQuoteList>
|
||||
GetAdminTransactionSwapQuotes?: (req: GetAdminTransactionSwapQuotes_Input & {ctx: AdminContext }) => Promise<TransactionSwapQuoteList>
|
||||
GetApp?: (req: GetApp_Input & {ctx: AppContext }) => Promise<Application>
|
||||
GetAppUser?: (req: GetAppUser_Input & {ctx: AppContext }) => Promise<AppUser>
|
||||
|
|
@ -397,19 +410,22 @@ export type ServerMethods = {
|
|||
HandleLnurlWithdraw?: (req: HandleLnurlWithdraw_Input & {ctx: GuestContext }) => Promise<void>
|
||||
Health?: (req: Health_Input & {ctx: GuestContext }) => Promise<void>
|
||||
LinkNPubThroughToken?: (req: LinkNPubThroughToken_Input & {ctx: GuestWithPubContext }) => Promise<void>
|
||||
ListAdminSwaps?: (req: ListAdminSwaps_Input & {ctx: AdminContext }) => Promise<SwapsList>
|
||||
ListAdminInvoiceSwaps?: (req: ListAdminInvoiceSwaps_Input & {ctx: AdminContext }) => Promise<InvoiceSwapsList>
|
||||
ListAdminTxSwaps?: (req: ListAdminTxSwaps_Input & {ctx: AdminContext }) => Promise<TxSwapsList>
|
||||
ListChannels?: (req: ListChannels_Input & {ctx: AdminContext }) => Promise<LndChannels>
|
||||
ListSwaps?: (req: ListSwaps_Input & {ctx: UserContext }) => Promise<SwapsList>
|
||||
ListTxSwaps?: (req: ListTxSwaps_Input & {ctx: UserContext }) => Promise<TxSwapsList>
|
||||
LndGetInfo?: (req: LndGetInfo_Input & {ctx: AdminContext }) => Promise<LndGetInfoResponse>
|
||||
NewAddress?: (req: NewAddress_Input & {ctx: UserContext }) => Promise<NewAddressResponse>
|
||||
NewInvoice?: (req: NewInvoice_Input & {ctx: UserContext }) => Promise<NewInvoiceResponse>
|
||||
NewProductInvoice?: (req: NewProductInvoice_Input & {ctx: UserContext }) => Promise<NewInvoiceResponse>
|
||||
OpenChannel?: (req: OpenChannel_Input & {ctx: AdminContext }) => Promise<OpenChannelResponse>
|
||||
PayAddress?: (req: PayAddress_Input & {ctx: UserContext }) => Promise<PayAddressResponse>
|
||||
PayAdminTransactionSwap?: (req: PayAdminTransactionSwap_Input & {ctx: AdminContext }) => Promise<AdminSwapResponse>
|
||||
PayAdminInvoiceSwap?: (req: PayAdminInvoiceSwap_Input & {ctx: AdminContext }) => Promise<AdminInvoiceSwapResponse>
|
||||
PayAdminTransactionSwap?: (req: PayAdminTransactionSwap_Input & {ctx: AdminContext }) => Promise<AdminTxSwapResponse>
|
||||
PayAppUserInvoice?: (req: PayAppUserInvoice_Input & {ctx: AppContext }) => Promise<PayInvoiceResponse>
|
||||
PayInvoice?: (req: PayInvoice_Input & {ctx: UserContext }) => Promise<PayInvoiceResponse>
|
||||
PingSubProcesses?: (req: PingSubProcesses_Input & {ctx: MetricsContext }) => Promise<void>
|
||||
RefundAdminInvoiceSwap?: (req: RefundAdminInvoiceSwap_Input & {ctx: AdminContext }) => Promise<AdminInvoiceSwapResponse>
|
||||
RequestNPubLinkingToken?: (req: RequestNPubLinkingToken_Input & {ctx: AppContext }) => Promise<RequestNPubLinkingTokenResponse>
|
||||
ResetDebit?: (req: ResetDebit_Input & {ctx: UserContext }) => Promise<void>
|
||||
ResetManage?: (req: ResetManage_Input & {ctx: UserContext }) => Promise<void>
|
||||
|
|
@ -671,17 +687,35 @@ export const AddProductRequestValidate = (o?: AddProductRequest, opts: AddProduc
|
|||
return null
|
||||
}
|
||||
|
||||
export type AdminSwapResponse = {
|
||||
export type AdminInvoiceSwapResponse = {
|
||||
tx_id: string
|
||||
}
|
||||
export const AdminInvoiceSwapResponseOptionalFields: [] = []
|
||||
export type AdminInvoiceSwapResponseOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
tx_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const AdminInvoiceSwapResponseValidate = (o?: AdminInvoiceSwapResponse, opts: AdminInvoiceSwapResponseOptions = {}, path: string = 'AdminInvoiceSwapResponse::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.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 AdminTxSwapResponse = {
|
||||
network_fee: number
|
||||
tx_id: string
|
||||
}
|
||||
export const AdminSwapResponseOptionalFields: [] = []
|
||||
export type AdminSwapResponseOptions = OptionsBaseMessage & {
|
||||
export const AdminTxSwapResponseOptionalFields: [] = []
|
||||
export type AdminTxSwapResponseOptions = 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 => {
|
||||
export const AdminTxSwapResponseValidate = (o?: AdminTxSwapResponse, opts: AdminTxSwapResponseOptions = {}, path: string = 'AdminTxSwapResponse::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')
|
||||
|
||||
|
|
@ -2088,6 +2122,185 @@ export const HttpCredsValidate = (o?: HttpCreds, opts: HttpCredsOptions = {}, pa
|
|||
return null
|
||||
}
|
||||
|
||||
export type InvoiceSwapOperation = {
|
||||
failure_reason?: string
|
||||
invoice_paid: string
|
||||
operation_payment?: UserOperation
|
||||
swap_operation_id: string
|
||||
tx_id: string
|
||||
}
|
||||
export type InvoiceSwapOperationOptionalField = 'failure_reason' | 'operation_payment'
|
||||
export const InvoiceSwapOperationOptionalFields: InvoiceSwapOperationOptionalField[] = ['failure_reason', 'operation_payment']
|
||||
export type InvoiceSwapOperationOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: InvoiceSwapOperationOptionalField[]
|
||||
failure_reason_CustomCheck?: (v?: string) => boolean
|
||||
invoice_paid_CustomCheck?: (v: string) => boolean
|
||||
operation_payment_Options?: UserOperationOptions
|
||||
swap_operation_id_CustomCheck?: (v: string) => boolean
|
||||
tx_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const InvoiceSwapOperationValidate = (o?: InvoiceSwapOperation, opts: InvoiceSwapOperationOptions = {}, path: string = 'InvoiceSwapOperation::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 ((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.invoice_paid !== 'string') return new Error(`${path}.invoice_paid: is not a string`)
|
||||
if (opts.invoice_paid_CustomCheck && !opts.invoice_paid_CustomCheck(o.invoice_paid)) return new Error(`${path}.invoice_paid: 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`)
|
||||
|
||||
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 InvoiceSwapQuote = {
|
||||
address: string
|
||||
chain_fee_sats: number
|
||||
invoice: string
|
||||
invoice_amount_sats: number
|
||||
service_fee_sats: number
|
||||
service_url: string
|
||||
swap_fee_sats: number
|
||||
swap_operation_id: string
|
||||
transaction_amount_sats: number
|
||||
tx_id: string
|
||||
}
|
||||
export const InvoiceSwapQuoteOptionalFields: [] = []
|
||||
export type InvoiceSwapQuoteOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
address_CustomCheck?: (v: string) => boolean
|
||||
chain_fee_sats_CustomCheck?: (v: number) => boolean
|
||||
invoice_CustomCheck?: (v: string) => boolean
|
||||
invoice_amount_sats_CustomCheck?: (v: number) => boolean
|
||||
service_fee_sats_CustomCheck?: (v: number) => boolean
|
||||
service_url_CustomCheck?: (v: string) => boolean
|
||||
swap_fee_sats_CustomCheck?: (v: number) => boolean
|
||||
swap_operation_id_CustomCheck?: (v: string) => boolean
|
||||
transaction_amount_sats_CustomCheck?: (v: number) => boolean
|
||||
tx_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const InvoiceSwapQuoteValidate = (o?: InvoiceSwapQuote, opts: InvoiceSwapQuoteOptions = {}, path: string = 'InvoiceSwapQuote::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.chain_fee_sats !== 'number') return new Error(`${path}.chain_fee_sats: is not a number`)
|
||||
if (opts.chain_fee_sats_CustomCheck && !opts.chain_fee_sats_CustomCheck(o.chain_fee_sats)) return new Error(`${path}.chain_fee_sats: custom check failed`)
|
||||
|
||||
if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`)
|
||||
if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`)
|
||||
|
||||
if (typeof o.invoice_amount_sats !== 'number') return new Error(`${path}.invoice_amount_sats: is not a number`)
|
||||
if (opts.invoice_amount_sats_CustomCheck && !opts.invoice_amount_sats_CustomCheck(o.invoice_amount_sats)) return new Error(`${path}.invoice_amount_sats: custom check failed`)
|
||||
|
||||
if (typeof o.service_fee_sats !== 'number') return new Error(`${path}.service_fee_sats: is not a number`)
|
||||
if (opts.service_fee_sats_CustomCheck && !opts.service_fee_sats_CustomCheck(o.service_fee_sats)) return new Error(`${path}.service_fee_sats: custom check failed`)
|
||||
|
||||
if (typeof o.service_url !== 'string') return new Error(`${path}.service_url: is not a string`)
|
||||
if (opts.service_url_CustomCheck && !opts.service_url_CustomCheck(o.service_url)) return new Error(`${path}.service_url: custom check failed`)
|
||||
|
||||
if (typeof o.swap_fee_sats !== 'number') return new Error(`${path}.swap_fee_sats: is not a number`)
|
||||
if (opts.swap_fee_sats_CustomCheck && !opts.swap_fee_sats_CustomCheck(o.swap_fee_sats)) return new Error(`${path}.swap_fee_sats: 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`)
|
||||
|
||||
if (typeof o.transaction_amount_sats !== 'number') return new Error(`${path}.transaction_amount_sats: is not a number`)
|
||||
if (opts.transaction_amount_sats_CustomCheck && !opts.transaction_amount_sats_CustomCheck(o.transaction_amount_sats)) return new Error(`${path}.transaction_amount_sats: 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 InvoiceSwapQuoteList = {
|
||||
quotes: InvoiceSwapQuote[]
|
||||
}
|
||||
export const InvoiceSwapQuoteListOptionalFields: [] = []
|
||||
export type InvoiceSwapQuoteListOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
quotes_ItemOptions?: InvoiceSwapQuoteOptions
|
||||
quotes_CustomCheck?: (v: InvoiceSwapQuote[]) => boolean
|
||||
}
|
||||
export const InvoiceSwapQuoteListValidate = (o?: InvoiceSwapQuoteList, opts: InvoiceSwapQuoteListOptions = {}, path: string = 'InvoiceSwapQuoteList::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.quotes)) return new Error(`${path}.quotes: is not an array`)
|
||||
for (let index = 0; index < o.quotes.length; index++) {
|
||||
const quotesErr = InvoiceSwapQuoteValidate(o.quotes[index], opts.quotes_ItemOptions, `${path}.quotes[${index}]`)
|
||||
if (quotesErr !== null) return quotesErr
|
||||
}
|
||||
if (opts.quotes_CustomCheck && !opts.quotes_CustomCheck(o.quotes)) return new Error(`${path}.quotes: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type InvoiceSwapRequest = {
|
||||
amount_sats: number
|
||||
}
|
||||
export const InvoiceSwapRequestOptionalFields: [] = []
|
||||
export type InvoiceSwapRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
amount_sats_CustomCheck?: (v: number) => boolean
|
||||
}
|
||||
export const InvoiceSwapRequestValidate = (o?: InvoiceSwapRequest, opts: InvoiceSwapRequestOptions = {}, path: string = 'InvoiceSwapRequest::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.amount_sats !== 'number') return new Error(`${path}.amount_sats: is not a number`)
|
||||
if (opts.amount_sats_CustomCheck && !opts.amount_sats_CustomCheck(o.amount_sats)) return new Error(`${path}.amount_sats: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type InvoiceSwapsList = {
|
||||
quotes: InvoiceSwapQuote[]
|
||||
swaps: InvoiceSwapOperation[]
|
||||
}
|
||||
export const InvoiceSwapsListOptionalFields: [] = []
|
||||
export type InvoiceSwapsListOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
quotes_ItemOptions?: InvoiceSwapQuoteOptions
|
||||
quotes_CustomCheck?: (v: InvoiceSwapQuote[]) => boolean
|
||||
swaps_ItemOptions?: InvoiceSwapOperationOptions
|
||||
swaps_CustomCheck?: (v: InvoiceSwapOperation[]) => boolean
|
||||
}
|
||||
export const InvoiceSwapsListValidate = (o?: InvoiceSwapsList, opts: InvoiceSwapsListOptions = {}, path: string = 'InvoiceSwapsList::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.quotes)) return new Error(`${path}.quotes: is not an array`)
|
||||
for (let index = 0; index < o.quotes.length; index++) {
|
||||
const quotesErr = InvoiceSwapQuoteValidate(o.quotes[index], opts.quotes_ItemOptions, `${path}.quotes[${index}]`)
|
||||
if (quotesErr !== null) return quotesErr
|
||||
}
|
||||
if (opts.quotes_CustomCheck && !opts.quotes_CustomCheck(o.quotes)) return new Error(`${path}.quotes: custom check failed`)
|
||||
|
||||
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 = InvoiceSwapOperationValidate(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 LatestBundleMetricReq = {
|
||||
limit?: number
|
||||
}
|
||||
|
|
@ -3308,6 +3521,35 @@ export const PayAddressResponseValidate = (o?: PayAddressResponse, opts: PayAddr
|
|||
return null
|
||||
}
|
||||
|
||||
export type PayAdminInvoiceSwapRequest = {
|
||||
no_claim?: boolean
|
||||
sat_per_v_byte: number
|
||||
swap_operation_id: string
|
||||
}
|
||||
export type PayAdminInvoiceSwapRequestOptionalField = 'no_claim'
|
||||
export const PayAdminInvoiceSwapRequestOptionalFields: PayAdminInvoiceSwapRequestOptionalField[] = ['no_claim']
|
||||
export type PayAdminInvoiceSwapRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: PayAdminInvoiceSwapRequestOptionalField[]
|
||||
no_claim_CustomCheck?: (v?: boolean) => boolean
|
||||
sat_per_v_byte_CustomCheck?: (v: number) => boolean
|
||||
swap_operation_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const PayAdminInvoiceSwapRequestValidate = (o?: PayAdminInvoiceSwapRequest, opts: PayAdminInvoiceSwapRequestOptions = {}, path: string = 'PayAdminInvoiceSwapRequest::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 ((o.no_claim || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('no_claim')) && typeof o.no_claim !== 'boolean') return new Error(`${path}.no_claim: is not a boolean`)
|
||||
if (opts.no_claim_CustomCheck && !opts.no_claim_CustomCheck(o.no_claim)) return new Error(`${path}.no_claim: custom check failed`)
|
||||
|
||||
if (typeof o.sat_per_v_byte !== 'number') return new Error(`${path}.sat_per_v_byte: is not a number`)
|
||||
if (opts.sat_per_v_byte_CustomCheck && !opts.sat_per_v_byte_CustomCheck(o.sat_per_v_byte)) return new Error(`${path}.sat_per_v_byte: 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 PayAdminTransactionSwapRequest = {
|
||||
address: string
|
||||
swap_operation_id: string
|
||||
|
|
@ -3600,6 +3842,29 @@ export const ProvidersDisruptionValidate = (o?: ProvidersDisruption, opts: Provi
|
|||
return null
|
||||
}
|
||||
|
||||
export type RefundAdminInvoiceSwapRequest = {
|
||||
sat_per_v_byte: number
|
||||
swap_operation_id: string
|
||||
}
|
||||
export const RefundAdminInvoiceSwapRequestOptionalFields: [] = []
|
||||
export type RefundAdminInvoiceSwapRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
sat_per_v_byte_CustomCheck?: (v: number) => boolean
|
||||
swap_operation_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const RefundAdminInvoiceSwapRequestValidate = (o?: RefundAdminInvoiceSwapRequest, opts: RefundAdminInvoiceSwapRequestOptions = {}, path: string = 'RefundAdminInvoiceSwapRequest::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.sat_per_v_byte !== 'number') return new Error(`${path}.sat_per_v_byte: is not a number`)
|
||||
if (opts.sat_per_v_byte_CustomCheck && !opts.sat_per_v_byte_CustomCheck(o.sat_per_v_byte)) return new Error(`${path}.sat_per_v_byte: 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 RelaysMigration = {
|
||||
relays: string[]
|
||||
}
|
||||
|
|
@ -3917,76 +4182,6 @@ 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 = {
|
||||
quotes: TransactionSwapQuote[]
|
||||
swaps: SwapOperation[]
|
||||
}
|
||||
export const SwapsListOptionalFields: [] = []
|
||||
export type SwapsListOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
quotes_ItemOptions?: TransactionSwapQuoteOptions
|
||||
quotes_CustomCheck?: (v: TransactionSwapQuote[]) => boolean
|
||||
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.quotes)) return new Error(`${path}.quotes: is not an array`)
|
||||
for (let index = 0; index < o.quotes.length; index++) {
|
||||
const quotesErr = TransactionSwapQuoteValidate(o.quotes[index], opts.quotes_ItemOptions, `${path}.quotes[${index}]`)
|
||||
if (quotesErr !== null) return quotesErr
|
||||
}
|
||||
if (opts.quotes_CustomCheck && !opts.quotes_CustomCheck(o.quotes)) return new Error(`${path}.quotes: custom check failed`)
|
||||
|
||||
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
|
||||
|
|
@ -4076,6 +4271,76 @@ export const TransactionSwapRequestValidate = (o?: TransactionSwapRequest, opts:
|
|||
return null
|
||||
}
|
||||
|
||||
export type TxSwapOperation = {
|
||||
address_paid: string
|
||||
failure_reason?: string
|
||||
operation_payment?: UserOperation
|
||||
swap_operation_id: string
|
||||
}
|
||||
export type TxSwapOperationOptionalField = 'failure_reason' | 'operation_payment'
|
||||
export const TxSwapOperationOptionalFields: TxSwapOperationOptionalField[] = ['failure_reason', 'operation_payment']
|
||||
export type TxSwapOperationOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: TxSwapOperationOptionalField[]
|
||||
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 TxSwapOperationValidate = (o?: TxSwapOperation, opts: TxSwapOperationOptions = {}, path: string = 'TxSwapOperation::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 TxSwapsList = {
|
||||
quotes: TransactionSwapQuote[]
|
||||
swaps: TxSwapOperation[]
|
||||
}
|
||||
export const TxSwapsListOptionalFields: [] = []
|
||||
export type TxSwapsListOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
quotes_ItemOptions?: TransactionSwapQuoteOptions
|
||||
quotes_CustomCheck?: (v: TransactionSwapQuote[]) => boolean
|
||||
swaps_ItemOptions?: TxSwapOperationOptions
|
||||
swaps_CustomCheck?: (v: TxSwapOperation[]) => boolean
|
||||
}
|
||||
export const TxSwapsListValidate = (o?: TxSwapsList, opts: TxSwapsListOptions = {}, path: string = 'TxSwapsList::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.quotes)) return new Error(`${path}.quotes: is not an array`)
|
||||
for (let index = 0; index < o.quotes.length; index++) {
|
||||
const quotesErr = TransactionSwapQuoteValidate(o.quotes[index], opts.quotes_ItemOptions, `${path}.quotes[${index}]`)
|
||||
if (quotesErr !== null) return quotesErr
|
||||
}
|
||||
if (opts.quotes_CustomCheck && !opts.quotes_CustomCheck(o.quotes)) return new Error(`${path}.quotes: custom check failed`)
|
||||
|
||||
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 = TxSwapOperationValidate(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 UpdateChannelPolicyRequest = {
|
||||
policy: ChannelPolicy
|
||||
update: UpdateChannelPolicyRequest_update
|
||||
|
|
|
|||
|
|
@ -175,6 +175,34 @@ service LightningPub {
|
|||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetAdminInvoiceSwapQuotes(structs.InvoiceSwapRequest) returns (structs.InvoiceSwapQuoteList) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/invoice/quote";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc ListAdminInvoiceSwaps(structs.Empty) returns (structs.InvoiceSwapsList) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/invoice/list";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc PayAdminInvoiceSwap(structs.PayAdminInvoiceSwapRequest) returns (structs.AdminInvoiceSwapResponse) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/invoice/pay";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc RefundAdminInvoiceSwap(structs.RefundAdminInvoiceSwapRequest) returns (structs.AdminInvoiceSwapResponse) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/invoice/refund";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetAdminTransactionSwapQuotes(structs.TransactionSwapRequest) returns (structs.TransactionSwapQuoteList) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
|
|
@ -182,17 +210,17 @@ service LightningPub {
|
|||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc PayAdminTransactionSwap(structs.PayAdminTransactionSwapRequest) returns (structs.AdminSwapResponse) {
|
||||
rpc PayAdminTransactionSwap(structs.PayAdminTransactionSwapRequest) returns (structs.AdminTxSwapResponse) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/transaction/pay";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc ListAdminSwaps(structs.Empty) returns (structs.SwapsList) {
|
||||
rpc ListAdminTxSwaps(structs.Empty) returns (structs.TxSwapsList) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/list";
|
||||
option (http_route) = "/api/admin/swap/transaction/list";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
|
|
@ -520,14 +548,14 @@ service LightningPub {
|
|||
rpc GetTransactionSwapQuotes(structs.TransactionSwapRequest) returns (structs.TransactionSwapQuoteList){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/swap/quote";
|
||||
option (http_route) = "/api/user/swap/transaction/quote";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc ListSwaps(structs.Empty) returns (structs.SwapsList){
|
||||
rpc ListTxSwaps(structs.Empty) returns (structs.TxSwapsList){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/swap/list";
|
||||
option (http_route) = "/api/user/swap/transaction/list";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -833,6 +833,55 @@ message MessagingToken {
|
|||
string firebase_messaging_token = 2;
|
||||
}
|
||||
|
||||
message InvoiceSwapRequest {
|
||||
int64 amount_sats = 1;
|
||||
}
|
||||
|
||||
message InvoiceSwapQuote {
|
||||
string swap_operation_id = 1;
|
||||
string invoice = 2;
|
||||
int64 invoice_amount_sats = 3;
|
||||
string address = 4;
|
||||
int64 transaction_amount_sats = 5;
|
||||
int64 chain_fee_sats = 6;
|
||||
int64 service_fee_sats = 7;
|
||||
string service_url = 8;
|
||||
int64 swap_fee_sats = 9;
|
||||
string tx_id = 10;
|
||||
}
|
||||
|
||||
message InvoiceSwapQuoteList {
|
||||
repeated InvoiceSwapQuote quotes = 1;
|
||||
}
|
||||
|
||||
message InvoiceSwapOperation {
|
||||
string swap_operation_id = 1;
|
||||
optional UserOperation operation_payment = 2;
|
||||
optional string failure_reason = 3;
|
||||
string invoice_paid = 4;
|
||||
string tx_id = 5;
|
||||
}
|
||||
|
||||
message InvoiceSwapsList {
|
||||
repeated InvoiceSwapOperation swaps = 1;
|
||||
repeated InvoiceSwapQuote quotes = 2;
|
||||
}
|
||||
|
||||
message RefundAdminInvoiceSwapRequest {
|
||||
string swap_operation_id = 1;
|
||||
int64 sat_per_v_byte = 2;
|
||||
}
|
||||
|
||||
message PayAdminInvoiceSwapRequest {
|
||||
string swap_operation_id = 1;
|
||||
int64 sat_per_v_byte = 2;
|
||||
optional bool no_claim = 3;
|
||||
}
|
||||
|
||||
message AdminInvoiceSwapResponse {
|
||||
string tx_id = 1;
|
||||
}
|
||||
|
||||
message TransactionSwapRequest {
|
||||
int64 transaction_amount_sats = 2;
|
||||
}
|
||||
|
|
@ -857,20 +906,20 @@ message TransactionSwapQuoteList {
|
|||
repeated TransactionSwapQuote quotes = 1;
|
||||
}
|
||||
|
||||
message AdminSwapResponse {
|
||||
message AdminTxSwapResponse {
|
||||
string tx_id = 1;
|
||||
int64 network_fee = 2;
|
||||
}
|
||||
|
||||
message SwapOperation {
|
||||
message TxSwapOperation {
|
||||
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 TxSwapsList {
|
||||
repeated TxSwapOperation swaps = 1;
|
||||
repeated TransactionSwapQuote quotes = 2;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { TxPointSettings } from '../storage/tlv/stateBundler.js';
|
|||
import { WalletKitClient } from '../../../proto/lnd/walletkit.client.js';
|
||||
import SettingsManager from '../main/settingsManager.js';
|
||||
import { LndNodeSettings, LndSettings } from '../main/settings.js';
|
||||
import { ListAddressesResponse } from '../../../proto/lnd/walletkit.js';
|
||||
import { ListAddressesResponse, PublishResponse } from '../../../proto/lnd/walletkit.js';
|
||||
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
const deadLndRetrySeconds = 20
|
||||
|
|
@ -157,6 +157,13 @@ export default class {
|
|||
})
|
||||
}
|
||||
|
||||
async PublishTransaction(txHex: string): Promise<PublishResponse> {
|
||||
const res = await this.walletKit.publishTransaction({
|
||||
txHex: Buffer.from(txHex, 'hex'), label: ""
|
||||
}, DeadLineMetadata())
|
||||
return res.response
|
||||
}
|
||||
|
||||
async GetInfo(): Promise<NodeInfo> {
|
||||
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||
// Return dummy info when bypass is enabled
|
||||
|
|
|
|||
|
|
@ -1,657 +0,0 @@
|
|||
import zkpInit from '@vulpemventures/secp256k1-zkp';
|
||||
import axios from 'axios';
|
||||
import { crypto, initEccLib, Transaction, address, Network } from 'bitcoinjs-lib';
|
||||
// import bolt11 from 'bolt11';
|
||||
import {
|
||||
Musig, SwapTreeSerializer, TaprootUtils, detectSwap,
|
||||
constructClaimTransaction, targetFee, OutputType,
|
||||
Networks,
|
||||
} from 'boltz-core';
|
||||
import { randomBytes, createHash } from 'crypto';
|
||||
import { ECPairFactory, ECPairInterface } from 'ecpair';
|
||||
import * as ecc from 'tiny-secp256k1';
|
||||
import ws from 'ws';
|
||||
import { getLogger, PubLogger, ERROR } from '../helpers/logger.js';
|
||||
import SettingsManager from '../main/settingsManager.js';
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js';
|
||||
import { BTCNetwork } from '../main/settings.js';
|
||||
import Storage from '../storage/index.js';
|
||||
import LND from './lnd.js';
|
||||
import { UserInvoicePayment } from '../storage/entity/UserInvoicePayment.js';
|
||||
type InvoiceSwapResponse = { id: string, claimPublicKey: string, swapTree: string }
|
||||
type InvoiceSwapInfo = { paymentHash: string, keys: ECPairInterface }
|
||||
type InvoiceSwapData = { createdResponse: InvoiceSwapResponse, info: InvoiceSwapInfo }
|
||||
|
||||
type TransactionSwapFees = {
|
||||
percentage: number,
|
||||
minerFees: {
|
||||
claim: number,
|
||||
lockup: number,
|
||||
}
|
||||
}
|
||||
|
||||
type TransactionSwapFeesRes = {
|
||||
BTC?: {
|
||||
BTC?: {
|
||||
fees: TransactionSwapFees
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type TransactionSwapResponse = {
|
||||
id: string, refundPublicKey: string, swapTree: string,
|
||||
timeoutBlockHeight: number, lockupAddress: string, invoice: string,
|
||||
onchainAmount?: number
|
||||
}
|
||||
type TransactionSwapInfo = { destinationAddress: string, preimage: Buffer, keys: ECPairInterface, chainFee: number }
|
||||
export type TransactionSwapData = { createdResponse: TransactionSwapResponse, info: TransactionSwapInfo }
|
||||
export class Swaps {
|
||||
settings: SettingsManager
|
||||
revSwappers: Record<string, ReverseSwaps>
|
||||
// submarineSwaps: SubmarineSwaps
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
log = getLogger({ component: 'swaps' })
|
||||
constructor(settings: SettingsManager, storage: Storage) {
|
||||
this.settings = settings
|
||||
this.revSwappers = {}
|
||||
const network = settings.getSettings().lndSettings.network
|
||||
const { boltzHttpUrl, boltzWebSocketUrl, boltsHttpUrlAlt, boltsWebSocketUrlAlt } = settings.getSettings().swapsSettings
|
||||
if (boltzHttpUrl && boltzWebSocketUrl) {
|
||||
this.revSwappers[boltzHttpUrl] = new ReverseSwaps({ httpUrl: boltzHttpUrl, wsUrl: boltzWebSocketUrl, network })
|
||||
}
|
||||
if (boltsHttpUrlAlt && boltsWebSocketUrlAlt) {
|
||||
this.revSwappers[boltsHttpUrlAlt] = new ReverseSwaps({ httpUrl: boltsHttpUrlAlt, wsUrl: boltsWebSocketUrlAlt, network })
|
||||
}
|
||||
this.storage = storage
|
||||
}
|
||||
|
||||
SetLnd = (lnd: LND) => {
|
||||
this.lnd = lnd
|
||||
}
|
||||
|
||||
Stop = () => { }
|
||||
|
||||
GetKeys = (privateKey: string) => {
|
||||
const keys = ECPairFactory(ecc).fromPrivateKey(Buffer.from(privateKey, 'hex'))
|
||||
return keys
|
||||
}
|
||||
|
||||
ListSwaps = async (appUserId: string, payments: UserInvoicePayment[], newOp: (p: UserInvoicePayment) => Types.UserOperation | undefined, getServiceFee: (amt: number) => number): Promise<Types.SwapsList> => {
|
||||
const completedSwaps = await this.storage.paymentStorage.ListCompletedSwaps(appUserId, payments)
|
||||
const pendingSwaps = await this.storage.paymentStorage.ListPendingTransactionSwaps(appUserId)
|
||||
return {
|
||||
swaps: completedSwaps.map(s => {
|
||||
const p = s.payment
|
||||
const op = p ? newOp(p) : 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,
|
||||
}
|
||||
}),
|
||||
quotes: pendingSwaps.map(s => {
|
||||
const serviceFee = getServiceFee(s.invoice_amount)
|
||||
return {
|
||||
swap_operation_id: s.swap_operation_id,
|
||||
invoice_amount_sats: s.invoice_amount,
|
||||
transaction_amount_sats: s.transaction_amount,
|
||||
chain_fee_sats: s.chain_fee_sats,
|
||||
service_fee_sats: serviceFee,
|
||||
swap_fee_sats: s.swap_fee_sats,
|
||||
service_url: s.service_url,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
GetTxSwapQuotes = async (appUserId: string, amt: number, getServiceFee: (decodedAmt: number) => number): Promise<Types.TransactionSwapQuote[]> => {
|
||||
if (!this.settings.getSettings().swapsSettings.enableSwaps) {
|
||||
throw new Error("Swaps are not enabled")
|
||||
}
|
||||
const swappers = Object.values(this.revSwappers)
|
||||
if (swappers.length === 0) {
|
||||
throw new Error("No swap services available")
|
||||
}
|
||||
const res = await Promise.allSettled(swappers.map(sw => this.getTxSwapQuote(sw, appUserId, amt, getServiceFee)))
|
||||
const failures: string[] = []
|
||||
const success: Types.TransactionSwapQuote[] = []
|
||||
for (const r of res) {
|
||||
if (r.status === 'fulfilled') {
|
||||
success.push(r.value)
|
||||
} else {
|
||||
failures.push(r.reason.message ? r.reason.message : r.reason.toString())
|
||||
}
|
||||
}
|
||||
if (success.length === 0) {
|
||||
throw new Error(failures.join("\n"))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
private async getTxSwapQuote(swapper: ReverseSwaps, appUserId: string, amt: number, getServiceFee: (decodedAmt: number) => number): Promise<Types.TransactionSwapQuote> {
|
||||
this.log("getting transaction swap quote")
|
||||
const feesRes = await swapper.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 swapper.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,
|
||||
service_url: swapper.getHttpUrl(),
|
||||
})
|
||||
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,
|
||||
service_url: swapper.getHttpUrl(),
|
||||
}
|
||||
}
|
||||
|
||||
async PayAddrWithSwap(appUserId: string, swapOpId: string, address: string, payInvoice: (invoice: string, amt: number) => Promise<void>) {
|
||||
if (!this.settings.getSettings().swapsSettings.enableSwaps) {
|
||||
throw new Error("Swaps are not enabled")
|
||||
}
|
||||
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 swapper = this.revSwappers[txSwap.service_url]
|
||||
if (!swapper) {
|
||||
throw new Error("swapper service not found")
|
||||
}
|
||||
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 }
|
||||
swapper.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 ReverseSwaps {
|
||||
// settings: SettingsManager
|
||||
private httpUrl: string
|
||||
private wsUrl: string
|
||||
log: PubLogger
|
||||
private network: BTCNetwork
|
||||
constructor({ httpUrl, wsUrl, network }: { httpUrl: string, wsUrl: string, network: BTCNetwork }) {
|
||||
this.httpUrl = httpUrl
|
||||
this.wsUrl = wsUrl
|
||||
this.network = network
|
||||
this.log = getLogger({ component: 'ReverseSwaps' })
|
||||
initEccLib(ecc)
|
||||
}
|
||||
|
||||
getHttpUrl = () => {
|
||||
return this.httpUrl
|
||||
}
|
||||
getWsUrl = () => {
|
||||
return this.wsUrl
|
||||
}
|
||||
|
||||
calculateFees = (fees: TransactionSwapFees, receiveAmount: number) => {
|
||||
const pct = fees.percentage / 100
|
||||
const minerFee = fees.minerFees.claim + fees.minerFees.lockup
|
||||
|
||||
const preFee = receiveAmount + minerFee
|
||||
const fee = Math.ceil(preFee * pct)
|
||||
const total = preFee + fee
|
||||
return { total, fee, minerFee }
|
||||
}
|
||||
|
||||
GetFees = async (): Promise<{ ok: true, fees: TransactionSwapFees, } | { ok: false, error: string }> => {
|
||||
const url = `${this.httpUrl}/v2/swap/reverse`
|
||||
const feesRes = await loggedGet<TransactionSwapFeesRes>(this.log, url)
|
||||
if (!feesRes.ok) {
|
||||
return { ok: false, error: feesRes.error }
|
||||
}
|
||||
if (!feesRes.data.BTC?.BTC?.fees) {
|
||||
return { ok: false, error: 'No fees found for BTC to BTC swap' }
|
||||
}
|
||||
|
||||
return { ok: true, fees: feesRes.data.BTC.BTC.fees }
|
||||
}
|
||||
|
||||
SwapTransaction = async (txAmount: number): Promise<{ ok: true, createdResponse: TransactionSwapResponse, preimage: string, pubkey: string, privKey: string } | { ok: false, error: string }> => {
|
||||
const preimage = randomBytes(32);
|
||||
const keys = ECPairFactory(ecc).makeRandom()
|
||||
if (!keys.privateKey) {
|
||||
return { ok: false, error: 'Failed to generate keys' }
|
||||
}
|
||||
const url = `${this.httpUrl}/v2/swap/reverse`
|
||||
const req: any = {
|
||||
onchainAmount: txAmount,
|
||||
to: 'BTC',
|
||||
from: 'BTC',
|
||||
claimPublicKey: Buffer.from(keys.publicKey).toString('hex'),
|
||||
preimageHash: createHash('sha256').update(preimage).digest('hex'),
|
||||
}
|
||||
const createdResponseRes = await loggedPost<TransactionSwapResponse>(this.log, url, req)
|
||||
if (!createdResponseRes.ok) {
|
||||
return createdResponseRes
|
||||
}
|
||||
const createdResponse = createdResponseRes.data
|
||||
this.log('Created transaction swap');
|
||||
this.log(createdResponse);
|
||||
return {
|
||||
ok: true, createdResponse,
|
||||
preimage: Buffer.from(preimage).toString('hex'),
|
||||
pubkey: Buffer.from(keys.publicKey).toString('hex'),
|
||||
privKey: Buffer.from(keys.privateKey).toString('hex')
|
||||
}
|
||||
}
|
||||
|
||||
SubscribeToTransactionSwap = async (data: TransactionSwapData, swapDone: (result: { ok: true, txId: string } | { ok: false, error: string }) => void) => {
|
||||
const webSocket = new ws(`${this.wsUrl}/v2/ws`)
|
||||
const subReq = { op: 'subscribe', channel: 'swap.update', args: [data.createdResponse.id] }
|
||||
webSocket.on('open', () => {
|
||||
webSocket.send(JSON.stringify(subReq))
|
||||
})
|
||||
let txId = "", isDone = false
|
||||
const done = () => {
|
||||
isDone = true
|
||||
webSocket.close()
|
||||
swapDone({ ok: true, txId })
|
||||
}
|
||||
webSocket.on('error', (err) => {
|
||||
this.log(ERROR, 'Error in WebSocket', err.message)
|
||||
})
|
||||
webSocket.on('close', () => {
|
||||
if (!isDone) {
|
||||
this.log(ERROR, 'WebSocket closed before swap was done');
|
||||
swapDone({ ok: false, error: 'WebSocket closed before swap was done' })
|
||||
}
|
||||
})
|
||||
webSocket.on('message', async (rawMsg) => {
|
||||
try {
|
||||
const result = await this.handleSwapTransactionMessage(rawMsg, data, done)
|
||||
if (result) {
|
||||
txId = result
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.log(ERROR, 'Error handling transaction WebSocket message', err.message)
|
||||
isDone = true
|
||||
webSocket.close()
|
||||
swapDone({ ok: false, error: err.message })
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleSwapTransactionMessage = async (rawMsg: ws.RawData, data: TransactionSwapData, done: () => void) => {
|
||||
const msg = JSON.parse(rawMsg.toString('utf-8'));
|
||||
if (msg.event !== 'update') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log('Got WebSocket update');
|
||||
this.log(msg);
|
||||
switch (msg.args[0].status) {
|
||||
// "swap.created" means Boltz is waiting for the invoice to be paid
|
||||
case 'swap.created':
|
||||
this.log('Waiting invoice to be paid');
|
||||
return;
|
||||
|
||||
// "transaction.mempool" means that Boltz sent an onchain transaction
|
||||
case 'transaction.mempool':
|
||||
const txIdRes = await this.handleTransactionMempool(data, msg.args[0].transaction.hex)
|
||||
if (!txIdRes.ok) {
|
||||
throw new Error(txIdRes.error)
|
||||
}
|
||||
return txIdRes.txId
|
||||
case 'invoice.settled':
|
||||
this.log('Transaction swap successful');
|
||||
done()
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handleTransactionMempool = async (data: TransactionSwapData, txHex: string): Promise<{ ok: true, txId: string } | { ok: false, error: string }> => {
|
||||
this.log('Creating claim transaction');
|
||||
const { createdResponse, info } = data
|
||||
const { destinationAddress, keys, preimage, chainFee } = info
|
||||
const boltzPublicKey = Buffer.from(
|
||||
createdResponse.refundPublicKey,
|
||||
'hex',
|
||||
);
|
||||
|
||||
// Create a musig signing session and tweak it with the Taptree of the swap scripts
|
||||
const musig = new Musig(await zkpInit(), keys, randomBytes(32), [
|
||||
boltzPublicKey,
|
||||
Buffer.from(keys.publicKey),
|
||||
]);
|
||||
const tweakedKey = TaprootUtils.tweakMusig(
|
||||
musig,
|
||||
// swap tree can either be a string or an object
|
||||
SwapTreeSerializer.deserializeSwapTree(createdResponse.swapTree).tree,
|
||||
);
|
||||
|
||||
// Parse the lockup transaction and find the output relevant for the swap
|
||||
const lockupTx = Transaction.fromHex(txHex);
|
||||
const swapOutput = detectSwap(tweakedKey, lockupTx);
|
||||
if (swapOutput === undefined) {
|
||||
this.log(ERROR, 'No swap output found in lockup transaction');
|
||||
return { ok: false, error: 'No swap output found in lockup transaction' }
|
||||
}
|
||||
const network = getNetwork(this.network)
|
||||
// Create a claim transaction to be signed cooperatively via a key path spend
|
||||
const claimTx = constructClaimTransaction(
|
||||
[
|
||||
{
|
||||
...swapOutput,
|
||||
keys,
|
||||
preimage,
|
||||
cooperative: true,
|
||||
type: OutputType.Taproot,
|
||||
txHash: lockupTx.getHash(),
|
||||
},
|
||||
],
|
||||
address.toOutputScript(destinationAddress, network),
|
||||
chainFee,
|
||||
)
|
||||
// Get the partial signature from Boltz
|
||||
const claimUrl = `${this.httpUrl}/v2/swap/reverse/${createdResponse.id}/claim`
|
||||
const claimReq = {
|
||||
index: 0,
|
||||
transaction: claimTx.toHex(),
|
||||
preimage: preimage.toString('hex'),
|
||||
pubNonce: Buffer.from(musig.getPublicNonce()).toString('hex'),
|
||||
}
|
||||
const boltzSigRes = await loggedPost<{ pubNonce: string, partialSignature: string }>(this.log, claimUrl, claimReq)
|
||||
if (!boltzSigRes.ok) {
|
||||
return boltzSigRes
|
||||
}
|
||||
const boltzSig = boltzSigRes.data
|
||||
|
||||
// Aggregate the nonces
|
||||
musig.aggregateNonces([
|
||||
[boltzPublicKey, Buffer.from(boltzSig.pubNonce, 'hex')],
|
||||
]);
|
||||
|
||||
// Initialize the session to sign the claim transaction
|
||||
musig.initializeSession(
|
||||
claimTx.hashForWitnessV1(
|
||||
0,
|
||||
[swapOutput.script],
|
||||
[swapOutput.value],
|
||||
Transaction.SIGHASH_DEFAULT,
|
||||
),
|
||||
);
|
||||
|
||||
// Add the partial signature from Boltz
|
||||
musig.addPartial(
|
||||
boltzPublicKey,
|
||||
Buffer.from(boltzSig.partialSignature, 'hex'),
|
||||
);
|
||||
|
||||
// Create our partial signature
|
||||
musig.signPartial();
|
||||
|
||||
// Witness of the input to the aggregated signature
|
||||
claimTx.ins[0].witness = [musig.aggregatePartials()];
|
||||
|
||||
// Broadcast the finalized transaction
|
||||
const broadcastUrl = `${this.httpUrl}/v2/chain/BTC/transaction`
|
||||
const broadcastReq = {
|
||||
hex: claimTx.toHex(),
|
||||
}
|
||||
|
||||
const broadcastResponse = await loggedPost<any>(this.log, broadcastUrl, broadcastReq)
|
||||
if (!broadcastResponse.ok) {
|
||||
return broadcastResponse
|
||||
}
|
||||
this.log('Transaction broadcasted', broadcastResponse.data)
|
||||
const txId = claimTx.getId()
|
||||
return { ok: true, txId }
|
||||
}
|
||||
}
|
||||
|
||||
const loggedPost = async <T>(log: PubLogger, url: string, req: any): Promise<{ ok: true, data: T } | { ok: false, error: string }> => {
|
||||
try {
|
||||
const { data } = await axios.post(url, req)
|
||||
return { ok: true, data: data as T }
|
||||
} catch (err: any) {
|
||||
if (err.response?.data) {
|
||||
log(ERROR, 'Error sending request', err.response.data)
|
||||
return { ok: false, error: JSON.stringify(err.response.data) }
|
||||
}
|
||||
log(ERROR, 'Error sending request', err.message)
|
||||
return { ok: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
const loggedGet = async <T>(log: PubLogger, url: string): Promise<{ ok: true, data: T } | { ok: false, error: string }> => {
|
||||
try {
|
||||
const { data } = await axios.get(url)
|
||||
return { ok: true, data: data as T }
|
||||
} catch (err: any) {
|
||||
if (err.response?.data) {
|
||||
log(ERROR, 'Error getting request', err.response.data)
|
||||
return { ok: false, error: err.response.data }
|
||||
}
|
||||
log(ERROR, 'Error getting request', err.message)
|
||||
return { ok: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
const getNetwork = (network: BTCNetwork): Network => {
|
||||
switch (network) {
|
||||
case 'mainnet':
|
||||
return Networks.bitcoinMainnet
|
||||
case 'testnet':
|
||||
return Networks.bitcoinTestnet
|
||||
case 'regtest':
|
||||
return Networks.bitcoinRegtest
|
||||
default:
|
||||
throw new Error(`Invalid network: ${network}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Submarine swaps currently not supported, keeping the code for future reference
|
||||
/*
|
||||
export class SubmarineSwaps {
|
||||
settings: SettingsManager
|
||||
log: PubLogger
|
||||
constructor(settings: SettingsManager) {
|
||||
this.settings = settings
|
||||
this.log = getLogger({ component: 'SubmarineSwaps' })
|
||||
}
|
||||
|
||||
SwapInvoice = async (invoice: string, paymentHash: string) => {
|
||||
if (!this.settings.getSettings().swapsSettings.enableSwaps) {
|
||||
this.log(ERROR, 'Swaps are not enabled');
|
||||
return;
|
||||
}
|
||||
const keys = ECPairFactory(ecc).makeRandom()
|
||||
const refundPublicKey = Buffer.from(keys.publicKey).toString('hex')
|
||||
const req = { invoice, to: 'BTC', from: 'BTC', refundPublicKey }
|
||||
const url = `${this.settings.getSettings().swapsSettings.boltzHttpUrl}/v2/swap/submarine`
|
||||
this.log('Sending invoice swap request to', url);
|
||||
const createdResponseRes = await loggedPost<InvoiceSwapResponse>(this.log, url, req)
|
||||
if (!createdResponseRes.ok) {
|
||||
return createdResponseRes
|
||||
}
|
||||
const createdResponse = createdResponseRes.data
|
||||
this.log('Created invoice swap');
|
||||
this.log(createdResponse);
|
||||
|
||||
const webSocket = new ws(`${this.settings.getSettings().swapsSettings.boltzWebSocketUrl}/v2/ws`)
|
||||
const subReq = { op: 'subscribe', channel: 'swap.update', args: [createdResponse.id] }
|
||||
webSocket.on('open', () => {
|
||||
webSocket.send(JSON.stringify(subReq))
|
||||
})
|
||||
|
||||
webSocket.on('message', async (rawMsg) => {
|
||||
try {
|
||||
await this.handleSwapInvoiceMessage(rawMsg, { createdResponse, info: { paymentHash, keys } }, () => webSocket.close())
|
||||
} catch (err: any) {
|
||||
this.log(ERROR, 'Error handling invoice WebSocket message', err.message)
|
||||
webSocket.close()
|
||||
return
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleSwapInvoiceMessage = async (rawMsg: ws.RawData, data: InvoiceSwapData, closeWebSocket: () => void) => {
|
||||
const msg = JSON.parse(rawMsg.toString('utf-8'));
|
||||
if (msg.event !== 'update') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log('Got invoice WebSocket update');
|
||||
this.log(msg);
|
||||
switch (msg.args[0].status) {
|
||||
// "invoice.set" means Boltz is waiting for an onchain transaction to be sent
|
||||
case 'invoice.set':
|
||||
this.log('Waiting for onchain transaction');
|
||||
return;
|
||||
// Create a partial signature to allow Boltz to do a key path spend to claim the mainchain coins
|
||||
case 'transaction.claim.pending':
|
||||
await this.handleInvoiceClaimPending(data)
|
||||
return;
|
||||
|
||||
case 'transaction.claimed':
|
||||
this.log('Invoice swap successful');
|
||||
closeWebSocket()
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handleInvoiceClaimPending = async (data: InvoiceSwapData) => {
|
||||
this.log('Creating cooperative claim transaction');
|
||||
const { createdResponse, info } = data
|
||||
const { paymentHash, keys } = info
|
||||
const { boltzHttpUrl } = this.settings.getSettings().swapsSettings
|
||||
// Get the information request to create a partial signature
|
||||
const url = `${boltzHttpUrl}/v2/swap/submarine/${createdResponse.id}/claim`
|
||||
const claimTxDetailsRes = await loggedGet<{ preimage: string, transactionHash: string, pubNonce: string }>(this.log, url)
|
||||
if (!claimTxDetailsRes.ok) {
|
||||
return claimTxDetailsRes
|
||||
}
|
||||
const claimTxDetails = claimTxDetailsRes.data
|
||||
// Verify that Boltz actually paid the invoice by comparing the preimage hash
|
||||
// of the invoice to the SHA256 hash of the preimage from the response
|
||||
const claimTxPreimageHash = createHash('sha256').update(Buffer.from(claimTxDetails.preimage, 'hex')).digest()
|
||||
const invoicePreimageHash = Buffer.from(paymentHash, 'hex')
|
||||
|
||||
if (!claimTxPreimageHash.equals(invoicePreimageHash)) {
|
||||
this.log(ERROR, 'Boltz provided invalid preimage');
|
||||
return;
|
||||
}
|
||||
|
||||
const boltzPublicKey = Buffer.from(createdResponse.claimPublicKey, 'hex')
|
||||
|
||||
// Create a musig signing instance
|
||||
const musig = new Musig(await zkpInit(), keys, randomBytes(32), [
|
||||
boltzPublicKey,
|
||||
Buffer.from(keys.publicKey),
|
||||
]);
|
||||
// Tweak that musig with the Taptree of the swap scripts
|
||||
TaprootUtils.tweakMusig(
|
||||
musig,
|
||||
SwapTreeSerializer.deserializeSwapTree(createdResponse.swapTree).tree,
|
||||
);
|
||||
|
||||
// Aggregate the nonces
|
||||
musig.aggregateNonces([
|
||||
[boltzPublicKey, Buffer.from(claimTxDetails.pubNonce, 'hex')],
|
||||
]);
|
||||
// Initialize the session to sign the transaction hash from the response
|
||||
musig.initializeSession(
|
||||
Buffer.from(claimTxDetails.transactionHash, 'hex'),
|
||||
);
|
||||
|
||||
// Give our public nonce and the partial signature to Boltz
|
||||
const claimUrl = `${boltzHttpUrl}/v2/swap/submarine/${createdResponse.id}/claim`
|
||||
const claimReq = {
|
||||
pubNonce: Buffer.from(musig.getPublicNonce()).toString('hex'),
|
||||
partialSignature: Buffer.from(musig.signPartial()).toString('hex'),
|
||||
}
|
||||
const claimResponseRes = await loggedPost<{ pubNonce: string, partialSignature: string }>(this.log, claimUrl, claimReq)
|
||||
if (!claimResponseRes.ok) {
|
||||
return claimResponseRes
|
||||
}
|
||||
const claimResponse = claimResponseRes.data
|
||||
this.log('Claim response', claimResponse)
|
||||
}
|
||||
}
|
||||
*/
|
||||
294
src/services/lnd/swaps/reverseSwaps.ts
Normal file
294
src/services/lnd/swaps/reverseSwaps.ts
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import secp256k1ZkpModule from '@vulpemventures/secp256k1-zkp';
|
||||
const zkpInit = (secp256k1ZkpModule as any).default || secp256k1ZkpModule;
|
||||
import { initEccLib, Transaction, address } from 'bitcoinjs-lib';
|
||||
// import bolt11 from 'bolt11';
|
||||
import {
|
||||
Musig, SwapTreeSerializer, TaprootUtils, detectSwap,
|
||||
constructClaimTransaction, OutputType, constructRefundTransaction
|
||||
} from 'boltz-core';
|
||||
import { randomBytes, createHash } from 'crypto';
|
||||
import { ECPairFactory, ECPairInterface } from 'ecpair';
|
||||
import * as ecc from 'tiny-secp256k1';
|
||||
import ws from 'ws';
|
||||
import { getLogger, PubLogger, ERROR } from '../../helpers/logger.js';
|
||||
import { BTCNetwork } from '../../main/settings.js';
|
||||
import { loggedGet, loggedPost, getNetwork } from './swapHelpers.js';
|
||||
|
||||
|
||||
type TransactionSwapFees = {
|
||||
percentage: number,
|
||||
minerFees: {
|
||||
claim: number,
|
||||
lockup: number,
|
||||
}
|
||||
}
|
||||
|
||||
type TransactionSwapFeesRes = {
|
||||
BTC?: {
|
||||
BTC?: {
|
||||
fees: TransactionSwapFees
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type TransactionSwapResponse = {
|
||||
id: string, refundPublicKey: string, swapTree: string,
|
||||
timeoutBlockHeight: number, lockupAddress: string, invoice: string,
|
||||
onchainAmount?: number
|
||||
}
|
||||
type TransactionSwapInfo = { destinationAddress: string, preimage: Buffer, keys: ECPairInterface, chainFee: number }
|
||||
export type TransactionSwapData = { createdResponse: TransactionSwapResponse, info: TransactionSwapInfo }
|
||||
|
||||
|
||||
|
||||
export class ReverseSwaps {
|
||||
// settings: SettingsManager
|
||||
private httpUrl: string
|
||||
private wsUrl: string
|
||||
log: PubLogger
|
||||
private network: BTCNetwork
|
||||
constructor({ httpUrl, wsUrl, network }: { httpUrl: string, wsUrl: string, network: BTCNetwork }) {
|
||||
this.httpUrl = httpUrl
|
||||
this.wsUrl = wsUrl
|
||||
this.network = network
|
||||
this.log = getLogger({ component: 'ReverseSwaps' })
|
||||
initEccLib(ecc)
|
||||
}
|
||||
|
||||
getHttpUrl = () => {
|
||||
return this.httpUrl
|
||||
}
|
||||
getWsUrl = () => {
|
||||
return this.wsUrl
|
||||
}
|
||||
|
||||
calculateFees = (fees: TransactionSwapFees, receiveAmount: number) => {
|
||||
const pct = fees.percentage / 100
|
||||
const minerFee = fees.minerFees.claim + fees.minerFees.lockup
|
||||
|
||||
const preFee = receiveAmount + minerFee
|
||||
const fee = Math.ceil(preFee * pct)
|
||||
const total = preFee + fee
|
||||
return { total, fee, minerFee }
|
||||
}
|
||||
|
||||
GetFees = async (): Promise<{ ok: true, fees: TransactionSwapFees, } | { ok: false, error: string }> => {
|
||||
const url = `${this.httpUrl}/v2/swap/reverse`
|
||||
const feesRes = await loggedGet<TransactionSwapFeesRes>(this.log, url)
|
||||
if (!feesRes.ok) {
|
||||
return { ok: false, error: feesRes.error }
|
||||
}
|
||||
if (!feesRes.data.BTC?.BTC?.fees) {
|
||||
return { ok: false, error: 'No fees found for BTC to BTC swap' }
|
||||
}
|
||||
|
||||
return { ok: true, fees: feesRes.data.BTC.BTC.fees }
|
||||
}
|
||||
|
||||
SwapTransaction = async (txAmount: number): Promise<{ ok: true, createdResponse: TransactionSwapResponse, preimage: string, pubkey: string, privKey: string } | { ok: false, error: string }> => {
|
||||
const preimage = randomBytes(32);
|
||||
const keys = ECPairFactory(ecc).makeRandom()
|
||||
if (!keys.privateKey) {
|
||||
return { ok: false, error: 'Failed to generate keys' }
|
||||
}
|
||||
const url = `${this.httpUrl}/v2/swap/reverse`
|
||||
const req: any = {
|
||||
onchainAmount: txAmount,
|
||||
to: 'BTC',
|
||||
from: 'BTC',
|
||||
claimPublicKey: Buffer.from(keys.publicKey).toString('hex'),
|
||||
preimageHash: createHash('sha256').update(preimage).digest('hex'),
|
||||
}
|
||||
const createdResponseRes = await loggedPost<TransactionSwapResponse>(this.log, url, req)
|
||||
if (!createdResponseRes.ok) {
|
||||
return createdResponseRes
|
||||
}
|
||||
const createdResponse = createdResponseRes.data
|
||||
this.log('Created transaction swap');
|
||||
this.log(createdResponse);
|
||||
return {
|
||||
ok: true, createdResponse,
|
||||
preimage: Buffer.from(preimage).toString('hex'),
|
||||
pubkey: Buffer.from(keys.publicKey).toString('hex'),
|
||||
privKey: Buffer.from(keys.privateKey).toString('hex')
|
||||
}
|
||||
}
|
||||
|
||||
SubscribeToTransactionSwap = async (data: TransactionSwapData, swapDone: (result: { ok: true, txId: string } | { ok: false, error: string }) => void) => {
|
||||
const webSocket = new ws(`${this.wsUrl}/v2/ws`)
|
||||
const subReq = { op: 'subscribe', channel: 'swap.update', args: [data.createdResponse.id] }
|
||||
webSocket.on('open', () => {
|
||||
webSocket.send(JSON.stringify(subReq))
|
||||
})
|
||||
let txId = "", isDone = false
|
||||
const done = (failureReason?: string) => {
|
||||
isDone = true
|
||||
webSocket.close()
|
||||
if (failureReason) {
|
||||
swapDone({ ok: false, error: failureReason })
|
||||
} else {
|
||||
swapDone({ ok: true, txId })
|
||||
}
|
||||
}
|
||||
webSocket.on('error', (err) => {
|
||||
this.log(ERROR, 'Error in WebSocket', err.message)
|
||||
})
|
||||
webSocket.on('close', () => {
|
||||
if (!isDone) {
|
||||
this.log(ERROR, 'WebSocket closed before swap was done');
|
||||
done('WebSocket closed before swap was done')
|
||||
}
|
||||
})
|
||||
webSocket.on('message', async (rawMsg) => {
|
||||
try {
|
||||
const result = await this.handleSwapTransactionMessage(rawMsg, data, done)
|
||||
if (result) {
|
||||
txId = result
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.log(ERROR, 'Error handling transaction WebSocket message', err.message)
|
||||
isDone = true
|
||||
webSocket.close()
|
||||
swapDone({ ok: false, error: err.message })
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleSwapTransactionMessage = async (rawMsg: ws.RawData, data: TransactionSwapData, done: (failureReason?: string) => void) => {
|
||||
const msg = JSON.parse(rawMsg.toString('utf-8'));
|
||||
if (msg.event !== 'update') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log('Got WebSocket update');
|
||||
this.log(msg);
|
||||
switch (msg.args[0].status) {
|
||||
// "swap.created" means Boltz is waiting for the invoice to be paid
|
||||
case 'swap.created':
|
||||
this.log('Waiting invoice to be paid');
|
||||
return;
|
||||
|
||||
// "transaction.mempool" means that Boltz sent an onchain transaction
|
||||
case 'transaction.mempool':
|
||||
const txIdRes = await this.handleTransactionMempool(data, msg.args[0].transaction.hex)
|
||||
if (!txIdRes.ok) {
|
||||
throw new Error(txIdRes.error)
|
||||
}
|
||||
return txIdRes.txId
|
||||
case 'invoice.settled':
|
||||
this.log('Transaction swap successful');
|
||||
done()
|
||||
return;
|
||||
case 'invoice.expired':
|
||||
case 'swap.expired':
|
||||
case 'transaction.failed':
|
||||
done(`swap ${data.createdResponse.id} failed with status ${msg.args[0].status}`)
|
||||
return;
|
||||
default:
|
||||
this.log('Unknown swap transaction WebSocket message', msg)
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
handleTransactionMempool = async (data: TransactionSwapData, txHex: string): Promise<{ ok: true, txId: string } | { ok: false, error: string }> => {
|
||||
this.log('Creating claim transaction');
|
||||
const { createdResponse, info } = data
|
||||
const { destinationAddress, keys, preimage, chainFee } = info
|
||||
const boltzPublicKey = Buffer.from(
|
||||
createdResponse.refundPublicKey,
|
||||
'hex',
|
||||
);
|
||||
|
||||
// Create a musig signing session and tweak it with the Taptree of the swap scripts
|
||||
const musig = new Musig(await zkpInit(), keys, randomBytes(32), [
|
||||
boltzPublicKey,
|
||||
Buffer.from(keys.publicKey),
|
||||
]);
|
||||
const tweakedKey = TaprootUtils.tweakMusig(
|
||||
musig,
|
||||
// swap tree can either be a string or an object
|
||||
SwapTreeSerializer.deserializeSwapTree(createdResponse.swapTree).tree,
|
||||
);
|
||||
|
||||
// Parse the lockup transaction and find the output relevant for the swap
|
||||
const lockupTx = Transaction.fromHex(txHex);
|
||||
const swapOutput = detectSwap(tweakedKey, lockupTx);
|
||||
if (swapOutput === undefined) {
|
||||
this.log(ERROR, 'No swap output found in lockup transaction');
|
||||
return { ok: false, error: 'No swap output found in lockup transaction' }
|
||||
}
|
||||
const network = getNetwork(this.network)
|
||||
// Create a claim transaction to be signed cooperatively via a key path spend
|
||||
const claimTx = constructClaimTransaction(
|
||||
[
|
||||
{
|
||||
...swapOutput,
|
||||
keys,
|
||||
preimage,
|
||||
cooperative: true,
|
||||
type: OutputType.Taproot,
|
||||
txHash: lockupTx.getHash(),
|
||||
},
|
||||
],
|
||||
address.toOutputScript(destinationAddress, network),
|
||||
chainFee,
|
||||
)
|
||||
// Get the partial signature from Boltz
|
||||
const claimUrl = `${this.httpUrl}/v2/swap/reverse/${createdResponse.id}/claim`
|
||||
const claimReq = {
|
||||
index: 0,
|
||||
transaction: claimTx.toHex(),
|
||||
preimage: preimage.toString('hex'),
|
||||
pubNonce: Buffer.from(musig.getPublicNonce()).toString('hex'),
|
||||
}
|
||||
const boltzSigRes = await loggedPost<{ pubNonce: string, partialSignature: string }>(this.log, claimUrl, claimReq)
|
||||
if (!boltzSigRes.ok) {
|
||||
return boltzSigRes
|
||||
}
|
||||
const boltzSig = boltzSigRes.data
|
||||
|
||||
// Aggregate the nonces
|
||||
musig.aggregateNonces([
|
||||
[boltzPublicKey, Buffer.from(boltzSig.pubNonce, 'hex')],
|
||||
]);
|
||||
|
||||
// Initialize the session to sign the claim transaction
|
||||
musig.initializeSession(
|
||||
claimTx.hashForWitnessV1(
|
||||
0,
|
||||
[swapOutput.script],
|
||||
[swapOutput.value],
|
||||
Transaction.SIGHASH_DEFAULT,
|
||||
),
|
||||
);
|
||||
|
||||
// Add the partial signature from Boltz
|
||||
musig.addPartial(
|
||||
boltzPublicKey,
|
||||
Buffer.from(boltzSig.partialSignature, 'hex'),
|
||||
);
|
||||
|
||||
// Create our partial signature
|
||||
musig.signPartial();
|
||||
|
||||
// Witness of the input to the aggregated signature
|
||||
claimTx.ins[0].witness = [musig.aggregatePartials()];
|
||||
|
||||
// Broadcast the finalized transaction
|
||||
const broadcastUrl = `${this.httpUrl}/v2/chain/BTC/transaction`
|
||||
const broadcastReq = {
|
||||
hex: claimTx.toHex(),
|
||||
}
|
||||
|
||||
const broadcastResponse = await loggedPost<any>(this.log, broadcastUrl, broadcastReq)
|
||||
if (!broadcastResponse.ok) {
|
||||
return broadcastResponse
|
||||
}
|
||||
this.log('Transaction broadcasted', broadcastResponse.data)
|
||||
const txId = claimTx.getId()
|
||||
return { ok: true, txId }
|
||||
}
|
||||
}
|
||||
503
src/services/lnd/swaps/submarineSwaps.ts
Normal file
503
src/services/lnd/swaps/submarineSwaps.ts
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
import secp256k1ZkpModule from '@vulpemventures/secp256k1-zkp';
|
||||
const zkpInit = (secp256k1ZkpModule as any).default || secp256k1ZkpModule;
|
||||
// import bolt11 from 'bolt11';
|
||||
import {
|
||||
Musig, SwapTreeSerializer, TaprootUtils, constructRefundTransaction,
|
||||
detectSwap, OutputType
|
||||
} from 'boltz-core';
|
||||
import { randomBytes, createHash } from 'crypto';
|
||||
import { ECPairFactory, ECPairInterface } from 'ecpair';
|
||||
import * as ecc from 'tiny-secp256k1';
|
||||
import { Transaction, address } from 'bitcoinjs-lib';
|
||||
import ws from 'ws';
|
||||
import { getLogger, PubLogger, ERROR } from '../../helpers/logger.js';
|
||||
import { loggedGet, loggedPost, getNetwork } from './swapHelpers.js';
|
||||
import { BTCNetwork } from '../../main/settings.js';
|
||||
|
||||
/* type InvoiceSwapFees = {
|
||||
hash: string,
|
||||
rate: number,
|
||||
limits: {
|
||||
maximal: number,
|
||||
minimal: number,
|
||||
maximalZeroConf: number
|
||||
},
|
||||
fees: {
|
||||
percentage: number,
|
||||
minerFees: number,
|
||||
}
|
||||
} */
|
||||
|
||||
type InvoiceSwapFees = {
|
||||
percentage: number,
|
||||
minerFees: number,
|
||||
}
|
||||
|
||||
type InvoiceSwapFeesRes = {
|
||||
BTC?: {
|
||||
BTC?: {
|
||||
fees: InvoiceSwapFees
|
||||
}
|
||||
}
|
||||
}
|
||||
type InvoiceSwapResponse = {
|
||||
id: string, claimPublicKey: string, swapTree: string, timeoutBlockHeight: number,
|
||||
expectedAmount: number, address: string
|
||||
}
|
||||
type InvoiceSwapInfo = { paymentHash: string, keys: ECPairInterface }
|
||||
export type InvoiceSwapData = { createdResponse: InvoiceSwapResponse, info: InvoiceSwapInfo }
|
||||
|
||||
export class SubmarineSwaps {
|
||||
private httpUrl: string
|
||||
private wsUrl: string
|
||||
private network: BTCNetwork
|
||||
log: PubLogger
|
||||
constructor({ httpUrl, wsUrl, network }: { httpUrl: string, wsUrl: string, network: BTCNetwork }) {
|
||||
this.httpUrl = httpUrl
|
||||
this.wsUrl = wsUrl
|
||||
this.network = network
|
||||
this.log = getLogger({ component: 'SubmarineSwaps' })
|
||||
}
|
||||
|
||||
getHttpUrl = () => {
|
||||
return this.httpUrl
|
||||
}
|
||||
getWsUrl = () => {
|
||||
return this.wsUrl
|
||||
}
|
||||
|
||||
GetFees = async (): Promise<{ ok: true, fees: InvoiceSwapFees, } | { ok: false, error: string }> => {
|
||||
const url = `${this.httpUrl}/v2/swap/submarine`
|
||||
const feesRes = await loggedGet<InvoiceSwapFeesRes>(this.log, url)
|
||||
if (!feesRes.ok) {
|
||||
return { ok: false, error: feesRes.error }
|
||||
}
|
||||
if (!feesRes.data.BTC?.BTC?.fees) {
|
||||
return { ok: false, error: 'No fees found for BTC to BTC swap' }
|
||||
}
|
||||
return { ok: true, fees: feesRes.data.BTC.BTC.fees }
|
||||
}
|
||||
|
||||
SwapInvoice = async (invoice: string): Promise<{ ok: true, createdResponse: InvoiceSwapResponse, pubkey: string, privKey: string } | { ok: false, error: string }> => {
|
||||
const keys = ECPairFactory(ecc).makeRandom()
|
||||
if (!keys.privateKey) {
|
||||
return { ok: false, error: 'Failed to generate keys' }
|
||||
}
|
||||
const refundPublicKey = Buffer.from(keys.publicKey).toString('hex')
|
||||
const req = { invoice, to: 'BTC', from: 'BTC', refundPublicKey }
|
||||
const url = `${this.httpUrl}/v2/swap/submarine`
|
||||
this.log('Sending invoice swap request to', url);
|
||||
const createdResponseRes = await loggedPost<InvoiceSwapResponse>(this.log, url, req)
|
||||
if (!createdResponseRes.ok) {
|
||||
return createdResponseRes
|
||||
}
|
||||
const createdResponse = createdResponseRes.data
|
||||
this.log('Created invoice swap');
|
||||
this.log(createdResponse);
|
||||
return {
|
||||
ok: true, createdResponse,
|
||||
pubkey: refundPublicKey,
|
||||
privKey: Buffer.from(keys.privateKey).toString('hex')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lockup transaction for a swap from Boltz
|
||||
*/
|
||||
private getLockupTransaction = async (swapId: string): Promise<{ ok: true, data: { hex: string } } | { ok: false, error: string }> => {
|
||||
const url = `${this.httpUrl}/v2/swap/submarine/${swapId}/transaction`
|
||||
return await loggedGet<{ hex: string }>(this.log, url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get partial refund signature from Boltz for cooperative refund
|
||||
*/
|
||||
private getPartialRefundSignature = async (
|
||||
swapId: string,
|
||||
pubNonce: Buffer,
|
||||
transaction: Transaction,
|
||||
index: number
|
||||
): Promise<{ ok: true, data: { pubNonce: string, partialSignature: string } } | { ok: false, error: string }> => {
|
||||
const url = `${this.httpUrl}/v2/swap/submarine/${swapId}/refund`
|
||||
const req = {
|
||||
index,
|
||||
pubNonce: pubNonce.toString('hex'),
|
||||
transaction: transaction.toHex()
|
||||
}
|
||||
return await loggedPost<{ pubNonce: string, partialSignature: string }>(this.log, url, req)
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Taproot refund transaction (cooperative or uncooperative)
|
||||
*/
|
||||
private constructTaprootRefund = async (
|
||||
swapId: string,
|
||||
claimPublicKey: string,
|
||||
swapTree: string,
|
||||
timeoutBlockHeight: number,
|
||||
lockupTx: Transaction,
|
||||
privateKey: ECPairInterface,
|
||||
refundAddress: string,
|
||||
feePerVbyte: number,
|
||||
cooperative: boolean = true
|
||||
): Promise<{
|
||||
ok: true,
|
||||
transaction: Transaction,
|
||||
cooperativeError?: string
|
||||
} | {
|
||||
ok: false,
|
||||
error: string
|
||||
}> => {
|
||||
this.log(`Constructing ${cooperative ? 'cooperative' : 'uncooperative'} Taproot refund for swap ${swapId}`)
|
||||
|
||||
const boltzPublicKey = Buffer.from(claimPublicKey, 'hex')
|
||||
const swapTreeDeserialized = SwapTreeSerializer.deserializeSwapTree(swapTree)
|
||||
|
||||
// Create musig and tweak it
|
||||
let musig = new Musig(await zkpInit(), privateKey, randomBytes(32), [
|
||||
boltzPublicKey,
|
||||
Buffer.from(privateKey.publicKey),
|
||||
])
|
||||
const tweakedKey = TaprootUtils.tweakMusig(musig, swapTreeDeserialized.tree)
|
||||
|
||||
// Detect the swap output in the lockup transaction
|
||||
const swapOutput = detectSwap(tweakedKey, lockupTx)
|
||||
if (!swapOutput) {
|
||||
return { ok: false, error: 'Could not detect swap output in lockup transaction' }
|
||||
}
|
||||
|
||||
const network = getNetwork(this.network)
|
||||
// const decodedAddress = address.fromBech32(refundAddress)
|
||||
|
||||
const details = [
|
||||
{
|
||||
...swapOutput,
|
||||
keys: privateKey,
|
||||
cooperative,
|
||||
type: OutputType.Taproot,
|
||||
txHash: lockupTx.getHash(),
|
||||
swapTree: swapTreeDeserialized,
|
||||
internalKey: musig.getAggregatedPublicKey(),
|
||||
}
|
||||
]
|
||||
const outputScript = address.toOutputScript(refundAddress, network)
|
||||
// Construct the refund transaction
|
||||
const refundTx = constructRefundTransaction(
|
||||
details,
|
||||
outputScript,
|
||||
cooperative ? 0 : timeoutBlockHeight,
|
||||
feePerVbyte,
|
||||
true
|
||||
)
|
||||
|
||||
if (!cooperative) {
|
||||
return { ok: true, transaction: refundTx }
|
||||
}
|
||||
|
||||
// For cooperative refund, get Boltz's partial signature
|
||||
try {
|
||||
musig = new Musig(await zkpInit(), privateKey, randomBytes(32), [
|
||||
boltzPublicKey,
|
||||
Buffer.from(privateKey.publicKey),
|
||||
])
|
||||
// Get the partial signature from Boltz
|
||||
const boltzSigRes = await this.getPartialRefundSignature(
|
||||
swapId,
|
||||
Buffer.from(musig.getPublicNonce()),
|
||||
refundTx,
|
||||
0
|
||||
)
|
||||
|
||||
if (!boltzSigRes.ok) {
|
||||
this.log(ERROR, 'Failed to get Boltz partial signature, falling back to uncooperative refund')
|
||||
// Fallback to uncooperative refund
|
||||
return await this.constructTaprootRefund(
|
||||
swapId,
|
||||
claimPublicKey,
|
||||
swapTree,
|
||||
timeoutBlockHeight,
|
||||
lockupTx,
|
||||
privateKey,
|
||||
refundAddress,
|
||||
feePerVbyte,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
const boltzSig = boltzSigRes.data
|
||||
|
||||
// Aggregate nonces
|
||||
musig.aggregateNonces([
|
||||
[boltzPublicKey, Musig.parsePubNonce(boltzSig.pubNonce)],
|
||||
])
|
||||
|
||||
// Tweak musig again after aggregating nonces
|
||||
TaprootUtils.tweakMusig(musig, swapTreeDeserialized.tree)
|
||||
|
||||
// Initialize session and sign
|
||||
musig.initializeSession(
|
||||
TaprootUtils.hashForWitnessV1(
|
||||
details,
|
||||
refundTx,
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
musig.signPartial()
|
||||
musig.addPartial(boltzPublicKey, Buffer.from(boltzSig.partialSignature, 'hex'))
|
||||
|
||||
// Set the witness to the aggregated signature
|
||||
refundTx.ins[0].witness = [musig.aggregatePartials()]
|
||||
|
||||
return { ok: true, transaction: refundTx }
|
||||
} catch (error: any) {
|
||||
this.log(ERROR, 'Cooperative refund failed:', error.message)
|
||||
// Fallback to uncooperative refund
|
||||
return await this.constructTaprootRefund(
|
||||
swapId,
|
||||
claimPublicKey,
|
||||
swapTree,
|
||||
timeoutBlockHeight,
|
||||
lockupTx,
|
||||
privateKey,
|
||||
refundAddress,
|
||||
feePerVbyte,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a refund transaction
|
||||
*/
|
||||
private broadcastRefundTransaction = async (transaction: Transaction): Promise<{ ok: true, txId: string } | { ok: false, error: string }> => {
|
||||
const url = `${this.httpUrl}/v2/chain/BTC/transaction`
|
||||
const req = { hex: transaction.toHex() }
|
||||
|
||||
const result = await loggedPost<{ id: string }>(this.log, url, req)
|
||||
if (!result.ok) {
|
||||
return result
|
||||
}
|
||||
|
||||
return { ok: true, txId: result.data.id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Refund a submarine swap
|
||||
* @param swapId - The swap ID
|
||||
* @param claimPublicKey - Boltz's claim public key
|
||||
* @param swapTree - The swap tree
|
||||
* @param timeoutBlockHeight - The timeout block height
|
||||
* @param privateKey - The refund private key (hex string)
|
||||
* @param refundAddress - The address to refund to
|
||||
* @param currentHeight - The current block height
|
||||
* @param lockupTxHex - The lockup transaction hex (optional, will fetch from Boltz if not provided)
|
||||
* @param feePerVbyte - Fee rate in sat/vbyte (optional, will use default if not provided)
|
||||
*/
|
||||
RefundSwap = async (params: {
|
||||
swapId: string,
|
||||
claimPublicKey: string,
|
||||
swapTree: string,
|
||||
timeoutBlockHeight: number,
|
||||
privateKeyHex: string,
|
||||
refundAddress: string,
|
||||
currentHeight: number,
|
||||
lockupTxHex?: string,
|
||||
feePerVbyte?: number
|
||||
}): Promise<{ ok: true, publish: { done: false, txHex: string, txId: string } | { done: true, txId: string } } | { ok: false, error: string }> => {
|
||||
const { swapId, claimPublicKey, swapTree, timeoutBlockHeight, privateKeyHex, refundAddress, currentHeight, lockupTxHex, feePerVbyte = 2 } = params
|
||||
|
||||
this.log('Starting refund process for swap:', swapId)
|
||||
|
||||
// Get the lockup transaction (from parameter or fetch from Boltz)
|
||||
let lockupTx: Transaction
|
||||
if (lockupTxHex) {
|
||||
this.log('Using provided lockup transaction hex')
|
||||
lockupTx = Transaction.fromHex(lockupTxHex)
|
||||
} else {
|
||||
this.log('Fetching lockup transaction from Boltz')
|
||||
const lockupTxRes = await this.getLockupTransaction(swapId)
|
||||
if (!lockupTxRes.ok) {
|
||||
return { ok: false, error: `Failed to get lockup transaction: ${lockupTxRes.error}` }
|
||||
}
|
||||
lockupTx = Transaction.fromHex(lockupTxRes.data.hex)
|
||||
}
|
||||
this.log('Lockup transaction retrieved:', lockupTx.getId())
|
||||
|
||||
// Check if swap has timed out
|
||||
if (currentHeight < timeoutBlockHeight) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `Swap has not timed out yet. Current height: ${currentHeight}, timeout: ${timeoutBlockHeight}`
|
||||
}
|
||||
}
|
||||
this.log(`Swap has timed out. Current height: ${currentHeight}, timeout: ${timeoutBlockHeight}`)
|
||||
|
||||
// Parse the private key
|
||||
const privateKey = ECPairFactory(ecc).fromPrivateKey(Buffer.from(privateKeyHex, 'hex'))
|
||||
|
||||
// Construct the refund transaction (tries cooperative first, then falls back to uncooperative)
|
||||
const refundTxRes = await this.constructTaprootRefund(
|
||||
swapId,
|
||||
claimPublicKey,
|
||||
swapTree,
|
||||
timeoutBlockHeight,
|
||||
lockupTx,
|
||||
privateKey,
|
||||
refundAddress,
|
||||
feePerVbyte,
|
||||
true // Try cooperative first
|
||||
)
|
||||
|
||||
if (!refundTxRes.ok) {
|
||||
return { ok: false, error: refundTxRes.error }
|
||||
}
|
||||
|
||||
const cooperative = !refundTxRes.cooperativeError
|
||||
this.log(`Refund transaction constructed (${cooperative ? 'cooperative' : 'uncooperative'}):`, refundTxRes.transaction.getId())
|
||||
if (!cooperative) {
|
||||
return { ok: true, publish: { done: false, txHex: refundTxRes.transaction.toHex(), txId: refundTxRes.transaction.getId() } }
|
||||
}
|
||||
// Broadcast the refund transaction
|
||||
const broadcastRes = await this.broadcastRefundTransaction(refundTxRes.transaction)
|
||||
if (!broadcastRes.ok) {
|
||||
return { ok: false, error: `Failed to broadcast refund transaction: ${broadcastRes.error}` }
|
||||
}
|
||||
|
||||
this.log('Refund transaction broadcasted successfully:', broadcastRes.txId)
|
||||
return { ok: true, publish: { done: true, txId: broadcastRes.txId } }
|
||||
}
|
||||
|
||||
SubscribeToInvoiceSwap = (data: InvoiceSwapData, swapDone: (result: { ok: true } | { ok: false, error: string }) => void, waitingTx: () => void) => {
|
||||
this.log("subscribing to invoice swap", { id: data.createdResponse.id })
|
||||
const webSocket = new ws(`${this.wsUrl}/v2/ws`)
|
||||
const subReq = { op: 'subscribe', channel: 'swap.update', args: [data.createdResponse.id] }
|
||||
webSocket.on('open', () => {
|
||||
webSocket.send(JSON.stringify(subReq))
|
||||
})
|
||||
let isDone = false
|
||||
const done = (failureReason?: string) => {
|
||||
isDone = true
|
||||
webSocket.close()
|
||||
if (failureReason) {
|
||||
swapDone({ ok: false, error: failureReason })
|
||||
} else {
|
||||
swapDone({ ok: true })
|
||||
}
|
||||
}
|
||||
webSocket.on('error', (err) => {
|
||||
this.log(ERROR, 'Error in WebSocket', err.message)
|
||||
})
|
||||
webSocket.on('close', () => {
|
||||
if (!isDone) {
|
||||
this.log(ERROR, 'WebSocket closed before swap was done');
|
||||
done('WebSocket closed before swap was done')
|
||||
}
|
||||
})
|
||||
webSocket.on('message', async (rawMsg) => {
|
||||
try {
|
||||
await this.handleSwapInvoiceMessage(rawMsg, data, done, waitingTx)
|
||||
} catch (err: any) {
|
||||
this.log(ERROR, 'Error handling invoice WebSocket message', err.message)
|
||||
webSocket.close()
|
||||
return
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
webSocket.close()
|
||||
}
|
||||
}
|
||||
|
||||
handleSwapInvoiceMessage = async (rawMsg: ws.RawData, data: InvoiceSwapData, closeWebSocket: (failureReason?: string) => void, waitingTx: () => void) => {
|
||||
const msg = JSON.parse(rawMsg.toString('utf-8'));
|
||||
if (msg.event !== 'update') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log('Got invoice WebSocket update');
|
||||
this.log(msg);
|
||||
switch (msg.args[0].status) {
|
||||
// "invoice.set" means Boltz is waiting for an onchain transaction to be sent
|
||||
case 'invoice.set':
|
||||
this.log('Waiting for onchain transaction');
|
||||
waitingTx()
|
||||
return;
|
||||
// Create a partial signature to allow Boltz to do a key path spend to claim the mainchain coins
|
||||
case 'transaction.claim.pending':
|
||||
await this.handleInvoiceClaimPending(data)
|
||||
return;
|
||||
|
||||
case 'transaction.claimed':
|
||||
this.log('Invoice swap successful');
|
||||
closeWebSocket()
|
||||
return;
|
||||
case 'swap.expired':
|
||||
case 'transaction.lockupFailed':
|
||||
case 'invoice.failedToPay':
|
||||
closeWebSocket(`swap ${data.createdResponse.id} failed with status ${msg.args[0].status}`)
|
||||
return;
|
||||
default:
|
||||
this.log('Unknown swap invoice WebSocket message', msg)
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handleInvoiceClaimPending = async (data: InvoiceSwapData) => {
|
||||
this.log('Creating cooperative claim transaction');
|
||||
const { createdResponse, info } = data
|
||||
const { paymentHash, keys } = info
|
||||
// Get the information request to create a partial signature
|
||||
const url = `${this.httpUrl}/v2/swap/submarine/${createdResponse.id}/claim`
|
||||
const claimTxDetailsRes = await loggedGet<{ preimage: string, transactionHash: string, pubNonce: string }>(this.log, url)
|
||||
if (!claimTxDetailsRes.ok) {
|
||||
return claimTxDetailsRes
|
||||
}
|
||||
const claimTxDetails = claimTxDetailsRes.data
|
||||
// Verify that Boltz actually paid the invoice by comparing the preimage hash
|
||||
// of the invoice to the SHA256 hash of the preimage from the response
|
||||
const claimTxPreimageHash = createHash('sha256').update(Buffer.from(claimTxDetails.preimage, 'hex')).digest()
|
||||
const invoicePreimageHash = Buffer.from(paymentHash, 'hex')
|
||||
|
||||
if (!claimTxPreimageHash.equals(invoicePreimageHash)) {
|
||||
this.log(ERROR, 'Boltz provided invalid preimage');
|
||||
return;
|
||||
}
|
||||
|
||||
const boltzPublicKey = Buffer.from(createdResponse.claimPublicKey, 'hex')
|
||||
|
||||
// Create a musig signing instance
|
||||
const musig = new Musig(await zkpInit(), keys, randomBytes(32), [
|
||||
boltzPublicKey,
|
||||
Buffer.from(keys.publicKey),
|
||||
]);
|
||||
// Tweak that musig with the Taptree of the swap scripts
|
||||
TaprootUtils.tweakMusig(
|
||||
musig,
|
||||
SwapTreeSerializer.deserializeSwapTree(createdResponse.swapTree).tree,
|
||||
);
|
||||
|
||||
// Aggregate the nonces
|
||||
musig.aggregateNonces([
|
||||
[boltzPublicKey, Buffer.from(claimTxDetails.pubNonce, 'hex')],
|
||||
]);
|
||||
// Initialize the session to sign the transaction hash from the response
|
||||
musig.initializeSession(
|
||||
Buffer.from(claimTxDetails.transactionHash, 'hex'),
|
||||
);
|
||||
|
||||
// Give our public nonce and the partial signature to Boltz
|
||||
const claimUrl = `${this.httpUrl}/v2/swap/submarine/${createdResponse.id}/claim`
|
||||
const claimReq = {
|
||||
pubNonce: Buffer.from(musig.getPublicNonce()).toString('hex'),
|
||||
partialSignature: Buffer.from(musig.signPartial()).toString('hex'),
|
||||
}
|
||||
const claimResponseRes = await loggedPost<{ pubNonce: string, partialSignature: string }>(this.log, claimUrl, claimReq)
|
||||
if (!claimResponseRes.ok) {
|
||||
return claimResponseRes
|
||||
}
|
||||
const claimResponse = claimResponseRes.data
|
||||
this.log('Claim response', claimResponse)
|
||||
}
|
||||
}
|
||||
50
src/services/lnd/swaps/swapHelpers.ts
Normal file
50
src/services/lnd/swaps/swapHelpers.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import axios from 'axios';
|
||||
import { Network } from 'bitcoinjs-lib';
|
||||
// import bolt11 from 'bolt11';
|
||||
import {
|
||||
Networks,
|
||||
} from 'boltz-core';
|
||||
import { PubLogger, ERROR } from '../../helpers/logger.js';
|
||||
import { BTCNetwork } from '../../main/settings.js';
|
||||
|
||||
|
||||
export const loggedPost = async <T>(log: PubLogger, url: string, req: any): Promise<{ ok: true, data: T } | { ok: false, error: string }> => {
|
||||
try {
|
||||
const { data } = await axios.post(url, req)
|
||||
return { ok: true, data: data as T }
|
||||
} catch (err: any) {
|
||||
if (err.response?.data) {
|
||||
log(ERROR, 'Error sending request', err.response.data)
|
||||
return { ok: false, error: JSON.stringify(err.response.data) }
|
||||
}
|
||||
log(ERROR, 'Error sending request', err.message)
|
||||
return { ok: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
export const loggedGet = async <T>(log: PubLogger, url: string): Promise<{ ok: true, data: T } | { ok: false, error: string }> => {
|
||||
try {
|
||||
const { data } = await axios.get(url)
|
||||
return { ok: true, data: data as T }
|
||||
} catch (err: any) {
|
||||
if (err.response?.data) {
|
||||
log(ERROR, 'Error getting request', err.response.data)
|
||||
return { ok: false, error: err.response.data }
|
||||
}
|
||||
log(ERROR, 'Error getting request', err.message)
|
||||
return { ok: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
export const getNetwork = (network: BTCNetwork): Network => {
|
||||
switch (network) {
|
||||
case 'mainnet':
|
||||
return Networks.bitcoinMainnet
|
||||
case 'testnet':
|
||||
return Networks.bitcoinTestnet
|
||||
case 'regtest':
|
||||
return Networks.bitcoinRegtest
|
||||
default:
|
||||
throw new Error(`Invalid network: ${network}`)
|
||||
}
|
||||
}
|
||||
431
src/services/lnd/swaps/swaps.ts
Normal file
431
src/services/lnd/swaps/swaps.ts
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
import { ECPairFactory } from 'ecpair';
|
||||
import * as ecc from 'tiny-secp256k1';
|
||||
import { getLogger } from '../../helpers/logger.js';
|
||||
import SettingsManager from '../../main/settingsManager.js';
|
||||
import * as Types from '../../../../proto/autogenerated/ts/types.js';
|
||||
import Storage from '../../storage/index.js';
|
||||
import LND from '../lnd.js';
|
||||
import { UserInvoicePayment } from '../../storage/entity/UserInvoicePayment.js';
|
||||
import { ReverseSwaps, TransactionSwapData } from './reverseSwaps.js';
|
||||
import { SubmarineSwaps, InvoiceSwapData } from './submarineSwaps.js';
|
||||
import { InvoiceSwap } from '../../storage/entity/InvoiceSwap.js';
|
||||
|
||||
|
||||
export class Swaps {
|
||||
settings: SettingsManager
|
||||
revSwappers: Record<string, ReverseSwaps>
|
||||
subSwappers: Record<string, SubmarineSwaps>
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
waitingSwaps: Record<string, boolean> = {}
|
||||
log = getLogger({ component: 'swaps' })
|
||||
constructor(settings: SettingsManager, storage: Storage) {
|
||||
this.settings = settings
|
||||
this.revSwappers = {}
|
||||
this.subSwappers = {}
|
||||
const network = settings.getSettings().lndSettings.network
|
||||
const { boltzHttpUrl, boltzWebSocketUrl, boltsHttpUrlAlt, boltsWebSocketUrlAlt } = settings.getSettings().swapsSettings
|
||||
if (boltzHttpUrl && boltzWebSocketUrl) {
|
||||
this.revSwappers[boltzHttpUrl] = new ReverseSwaps({ httpUrl: boltzHttpUrl, wsUrl: boltzWebSocketUrl, network })
|
||||
this.subSwappers[boltzHttpUrl] = new SubmarineSwaps({ httpUrl: boltzHttpUrl, wsUrl: boltzWebSocketUrl, network })
|
||||
}
|
||||
if (boltsHttpUrlAlt && boltsWebSocketUrlAlt) {
|
||||
this.revSwappers[boltsHttpUrlAlt] = new ReverseSwaps({ httpUrl: boltsHttpUrlAlt, wsUrl: boltsWebSocketUrlAlt, network })
|
||||
this.subSwappers[boltsHttpUrlAlt] = new SubmarineSwaps({ httpUrl: boltsHttpUrlAlt, wsUrl: boltsWebSocketUrlAlt, network })
|
||||
}
|
||||
this.storage = storage
|
||||
}
|
||||
|
||||
SetLnd = (lnd: LND) => {
|
||||
this.lnd = lnd
|
||||
}
|
||||
|
||||
Stop = () => { }
|
||||
|
||||
GetKeys = (privateKey: string) => {
|
||||
const keys = ECPairFactory(ecc).fromPrivateKey(Buffer.from(privateKey, 'hex'))
|
||||
return keys
|
||||
}
|
||||
|
||||
GetInvoiceSwapQuotes = async (appUserId: string, invoice: string): Promise<Types.InvoiceSwapQuote[]> => {
|
||||
if (!this.settings.getSettings().swapsSettings.enableSwaps) {
|
||||
throw new Error("Swaps are not enabled")
|
||||
}
|
||||
const swappers = Object.values(this.subSwappers)
|
||||
if (swappers.length === 0) {
|
||||
throw new Error("No swap services available")
|
||||
}
|
||||
const res = await Promise.allSettled(swappers.map(sw => this.getInvoiceSwapQuote(sw, appUserId, invoice)))
|
||||
const failures: string[] = []
|
||||
const success: Types.InvoiceSwapQuote[] = []
|
||||
for (const r of res) {
|
||||
if (r.status === 'fulfilled') {
|
||||
success.push(r.value)
|
||||
} else {
|
||||
failures.push(r.reason.message ? r.reason.message : r.reason.toString())
|
||||
}
|
||||
}
|
||||
if (success.length === 0) {
|
||||
throw new Error(failures.join("\n"))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
ListInvoiceSwaps = async (appUserId: string): Promise<Types.InvoiceSwapsList> => {
|
||||
const completedSwaps = await this.storage.paymentStorage.ListCompletedInvoiceSwaps(appUserId)
|
||||
const pendingSwaps = await this.storage.paymentStorage.ListPendingInvoiceSwaps(appUserId)
|
||||
return {
|
||||
swaps: completedSwaps.map(s => {
|
||||
return {
|
||||
invoice_paid: s.invoice,
|
||||
swap_operation_id: s.swap_operation_id,
|
||||
failure_reason: s.failure_reason,
|
||||
tx_id: s.tx_id,
|
||||
}
|
||||
}),
|
||||
quotes: pendingSwaps.map(s => {
|
||||
return {
|
||||
swap_operation_id: s.swap_operation_id,
|
||||
invoice: s.invoice,
|
||||
invoice_amount_sats: s.invoice_amount,
|
||||
address: s.address,
|
||||
transaction_amount_sats: s.transaction_amount,
|
||||
chain_fee_sats: s.chain_fee_sats,
|
||||
service_fee_sats: 0,
|
||||
service_url: s.service_url,
|
||||
swap_fee_sats: s.swap_fee_sats,
|
||||
tx_id: s.tx_id,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
RefundInvoiceSwap = async (swapOperationId: string, satPerVByte: number, refundAddress: string, currentHeight: number): Promise<{ published: false, txHex: string, txId: string } | { published: true, txId: string }> => {
|
||||
this.log("refunding invoice swap", { swapOperationId, satPerVByte, refundAddress, currentHeight })
|
||||
const swap = await this.storage.paymentStorage.GetRefundableInvoiceSwap(swapOperationId)
|
||||
if (!swap) {
|
||||
throw new Error("Swap not found or already used")
|
||||
}
|
||||
const swapper = this.subSwappers[swap.service_url]
|
||||
if (!swapper) {
|
||||
throw new Error("swapper service not found")
|
||||
}
|
||||
const result = await swapper.RefundSwap({
|
||||
swapId: swap.swap_quote_id,
|
||||
claimPublicKey: swap.claim_public_key,
|
||||
currentHeight,
|
||||
privateKeyHex: swap.ephemeral_private_key,
|
||||
refundAddress,
|
||||
swapTree: swap.swap_tree,
|
||||
timeoutBlockHeight: swap.timeout_block_height,
|
||||
feePerVbyte: satPerVByte,
|
||||
lockupTxHex: swap.lockup_tx_hex,
|
||||
})
|
||||
if (!result.ok) {
|
||||
throw new Error(result.error)
|
||||
}
|
||||
if (result.publish.done) {
|
||||
return { published: true, txId: result.publish.txId }
|
||||
}
|
||||
return { published: false, txHex: result.publish.txHex, txId: result.publish.txId }
|
||||
|
||||
}
|
||||
|
||||
PayInvoiceSwap = async (appUserId: string, swapOpId: string, satPerVByte: number, payAddress: (address: string, amt: number) => Promise<{ txId: string }>): Promise<void> => {
|
||||
this.log("paying invoice swap", { appUserId, swapOpId, satPerVByte })
|
||||
if (!this.settings.getSettings().swapsSettings.enableSwaps) {
|
||||
throw new Error("Swaps are not enabled")
|
||||
}
|
||||
if (!swapOpId) {
|
||||
throw new Error("swap operation id is required")
|
||||
}
|
||||
if (!satPerVByte) {
|
||||
throw new Error("sat per v byte is required")
|
||||
}
|
||||
const swap = await this.storage.paymentStorage.GetInvoiceSwap(swapOpId, appUserId)
|
||||
if (!swap) {
|
||||
throw new Error("swap not found")
|
||||
}
|
||||
const swapper = this.subSwappers[swap.service_url]
|
||||
if (!swapper) {
|
||||
throw new Error("swapper service not found")
|
||||
}
|
||||
if (this.waitingSwaps[swapOpId]) {
|
||||
throw new Error("swap already in progress")
|
||||
}
|
||||
this.waitingSwaps[swapOpId] = true
|
||||
const data = this.getInvoiceSwapData(swap)
|
||||
let txId = ""
|
||||
const close = swapper.SubscribeToInvoiceSwap(data, async (result) => {
|
||||
if (result.ok) {
|
||||
await this.storage.paymentStorage.FinalizeInvoiceSwap(swapOpId)
|
||||
this.log("invoice swap completed", { swapOpId, txId })
|
||||
} else {
|
||||
await this.storage.paymentStorage.FailInvoiceSwap(swapOpId, result.error, txId)
|
||||
this.log("invoice swap failed", { swapOpId, error: result.error })
|
||||
}
|
||||
}, () => payAddress(swap.address, swap.transaction_amount)
|
||||
.then(res => { txId = res.txId })
|
||||
.catch(err => { close(); this.log("error paying address", err.message || err) }))
|
||||
}
|
||||
|
||||
ResumeInvoiceSwaps = async () => {
|
||||
this.log("resuming invoice swaps")
|
||||
const swaps = await this.storage.paymentStorage.ListUnfinishedInvoiceSwaps()
|
||||
this.log("resuming", swaps.length, "invoice swaps")
|
||||
for (const swap of swaps) {
|
||||
try {
|
||||
this.resumeInvoiceSwap(swap)
|
||||
} catch (err: any) {
|
||||
this.log("error resuming invoice swap", err.message || err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private resumeInvoiceSwap = (swap: InvoiceSwap) => {
|
||||
// const swap = await this.storage.paymentStorage.GetInvoiceSwap(swapOpId, appUserId)
|
||||
if (!swap || !swap.tx_id || swap.used) {
|
||||
throw new Error("swap to resume not found, or does not have a tx id")
|
||||
}
|
||||
const swapper = this.subSwappers[swap.service_url]
|
||||
if (!swapper) {
|
||||
throw new Error("swapper service not found")
|
||||
}
|
||||
const data = this.getInvoiceSwapData(swap)
|
||||
swapper.SubscribeToInvoiceSwap(data, async (result) => {
|
||||
if (result.ok) {
|
||||
await this.storage.paymentStorage.FinalizeInvoiceSwap(swap.swap_operation_id)
|
||||
this.log("invoice swap completed", { swapOpId: swap.swap_operation_id, txId: swap.tx_id })
|
||||
} else {
|
||||
await this.storage.paymentStorage.FailInvoiceSwap(swap.swap_operation_id, result.error)
|
||||
this.log("invoice swap failed", { swapOpId: swap.swap_operation_id, error: result.error })
|
||||
}
|
||||
}, () => { throw new Error("swap tx already paid") })
|
||||
}
|
||||
|
||||
private getInvoiceSwapData = (swap: InvoiceSwap) => {
|
||||
return {
|
||||
createdResponse: {
|
||||
address: swap.address,
|
||||
claimPublicKey: swap.claim_public_key,
|
||||
id: swap.swap_quote_id,
|
||||
swapTree: swap.swap_tree,
|
||||
timeoutBlockHeight: swap.timeout_block_height,
|
||||
expectedAmount: swap.transaction_amount,
|
||||
},
|
||||
info: {
|
||||
keys: this.GetKeys(swap.ephemeral_private_key),
|
||||
paymentHash: swap.payment_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getInvoiceSwapQuote(swapper: SubmarineSwaps, appUserId: string, invoice: string): Promise<Types.InvoiceSwapQuote> {
|
||||
const feesRes = await swapper.GetFees()
|
||||
if (!feesRes.ok) {
|
||||
throw new Error(feesRes.error)
|
||||
}
|
||||
const decoded = await this.lnd.DecodeInvoice(invoice)
|
||||
const amt = decoded.numSatoshis
|
||||
const fee = Math.ceil((feesRes.fees.percentage / 100) * amt) + feesRes.fees.minerFees
|
||||
const res = await swapper.SwapInvoice(invoice)
|
||||
if (!res.ok) {
|
||||
throw new Error(res.error)
|
||||
}
|
||||
const newSwap = await this.storage.paymentStorage.AddInvoiceSwap({
|
||||
app_user_id: appUserId,
|
||||
swap_quote_id: res.createdResponse.id,
|
||||
swap_tree: JSON.stringify(res.createdResponse.swapTree),
|
||||
timeout_block_height: res.createdResponse.timeoutBlockHeight,
|
||||
ephemeral_public_key: res.pubkey,
|
||||
ephemeral_private_key: res.privKey,
|
||||
invoice: invoice,
|
||||
invoice_amount: amt,
|
||||
transaction_amount: res.createdResponse.expectedAmount,
|
||||
swap_fee_sats: fee,
|
||||
chain_fee_sats: 0,
|
||||
service_url: swapper.getHttpUrl(),
|
||||
address: res.createdResponse.address,
|
||||
claim_public_key: res.createdResponse.claimPublicKey,
|
||||
payment_hash: decoded.paymentHash,
|
||||
})
|
||||
return {
|
||||
swap_operation_id: newSwap.swap_operation_id,
|
||||
invoice: invoice,
|
||||
invoice_amount_sats: amt,
|
||||
address: res.createdResponse.address,
|
||||
transaction_amount_sats: res.createdResponse.expectedAmount,
|
||||
chain_fee_sats: 0,
|
||||
service_fee_sats: 0,
|
||||
service_url: swapper.getHttpUrl(),
|
||||
swap_fee_sats: fee,
|
||||
tx_id: newSwap.tx_id,
|
||||
}
|
||||
}
|
||||
|
||||
ListTxSwaps = async (appUserId: string, payments: UserInvoicePayment[], newOp: (p: UserInvoicePayment) => Types.UserOperation | undefined, getServiceFee: (amt: number) => number): Promise<Types.TxSwapsList> => {
|
||||
const completedSwaps = await this.storage.paymentStorage.ListCompletedTxSwaps(appUserId, payments)
|
||||
const pendingSwaps = await this.storage.paymentStorage.ListPendingTransactionSwaps(appUserId)
|
||||
return {
|
||||
swaps: completedSwaps.map(s => {
|
||||
const p = s.payment
|
||||
const op = p ? newOp(p) : 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,
|
||||
}
|
||||
}),
|
||||
quotes: pendingSwaps.map(s => {
|
||||
const serviceFee = getServiceFee(s.invoice_amount)
|
||||
return {
|
||||
swap_operation_id: s.swap_operation_id,
|
||||
invoice_amount_sats: s.invoice_amount,
|
||||
transaction_amount_sats: s.transaction_amount,
|
||||
chain_fee_sats: s.chain_fee_sats,
|
||||
service_fee_sats: serviceFee,
|
||||
swap_fee_sats: s.swap_fee_sats,
|
||||
service_url: s.service_url,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
GetTxSwapQuotes = async (appUserId: string, amt: number, getServiceFee: (decodedAmt: number) => number): Promise<Types.TransactionSwapQuote[]> => {
|
||||
if (!this.settings.getSettings().swapsSettings.enableSwaps) {
|
||||
throw new Error("Swaps are not enabled")
|
||||
}
|
||||
const swappers = Object.values(this.revSwappers)
|
||||
if (swappers.length === 0) {
|
||||
throw new Error("No swap services available")
|
||||
}
|
||||
const res = await Promise.allSettled(swappers.map(sw => this.getTxSwapQuote(sw, appUserId, amt, getServiceFee)))
|
||||
const failures: string[] = []
|
||||
const success: Types.TransactionSwapQuote[] = []
|
||||
for (const r of res) {
|
||||
if (r.status === 'fulfilled') {
|
||||
success.push(r.value)
|
||||
} else {
|
||||
failures.push(r.reason.message ? r.reason.message : r.reason.toString())
|
||||
}
|
||||
}
|
||||
if (success.length === 0) {
|
||||
throw new Error(failures.join("\n"))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
private async getTxSwapQuote(swapper: ReverseSwaps, appUserId: string, amt: number, getServiceFee: (decodedAmt: number) => number): Promise<Types.TransactionSwapQuote> {
|
||||
this.log("getting transaction swap quote")
|
||||
const feesRes = await swapper.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 swapper.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,
|
||||
service_url: swapper.getHttpUrl(),
|
||||
})
|
||||
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,
|
||||
service_url: swapper.getHttpUrl(),
|
||||
}
|
||||
}
|
||||
|
||||
async PayAddrWithSwap(appUserId: string, swapOpId: string, address: string, payInvoice: (invoice: string, amt: number) => Promise<void>) {
|
||||
if (!this.settings.getSettings().swapsSettings.enableSwaps) {
|
||||
throw new Error("Swaps are not enabled")
|
||||
}
|
||||
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 swapper = this.revSwappers[txSwap.service_url]
|
||||
if (!swapper) {
|
||||
throw new Error("swapper service not found")
|
||||
}
|
||||
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 }
|
||||
swapper.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@ import Storage from "../storage/index.js";
|
|||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import LND from "../lnd/lnd.js";
|
||||
import SettingsManager from "./settingsManager.js";
|
||||
import { Swaps } from "../lnd/swaps.js";
|
||||
import { Swaps } from "../lnd/swaps/swaps.js";
|
||||
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js";
|
||||
export class AdminManager {
|
||||
settings: SettingsManager
|
||||
storage: Storage
|
||||
|
|
@ -260,15 +261,62 @@ export class AdminManager {
|
|||
}
|
||||
}
|
||||
|
||||
async ListAdminSwaps(): Promise<Types.SwapsList> {
|
||||
return this.swaps.ListSwaps("admin", [], p => undefined, amt => 0)
|
||||
async ListAdminInvoiceSwaps(): Promise<Types.InvoiceSwapsList> {
|
||||
return this.swaps.ListInvoiceSwaps("admin")
|
||||
}
|
||||
|
||||
async GetAdminInvoiceSwapQuotes(req: Types.InvoiceSwapRequest): Promise<Types.InvoiceSwapQuoteList> {
|
||||
const invoice = await this.lnd.NewInvoice(req.amount_sats, "Admin Swap", defaultInvoiceExpiry, { useProvider: false, from: 'system' })
|
||||
const quotes = await this.swaps.GetInvoiceSwapQuotes("admin", invoice.payRequest)
|
||||
return { quotes }
|
||||
}
|
||||
|
||||
async PayAdminInvoiceSwap(req: Types.PayAdminInvoiceSwapRequest): Promise<Types.AdminInvoiceSwapResponse> {
|
||||
const txId = await new Promise<string>(res => {
|
||||
this.swaps.PayInvoiceSwap("admin", req.swap_operation_id, req.sat_per_v_byte, async (addr, amt) => {
|
||||
const tx = await this.lnd.PayAddress(addr, amt, req.sat_per_v_byte, "", { useProvider: false, from: 'system' })
|
||||
this.log("paid admin invoice swap", { swapOpId: req.swap_operation_id, txId: tx.txid })
|
||||
await this.storage.metricsStorage.AddRootOperation("chain_payment", txId, amt)
|
||||
|
||||
// Fetch the full transaction hex for potential refunds
|
||||
let lockupTxHex: string | undefined
|
||||
try {
|
||||
const txDetails = await this.lnd.GetTx(tx.txid)
|
||||
lockupTxHex = txDetails.rawTxHex
|
||||
} catch (err: any) {
|
||||
this.log("Warning: Could not fetch transaction hex for refund purposes:", err.message)
|
||||
}
|
||||
|
||||
await this.storage.paymentStorage.SetInvoiceSwapTxId(req.swap_operation_id, txId, lockupTxHex)
|
||||
this.log("saved admin swap txid", { swapOpId: req.swap_operation_id, txId })
|
||||
res(tx.txid)
|
||||
return { txId: tx.txid }
|
||||
})
|
||||
})
|
||||
return { tx_id: txId }
|
||||
}
|
||||
|
||||
async RefundAdminInvoiceSwap(req: Types.RefundAdminInvoiceSwapRequest): Promise<Types.AdminInvoiceSwapResponse> {
|
||||
const info = await this.lnd.GetInfo()
|
||||
const currentHeight = info.blockHeight
|
||||
const address = await this.lnd.NewAddress(Types.AddressType.WITNESS_PUBKEY_HASH, { useProvider: false, from: 'system' })
|
||||
const result = await this.swaps.RefundInvoiceSwap(req.swap_operation_id, req.sat_per_v_byte, address.address, currentHeight)
|
||||
if (result.published) {
|
||||
return { tx_id: result.txId }
|
||||
}
|
||||
await this.lnd.PublishTransaction(result.txHex)
|
||||
return { tx_id: result.txId }
|
||||
}
|
||||
|
||||
async ListAdminTxSwaps(): Promise<Types.TxSwapsList> {
|
||||
return this.swaps.ListTxSwaps("admin", [], p => undefined, amt => 0)
|
||||
}
|
||||
|
||||
async GetAdminTransactionSwapQuotes(req: Types.TransactionSwapRequest): Promise<Types.TransactionSwapQuoteList> {
|
||||
const quotes = await this.swaps.GetTxSwapQuotes("admin", req.transaction_amount_sats, () => 0)
|
||||
return { quotes }
|
||||
}
|
||||
async PayAdminTransactionSwap(req: Types.PayAdminTransactionSwapRequest): Promise<Types.AdminSwapResponse> {
|
||||
async PayAdminTransactionSwap(req: Types.PayAdminTransactionSwapRequest): Promise<Types.AdminTxSwapResponse> {
|
||||
const routingFloor = this.settings.getSettings().lndSettings.routingFeeFloor
|
||||
const routingLimit = this.settings.getSettings().lndSettings.routingFeeLimitBps / 10000
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,8 @@ export default class {
|
|||
let log = getLogger({})
|
||||
this.storage.paymentStorage.DeleteExpiredTransactionSwaps(height)
|
||||
.catch(err => log(ERROR, "failed to delete expired transaction swaps", err.message || err))
|
||||
this.storage.paymentStorage.DeleteExpiredInvoiceSwaps(height)
|
||||
.catch(err => log(ERROR, "failed to delete expired invoice swaps", err.message || err))
|
||||
try {
|
||||
const balanceEvents = await this.paymentManager.GetLndBalance()
|
||||
if (!skipMetrics) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { AdminManager } from "./adminManager.js"
|
|||
import SettingsManager from "./settingsManager.js"
|
||||
import { LoadStorageSettingsFromEnv } from "../storage/index.js"
|
||||
import { NostrSender } from "../nostr/sender.js"
|
||||
import { Swaps } from "../lnd/swaps.js"
|
||||
import { Swaps } from "../lnd/swaps/swaps.js"
|
||||
export type AppData = {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
|
|
@ -79,6 +79,7 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM
|
|||
await mainHandler.paymentManager.CleanupOldUnpaidInvoices()
|
||||
await mainHandler.appUserManager.CleanupInactiveUsers()
|
||||
await mainHandler.appUserManager.CleanupNeverActiveUsers()
|
||||
await swaps.ResumeInvoiceSwaps()
|
||||
await mainHandler.paymentManager.watchDog.Start()
|
||||
return { mainHandler, apps, localProviderClient, wizard, adminManager }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { LiquidityManager } from './liquidityManager.js'
|
|||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
import { UserInvoicePayment } from '../storage/entity/UserInvoicePayment.js'
|
||||
import SettingsManager from './settingsManager.js'
|
||||
import { Swaps, TransactionSwapData } from '../lnd/swaps.js'
|
||||
import { Swaps } from '../lnd/swaps/swaps.js'
|
||||
import { Transaction, OutputDetail } from '../../../proto/lnd/lightning.js'
|
||||
import { LndAddress } from '../lnd/lnd.js'
|
||||
import Metrics from '../metrics/index.js'
|
||||
|
|
@ -634,11 +634,11 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
async ListSwaps(ctx: Types.UserContext): Promise<Types.SwapsList> {
|
||||
const payments = await this.storage.paymentStorage.ListSwapPayments(ctx.app_user_id)
|
||||
async ListTxSwaps(ctx: Types.UserContext): Promise<Types.TxSwapsList> {
|
||||
const payments = await this.storage.paymentStorage.ListTxSwapPayments(ctx.app_user_id)
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const isManagedUser = ctx.user_id !== app.owner.user_id
|
||||
return this.swaps.ListSwaps(ctx.app_user_id, payments, p => {
|
||||
return this.swaps.ListTxSwaps(ctx.app_user_id, payments, p => {
|
||||
const opId = `${Types.UserOperationType.OUTGOING_TX}-${p.serial_id}`
|
||||
return this.newInvoicePaymentOperation({ amount: p.paid_amount, confirmed: p.paid_at_unix !== 0, invoice: p.invoice, opId, networkFee: p.routing_fees, serviceFee: p.service_fees, paidAtUnix: p.paid_at_unix })
|
||||
}, amt => this.getSendServiceFee(Types.UserOperationType.OUTGOING_INVOICE, amt, isManagedUser))
|
||||
|
|
|
|||
|
|
@ -106,6 +106,33 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.PayAdminTransactionSwap(req)
|
||||
},
|
||||
ListAdminTxSwaps: async ({ ctx }) => {
|
||||
return mainHandler.adminManager.ListAdminTxSwaps()
|
||||
},
|
||||
GetAdminInvoiceSwapQuotes: async ({ ctx, req }) => {
|
||||
const err = Types.InvoiceSwapRequestValidate(req, {
|
||||
amount_sats_CustomCheck: amt => amt > 0
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.GetAdminInvoiceSwapQuotes(req)
|
||||
},
|
||||
RefundAdminInvoiceSwap: async ({ ctx, req }) => {
|
||||
const err = Types.RefundAdminInvoiceSwapRequestValidate(req, {
|
||||
swap_operation_id_CustomCheck: id => id !== '',
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.RefundAdminInvoiceSwap(req)
|
||||
},
|
||||
ListAdminInvoiceSwaps: async ({ ctx }) => {
|
||||
return mainHandler.adminManager.ListAdminInvoiceSwaps()
|
||||
},
|
||||
PayAdminInvoiceSwap: async ({ ctx, req }) => {
|
||||
const err = Types.PayAdminInvoiceSwapRequestValidate(req, {
|
||||
swap_operation_id_CustomCheck: id => id !== '',
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.PayAdminInvoiceSwap(req)
|
||||
},
|
||||
GetProvidersDisruption: async () => {
|
||||
return mainHandler.metricsManager.GetProvidersDisruption()
|
||||
},
|
||||
|
|
@ -145,9 +172,7 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
GetUserOperations: async ({ ctx, req }) => {
|
||||
return mainHandler.paymentManager.GetUserOperations(ctx.user_id, req)
|
||||
},
|
||||
ListAdminSwaps: async ({ ctx }) => {
|
||||
return mainHandler.adminManager.ListAdminSwaps()
|
||||
},
|
||||
|
||||
GetPaymentState: async ({ ctx, req }) => {
|
||||
const err = Types.GetPaymentStateRequestValidate(req, {
|
||||
invoice_CustomCheck: invoice => invoice !== ""
|
||||
|
|
@ -165,8 +190,8 @@ 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)
|
||||
ListTxSwaps: async ({ ctx }) => {
|
||||
return mainHandler.paymentManager.ListTxSwaps(ctx)
|
||||
},
|
||||
GetTransactionSwapQuotes: async ({ ctx, req }) => {
|
||||
return mainHandler.paymentManager.GetTransactionSwapQuotes(ctx, req)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import * as fs from 'fs'
|
|||
import { UserAccess } from "../entity/UserAccess.js"
|
||||
import { AdminSettings } from "../entity/AdminSettings.js"
|
||||
import { TransactionSwap } from "../entity/TransactionSwap.js"
|
||||
import { InvoiceSwap } from "../entity/InvoiceSwap.js"
|
||||
|
||||
|
||||
export type DbSettings = {
|
||||
|
|
@ -76,7 +77,8 @@ export const MainDbEntities = {
|
|||
'AppUserDevice': AppUserDevice,
|
||||
'UserAccess': UserAccess,
|
||||
'AdminSettings': AdminSettings,
|
||||
'TransactionSwap': TransactionSwap
|
||||
'TransactionSwap': TransactionSwap,
|
||||
'InvoiceSwap': InvoiceSwap
|
||||
}
|
||||
export type MainDbNames = keyof typeof MainDbEntities
|
||||
export const MainDbEntitiesNames = Object.keys(MainDbEntities)
|
||||
|
|
|
|||
88
src/services/storage/entity/InvoiceSwap.ts
Normal file
88
src/services/storage/entity/InvoiceSwap.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn, UpdateDateColumn } from "typeorm";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity()
|
||||
export class InvoiceSwap {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
swap_operation_id: string
|
||||
|
||||
@Column()
|
||||
app_user_id: string
|
||||
|
||||
@Column()
|
||||
swap_quote_id: string
|
||||
|
||||
@Column()
|
||||
swap_tree: string
|
||||
|
||||
@Column()
|
||||
claim_public_key: string
|
||||
|
||||
@Column()
|
||||
payment_hash: string
|
||||
|
||||
/* @Column()
|
||||
lockup_address: string */
|
||||
|
||||
/* @Column()
|
||||
refund_public_key: string */
|
||||
|
||||
@Column()
|
||||
timeout_block_height: number
|
||||
|
||||
@Column()
|
||||
invoice: string
|
||||
|
||||
@Column()
|
||||
invoice_amount: number
|
||||
|
||||
@Column()
|
||||
transaction_amount: number
|
||||
|
||||
@Column()
|
||||
swap_fee_sats: number
|
||||
|
||||
@Column()
|
||||
chain_fee_sats: number
|
||||
|
||||
|
||||
|
||||
@Column()
|
||||
ephemeral_public_key: string
|
||||
|
||||
@Column()
|
||||
address: string
|
||||
|
||||
// the private key is used on to perform a swap, it does not hold any funds once the swap is completed
|
||||
// the swap should only last a few seconds, so it is not a security risk to store the private key in the database
|
||||
// the key is stored here mostly for recovery purposes, in case something goes wrong with the swap
|
||||
@Column()
|
||||
ephemeral_private_key: string
|
||||
|
||||
@Column({ default: false })
|
||||
used: boolean
|
||||
|
||||
@Column({ default: "" })
|
||||
preimage: string
|
||||
|
||||
@Column({ default: "" })
|
||||
failure_reason: string
|
||||
|
||||
@Column({ default: "" })
|
||||
tx_id: string
|
||||
|
||||
@Column({ default: "", type: "text" })
|
||||
lockup_tx_hex: string
|
||||
|
||||
/* @Column({ default: "" })
|
||||
address_paid: string */
|
||||
|
||||
@Column({ default: "" })
|
||||
service_url: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class InvoiceSwaps1769529793283 implements MigrationInterface {
|
||||
name = 'InvoiceSwaps1769529793283'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "invoice_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, "claim_public_key" varchar NOT NULL, "payment_hash" 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, "ephemeral_public_key" varchar NOT NULL, "address" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "preimage" varchar NOT NULL DEFAULT (''), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "service_url" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "invoice_swap"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class InvoiceSwapsFixes1769805357459 implements MigrationInterface {
|
||||
name = 'InvoiceSwapsFixes1769805357459'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "temporary_invoice_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, "claim_public_key" varchar NOT NULL, "payment_hash" 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, "ephemeral_public_key" varchar NOT NULL, "address" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "preimage" varchar NOT NULL DEFAULT (''), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "service_url" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "lockup_tx_hex" text NOT NULL DEFAULT (''))`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_invoice_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at" FROM "invoice_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "invoice_swap"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_invoice_swap" RENAME TO "invoice_swap"`);
|
||||
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "invoice_swap" RENAME TO "temporary_invoice_swap"`);
|
||||
await queryRunner.query(`CREATE TABLE "invoice_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, "claim_public_key" varchar NOT NULL, "payment_hash" 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, "ephemeral_public_key" varchar NOT NULL, "address" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "preimage" varchar NOT NULL DEFAULT (''), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "service_url" 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 "invoice_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at" FROM "temporary_invoice_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_invoice_swap"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -32,14 +32,15 @@ import { TxSwapAddress1764779178945 } from './1764779178945-tx_swap_address.js'
|
|||
import { ClinkRequester1765497600000 } from './1765497600000-clink_requester.js'
|
||||
import { TrackedProviderHeight1766504040000 } from './1766504040000-tracked_provider_height.js'
|
||||
import { SwapsServiceUrl1768413055036 } from './1768413055036-swaps_service_url.js'
|
||||
|
||||
import { InvoiceSwaps1769529793283 } from './1769529793283-invoice_swaps.js'
|
||||
import { InvoiceSwapsFixes1769805357459 } from './1769805357459-invoice_swaps_fixes.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,
|
||||
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036]
|
||||
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459]
|
||||
|
||||
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> => {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import TransactionsQueue from "./db/transactionsQueue.js";
|
|||
import { LoggedEvent } from './eventsLog.js';
|
||||
import { StorageInterface } from './db/storageInterface.js';
|
||||
import { TransactionSwap } from './entity/TransactionSwap.js';
|
||||
import { InvoiceSwap } from './entity/InvoiceSwap.js';
|
||||
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo, offerId?: string, payerData?: Record<string, string>, rejectUnauthorized?: boolean, token?: string, blind?: boolean, clinkRequesterPub?: string, clinkRequesterEventId?: string }
|
||||
export const defaultInvoiceExpiry = 60 * 60
|
||||
export default class {
|
||||
|
|
@ -472,20 +473,20 @@ 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, address: string, txId: string) {
|
||||
async FinalizeTransactionSwap(swapOperationId: string, address: string, chainTxId: string, txId?: string) {
|
||||
return this.dbs.Update<TransactionSwap>('TransactionSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
tx_id: txId,
|
||||
tx_id: chainTxId,
|
||||
address_paid: address,
|
||||
})
|
||||
}, txId)
|
||||
}
|
||||
|
||||
async FailTransactionSwap(swapOperationId: string, address: string, failureReason: string) {
|
||||
async FailTransactionSwap(swapOperationId: string, address: string, failureReason: string, txId?: string) {
|
||||
return this.dbs.Update<TransactionSwap>('TransactionSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
failure_reason: failureReason,
|
||||
address_paid: address,
|
||||
})
|
||||
}, txId)
|
||||
}
|
||||
|
||||
async DeleteTransactionSwap(swapOperationId: string, txId?: string) {
|
||||
|
|
@ -500,11 +501,11 @@ export default class {
|
|||
return this.dbs.Find<TransactionSwap>('TransactionSwap', { where: { used: false, app_user_id: appUserId } }, txId)
|
||||
}
|
||||
|
||||
async ListSwapPayments(userId: string, txId?: string) {
|
||||
async ListTxSwapPayments(userId: string, txId?: string) {
|
||||
return this.dbs.Find<UserInvoicePayment>('UserInvoicePayment', { where: { swap_operation_id: Not(IsNull()), user: { user_id: userId } } }, txId)
|
||||
}
|
||||
|
||||
async ListCompletedSwaps(appUserId: string, payments: UserInvoicePayment[], txId?: string) {
|
||||
async ListCompletedTxSwaps(appUserId: string, payments: UserInvoicePayment[], 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>()
|
||||
|
|
@ -515,6 +516,78 @@ export default class {
|
|||
swap: c, payment: paymentsMap.get(c.swap_operation_id)
|
||||
}))
|
||||
}
|
||||
|
||||
async AddInvoiceSwap(swap: Partial<InvoiceSwap>) {
|
||||
return this.dbs.CreateAndSave<InvoiceSwap>('InvoiceSwap', swap)
|
||||
}
|
||||
|
||||
async GetInvoiceSwap(swapOperationId: string, appUserId: string, txId?: string) {
|
||||
const swap = await this.dbs.FindOne<InvoiceSwap>('InvoiceSwap', { where: { swap_operation_id: swapOperationId, used: false, app_user_id: appUserId } }, txId)
|
||||
if (!swap || swap.tx_id) {
|
||||
return null
|
||||
}
|
||||
return swap
|
||||
}
|
||||
|
||||
async FinalizeInvoiceSwap(swapOperationId: string, txId?: string) {
|
||||
return this.dbs.Update<InvoiceSwap>('InvoiceSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
}, txId)
|
||||
}
|
||||
|
||||
async UpdateInvoiceSwap(swapOperationId: string, update: Partial<InvoiceSwap>, txId?: string) {
|
||||
return this.dbs.Update<InvoiceSwap>('InvoiceSwap', { swap_operation_id: swapOperationId }, update, txId)
|
||||
}
|
||||
|
||||
async SetInvoiceSwapTxId(swapOperationId: string, chainTxId: string, lockupTxHex?: string, txId?: string) {
|
||||
const update: Partial<InvoiceSwap> = {
|
||||
tx_id: chainTxId,
|
||||
}
|
||||
if (lockupTxHex) {
|
||||
update.lockup_tx_hex = lockupTxHex
|
||||
}
|
||||
return this.dbs.Update<InvoiceSwap>('InvoiceSwap', { swap_operation_id: swapOperationId }, update, txId)
|
||||
}
|
||||
|
||||
async FailInvoiceSwap(swapOperationId: string, failureReason: string, txId?: string) {
|
||||
return this.dbs.Update<InvoiceSwap>('InvoiceSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
failure_reason: failureReason,
|
||||
}, txId)
|
||||
}
|
||||
|
||||
async DeleteInvoiceSwap(swapOperationId: string, txId?: string) {
|
||||
return this.dbs.Delete<InvoiceSwap>('InvoiceSwap', { swap_operation_id: swapOperationId }, txId)
|
||||
}
|
||||
|
||||
async DeleteExpiredInvoiceSwaps(currentHeight: number, txId?: string) {
|
||||
return this.dbs.Delete<InvoiceSwap>('InvoiceSwap', { timeout_block_height: LessThan(currentHeight) }, txId)
|
||||
}
|
||||
|
||||
async ListCompletedInvoiceSwaps(appUserId: string, txId?: string) {
|
||||
return this.dbs.Find<InvoiceSwap>('InvoiceSwap', { where: { used: true, app_user_id: appUserId } }, txId)
|
||||
}
|
||||
|
||||
async ListPendingInvoiceSwaps(appUserId: string, txId?: string) {
|
||||
return this.dbs.Find<InvoiceSwap>('InvoiceSwap', { where: { used: false, app_user_id: appUserId } }, txId)
|
||||
}
|
||||
|
||||
async ListUnfinishedInvoiceSwaps(txId?: string) {
|
||||
const swaps = await this.dbs.Find<InvoiceSwap>('InvoiceSwap', { where: { used: false } }, txId)
|
||||
return swaps.filter(s => !!s.tx_id)
|
||||
}
|
||||
|
||||
async GetRefundableInvoiceSwap(swapOperationId: string, txId?: string) {
|
||||
const swap = await this.dbs.FindOne<InvoiceSwap>('InvoiceSwap', { where: { swap_operation_id: swapOperationId } }, txId)
|
||||
if (!swap || !swap.tx_id) {
|
||||
return null
|
||||
}
|
||||
if (swap.used && !swap.failure_reason) {
|
||||
return null
|
||||
}
|
||||
return swap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const orFail = async <T>(resultPromise: Promise<T | null>) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue