Merge pull request #858 from shocknet/swaps-test
Reverse Swaps + provider fixes + fees fixes
This commit is contained in:
commit
4c58cab1d6
70 changed files with 4306 additions and 770 deletions
|
|
@ -21,6 +21,7 @@ import { ManagementGrant } from "./build/src/services/storage/entity/ManagementG
|
|||
import { AppUserDevice } from "./build/src/services/storage/entity/AppUserDevice.js"
|
||||
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 { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
|
||||
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
|
||||
|
|
@ -41,6 +42,11 @@ import { AppUserDevice1753285173175 } from './build/src/services/storage/migrati
|
|||
import { UserAccess1759426050669 } from './build/src/services/storage/migrations/1759426050669-user_access.js'
|
||||
import { AddBlindToUserOffer1760000000000 } from './build/src/services/storage/migrations/1760000000000-add_blind_to_user_offer.js'
|
||||
import { ApplicationAvatarUrl1761000001000 } from './build/src/services/storage/migrations/1761000001000-application_avatar_url.js'
|
||||
import { AdminSettings1761683639419 } from './build/src/services/storage/migrations/1761683639419-admin_settings.js'
|
||||
import { TxSwap1762890527098 } from './build/src/services/storage/migrations/1762890527098-tx_swap.js'
|
||||
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'
|
||||
|
||||
export default new DataSource({
|
||||
type: "better-sqlite3",
|
||||
|
|
@ -49,10 +55,12 @@ export default new DataSource({
|
|||
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878,
|
||||
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
||||
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611,
|
||||
AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000],
|
||||
AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098,
|
||||
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000],
|
||||
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo,
|
||||
TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings],
|
||||
TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings, TransactionSwap],
|
||||
// synchronize: true,
|
||||
})
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/admin_settings -d ./datasource.js
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/swaps_service_url -d ./datasource.js
|
||||
73
decode.js
Executable file
73
decode.js
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { decodeBech32, nip19 } from '@shocknet/clink-sdk'
|
||||
|
||||
const nip19String = process.argv[2]
|
||||
|
||||
if (!nip19String) {
|
||||
console.error('Usage: node decode.js <nip19_string>')
|
||||
console.error('Example: node decode.js nprofile1qyd8wumn8ghj7um5wfn8y7fwwd5x7cmt9ehx2arhdaexkqpqwmk5tuqvafa6ckwc6zmaypyy3af3n4aeds2ql7m0ew42kzsn638q9s9z8p')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
try {
|
||||
// Check prefix to determine which decoder to use
|
||||
const prefix = nip19String.split('1')[0]
|
||||
const decoded = (prefix === 'noffer' || prefix === 'ndebit' || prefix === 'nmanage')
|
||||
? decodeBech32(nip19String)
|
||||
: nip19.decode(nip19String)
|
||||
|
||||
console.log('\nDecoded:')
|
||||
console.log('Type:', decoded.type)
|
||||
console.log('\nData:')
|
||||
console.log(JSON.stringify(decoded.data, null, 2))
|
||||
|
||||
if (decoded.type === 'nprofile') {
|
||||
console.log('\nDetails:')
|
||||
console.log(' Pubkey:', decoded.data.pubkey)
|
||||
if (decoded.data.relays) {
|
||||
console.log(' Relays:')
|
||||
decoded.data.relays.forEach((relay, i) => {
|
||||
console.log(` ${i + 1}. ${relay}`)
|
||||
})
|
||||
}
|
||||
} else if (decoded.type === 'npub') {
|
||||
console.log('\nDetails:')
|
||||
console.log(' Pubkey:', decoded.data)
|
||||
} else if (decoded.type === 'nsec') {
|
||||
console.log('\nDetails:')
|
||||
console.log(' Private Key:', decoded.data)
|
||||
} else if (decoded.type === 'note') {
|
||||
console.log('\nDetails:')
|
||||
console.log(' Event ID:', decoded.data)
|
||||
} else if (decoded.type === 'noffer') {
|
||||
console.log('\nDetails:')
|
||||
console.log(' Pubkey:', decoded.data.pubkey)
|
||||
console.log(' Offer:', decoded.data.offer)
|
||||
if (decoded.data.relay) {
|
||||
console.log(' Relay:', decoded.data.relay)
|
||||
}
|
||||
if (decoded.data.priceType) {
|
||||
console.log(' Price Type:', decoded.data.priceType)
|
||||
}
|
||||
} else if (decoded.type === 'ndebit') {
|
||||
console.log('\nDetails:')
|
||||
console.log(' Pubkey:', decoded.data.pubkey)
|
||||
console.log(' Pointer:', decoded.data.pointer)
|
||||
if (decoded.data.relay) {
|
||||
console.log(' Relay:', decoded.data.relay)
|
||||
}
|
||||
} else if (decoded.type === 'nmanage') {
|
||||
console.log('\nDetails:')
|
||||
console.log(' Pubkey:', decoded.data.pubkey)
|
||||
console.log(' Pointer:', decoded.data.pointer)
|
||||
if (decoded.data.relay) {
|
||||
console.log(' Relay:', decoded.data.relay)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error decoding bech32 string:', error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
80
env.example
80
env.example
|
|
@ -2,6 +2,7 @@
|
|||
# Copy this file as .env in the Pub folder and uncomment the desired settings to override defaults
|
||||
# Alternatively, these settings can be passed as environment variables at startup
|
||||
|
||||
|
||||
#LND_CONNECTION
|
||||
# Defaults typical for straight Linux
|
||||
# Containers, Mac and Windows may need more detailed paths
|
||||
|
|
@ -9,15 +10,22 @@
|
|||
#LND_CERT_PATH=~/.lnd/tls.cert
|
||||
#LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
|
||||
#LND_LOG_DIR=~/.lnd/logs/bitcoin/mainnet/lnd.log
|
||||
#BTC_NETWORK=mainnet
|
||||
# Bypass LND entirely and daisychain off the bootstrap provider (testing only)
|
||||
#USE_ONLY_LIQUIDITY_PROVIDER=false
|
||||
|
||||
#BOOTSTRAP_PEER
|
||||
# A trusted peer that will hold a node-level account until channel automation becomes affordable
|
||||
# The developer is used by default or you may specify your own
|
||||
# To disable this feature entirely overwrite the env with "null"
|
||||
#LIQUIDITY_PROVIDER_PUB=null
|
||||
# The provider pubkey is extracted from PROVIDER_NPROFILE (nprofile contains both pubkey and relay URL)
|
||||
# The developers node is used by default, or you may specify another.
|
||||
# To disable this feature entirely set DISABLE_LIQUIDITY_PROVIDER=true
|
||||
#DISABLE_LIQUIDITY_PROVIDER=false
|
||||
#PROVIDER_NPROFILE=nprofile1qyd8wumn8ghj7um5wfn8y7fwwd5x7cmt9ehx2arhdaexkqpqwmk5tuqvafa6ckwc6zmaypyy3af3n4aeds2ql7m0ew42kzsn638q9s9z8p
|
||||
|
||||
#SWAPS
|
||||
#BOLTZ_HTTP_URL=
|
||||
#BOLTZ_WEBSOCKET_URL=
|
||||
#ENABLE_SWAPS=false
|
||||
|
||||
#DB
|
||||
#DATABASE_FILE=db.sqlite
|
||||
|
|
@ -33,48 +41,44 @@
|
|||
|
||||
|
||||
#LOCALHOST
|
||||
# For REST Management
|
||||
#ADMIN_TOKEN=
|
||||
#PORT=1776
|
||||
#JWT_SECRET=
|
||||
|
||||
#PUSH_SERVICE
|
||||
# For Wallet notifs via FCM
|
||||
#SHOCK_PUSH_URL=
|
||||
|
||||
#Lightning Address Bridge
|
||||
#LNA Bridge
|
||||
# Tell wallets where they can get a noffer-based Lightning Address
|
||||
#BRIDGE_URL=https://shockwallet.app
|
||||
|
||||
#LIGHTNING
|
||||
# Maximum amount in network fees passed to LND when it pays an external invoice
|
||||
# Service Fee: What Pub charges users (max of BPS*amount or floor)
|
||||
# Routing Fee Limit: What Pub allows LND to spend (max of BPS*amount or floor)
|
||||
# Routing fee limit must be <= service fee (validated at startup to ensure spread)
|
||||
# BPS are basis points, 100 BPS = 1%
|
||||
#OUTBOUND_MAX_FEE_BPS=60
|
||||
#OUTBOUND_MAX_FEE_EXTRA_SATS=100
|
||||
# If the back-end doesn't have adequate channel capacity, buy one from an LSP
|
||||
# Will execute when it costs less than 1% of balance and uses a trusted peer
|
||||
#BOOTSTRAP=1
|
||||
#SERVICE_FEE_BPS=60
|
||||
#ROUTING_FEE_LIMIT_BPS=50
|
||||
#SERVICE_FEE_FLOOR_SATS=10
|
||||
#ROUTING_FEE_FLOOR_SATS=5
|
||||
|
||||
|
||||
#LSP
|
||||
OLYMPUS_LSP_URL=https://lsps1.lnolymp.us/api/v1
|
||||
VOLTAGE_LSP_URL=https://lsp.voltageapi.com/api/v1
|
||||
FLASHSATS_LSP_URL=https://lsp.flashsats.xyz/lsp/channel
|
||||
LSP_CHANNEL_THRESHOLD=1000000
|
||||
LSP_MAX_FEE_BPS=100
|
||||
|
||||
#ROOT_FEES
|
||||
# Applied to either debits or credits and sent to an admin account
|
||||
# BPS are basis points, 100 BPS = 1%
|
||||
#INCOMING_CHAIN_FEE_ROOT_BPS=0
|
||||
#INCOMING_INVOICE_FEE_ROOT_BPS=0
|
||||
# Chain spends are currently unstable and thus disabled, do not use until further notice
|
||||
#OUTGOING_CHAIN_FEE_ROOT_BPS=60
|
||||
# Outgoing Invoice Fee must be >= Lightning Outbound Max Fee so admins don't incur losses on spends
|
||||
#OUTGOING_INVOICE_FEE_ROOT_BPS=60
|
||||
# Internal user fees bugged, do not use until further notice
|
||||
#TX_FEE_INTERNAL_ROOT_BPS=0 #applied to inter-application txns
|
||||
# Internal transaction fees (placeholder, bugged, do not use)
|
||||
#TX_FEE_INTERNAL_ROOT_BPS=0
|
||||
|
||||
#APP_FEES
|
||||
# An extra fee applied at the app level and sent to the application owner
|
||||
#INCOMING_INVOICE_FEE_USER_BPS=0
|
||||
#OUTGOING_INVOICE_FEE_USER_BPS=0
|
||||
#TX_FEE_INTERNAL_USER_BPS=0
|
||||
#LSP
|
||||
# If the back-end doesn't have adequate channel capacity, buy one from an LSP
|
||||
#OLYMPUS_LSP_URL=https://lsps1.lnolymp.us/api/v1
|
||||
#FLASHSATS_LSP_URL=https://lsp.flashsats.xyz/lsp/channel
|
||||
#LSP_CHANNEL_THRESHOLD=1000000
|
||||
#LSP_MAX_FEE_BPS=100
|
||||
|
||||
# Use trusted peer until LSP costs less than ~1% of balance or threshold
|
||||
#BOOTSTRAP=1
|
||||
|
||||
#NOSTR
|
||||
# Default relay may become rate-limited without a paid subscription
|
||||
|
|
@ -84,9 +88,9 @@ LSP_MAX_FEE_BPS=100
|
|||
|
||||
#LNURL
|
||||
# Optional
|
||||
# If undefined, LNURLs (including Lightning Address) will be disabled
|
||||
# To enable, add a reachable https endpoint for requests (or purchase a subscription)
|
||||
# You also need an SSL reverse proxy from the domain to this local host
|
||||
# If undefined, LNURLs will be disabled but wallets may still use bridge for Lightning Addresses
|
||||
# To enable, add a reachable https endpoint for requests
|
||||
# You also need an SSL reverse proxy from the domain to the Pub
|
||||
# Read more at https://docs.shock.network
|
||||
#SERVICE_URL=https://yourdomainhere.xyz
|
||||
#LNURL_META_TEXT=LNURL via Lightning.Pub
|
||||
|
|
@ -97,7 +101,7 @@ LSP_MAX_FEE_BPS=100
|
|||
# Read more at https://docs.shock.network
|
||||
#SUBSCRIBER=1
|
||||
|
||||
#DEV_OPTS
|
||||
#DEV
|
||||
#MOCK_LND=false
|
||||
#ALLOW_BALANCE_MIGRATION=false
|
||||
#MIGRATE_DB=false
|
||||
|
|
@ -109,10 +113,8 @@ LSP_MAX_FEE_BPS=100
|
|||
#SKIP_SANITY_CHECK=false
|
||||
# A read-only token that can be used with dashboard to view reports
|
||||
#METRICS_TOKEN=
|
||||
# Disable outbound payments aka honeypot mode
|
||||
#DISABLE_EXTERNAL_PAYMENTS=false
|
||||
#ALLOW_RESET_METRICS_STORAGES=false
|
||||
ALLOW_HTTP_UPGRADE=false
|
||||
#ALLOW_HTTP_UPGRADE=false
|
||||
|
||||
#WATCHDOG SECURITY
|
||||
# A last line of defense against 0-day drainage attacks
|
||||
|
|
@ -121,3 +123,5 @@ ALLOW_HTTP_UPGRADE=false
|
|||
# Increase this values to add a spending buffer for non-Pub services sharing LND
|
||||
# Max difference between users balance and LND balance at Pub startup
|
||||
#WATCHDOG_MAX_DIFF_SATS=0
|
||||
# Disable outbound payments aka honeypot mode
|
||||
#DISABLE_EXTERNAL_PAYMENTS=false
|
||||
480
package-lock.json
generated
480
package-lock.json
generated
|
|
@ -18,10 +18,13 @@
|
|||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/secp256k1": "^4.0.3",
|
||||
"@vulpemventures/secp256k1-zkp": "^3.2.1",
|
||||
"axios": "^1.9.0",
|
||||
"bech32": "^2.0.0",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"bitcoin-core": "^4.2.0",
|
||||
"bitcoinjs-lib": "^6.1.7",
|
||||
"boltz-core": "^3.0.0",
|
||||
"chai": "^4.3.7",
|
||||
"chai-string": "^1.5.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
|
|
@ -29,6 +32,7 @@
|
|||
"csv": "^6.3.8",
|
||||
"dotenv": "^16.4.5",
|
||||
"eccrypto": "^1.1.6",
|
||||
"ecpair": "^3.0.0",
|
||||
"express": "^4.21.2",
|
||||
"globby": "^13.1.2",
|
||||
"grpc-tools": "^1.12.4",
|
||||
|
|
@ -42,6 +46,7 @@
|
|||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.5",
|
||||
"secp256k1": "^5.0.1",
|
||||
"tiny-secp256k1": "^2.2.4",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-proto": "^1.131.2",
|
||||
"typeorm": "^0.3.26",
|
||||
|
|
@ -70,6 +75,12 @@
|
|||
"typescript": "5.5.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@boltz/bitcoin-ops": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@boltz/bitcoin-ops/-/bitcoin-ops-2.0.0.tgz",
|
||||
"integrity": "sha512-AM7vFNwSD7B4XI6yeRKccWbbD/lvwoFr8U3pqhzryBQo4uMkYe5V3/kMVnml4SNuxzyqdIFu4ur3TId02sC33A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.7.0.tgz",
|
||||
|
|
@ -443,6 +454,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@openzeppelin/contracts": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz",
|
||||
"integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
|
@ -1014,6 +1031,16 @@
|
|||
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/randombytes": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.3.tgz",
|
||||
"integrity": "sha512-+NRgihTfuURllWCiIAhm1wsJqzsocnqXM77V/CalsdJIYSRGEHMnritxh+6EsBklshC+clo1KgnN14qgSGeQdw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
|
|
@ -1114,6 +1141,18 @@
|
|||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/@vulpemventures/secp256k1-zkp": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vulpemventures/secp256k1-zkp/-/secp256k1-zkp-3.2.1.tgz",
|
||||
"integrity": "sha512-2U4nuNbXuUgMmxhuoILbRMoD2DE7KND3udk5cYilIS1MHvMtje9ywUm/zsI0g7d7x8g2A57xri+wvqCC/fCnJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"long": "^5.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
|
|
@ -1686,6 +1725,12 @@
|
|||
"license": "Apache-2.0",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/base-x": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz",
|
||||
"integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
|
|
@ -1766,12 +1811,54 @@
|
|||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bip174": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz",
|
||||
"integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bip174-liquid": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bip174-liquid/-/bip174-liquid-1.0.3.tgz",
|
||||
"integrity": "sha512-e69sC0Cq2tBJuhG2+wieXv40DN13YBR/wbIjZp4Mqwpar5vQm8Ldqijdd6N33XG7LtfvQi/zKm5fSzdPY/9mgw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bip32": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bip32/-/bip32-4.0.0.tgz",
|
||||
"integrity": "sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"@scure/base": "^1.1.1",
|
||||
"typeforce": "^1.11.5",
|
||||
"wif": "^2.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bip65": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bip65/-/bip65-1.0.3.tgz",
|
||||
"integrity": "sha512-RQ1nc7xtnLa5XltnCqkoR2zmhuz498RjMJwrLKQzOE049D1HUqnYfon7cVSbwS5UGm0/EQlC2CH+NY3MyITA4Q==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bip66": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
|
||||
"integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
|
|
@ -1794,6 +1881,71 @@
|
|||
"node": ">=7"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoinjs-lib": {
|
||||
"version": "6.1.7",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz",
|
||||
"integrity": "sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^2.1.1",
|
||||
"bs58check": "^3.0.1",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoinjs-lib/node_modules/base-x": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz",
|
||||
"integrity": "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bitcoinjs-lib/node_modules/bs58": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base-x": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoinjs-lib/node_modules/bs58check": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz",
|
||||
"integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bs58": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoinjs-lib/node_modules/varuint-bitcoin": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz",
|
||||
"integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bitset": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/bitset/-/bitset-5.2.3.tgz",
|
||||
"integrity": "sha512-uZ7++Z60MC9cZ+7YzQ1v9yPDydcjhmcMjGx2yoGTjjSXBoVMmTr2LCRbkpI19S9P/C75hhP7Bsakj+gVzVUDbQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
|
|
@ -1828,6 +1980,26 @@
|
|||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/blech32": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/blech32/-/blech32-1.1.2.tgz",
|
||||
"integrity": "sha512-C5qxzoF9KyX88X8Zz18cZ6BOeL0n5/Eg/cDot1frntkArRMwg1djNim5wA6QFWwu0lJ1LN8iiRMN4Lp2kZzdfA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"long": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/blech32/node_modules/long": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
|
|
@ -1873,6 +2045,42 @@
|
|||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boltz-core": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boltz-core/-/boltz-core-3.0.0.tgz",
|
||||
"integrity": "sha512-L0jzUnTIb/vir9k6yVlCkV8I29ZeBK7LWq3rrDcp/KP7bB0VvKR5JTceua8g2KBCBM5HOMVDEuPW3c9/82/Nmg==",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@boltz/bitcoin-ops": "^2.0.0",
|
||||
"@openzeppelin/contracts": "^5.2.0",
|
||||
"@vulpemventures/secp256k1-zkp": "^3.2.1",
|
||||
"bip32": "^4.0.0",
|
||||
"bip65": "^1.0.3",
|
||||
"bip66": "^2.0.0",
|
||||
"bitcoinjs-lib": "^6.1.7",
|
||||
"bn.js": "^5.2.1",
|
||||
"ecpair": "^3.0.0",
|
||||
"varuint-bitcoin": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"liquidjs-lib": "^6.0.2-liquid.37"
|
||||
}
|
||||
},
|
||||
"node_modules/boltz-core/node_modules/bip66": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bip66/-/bip66-2.0.0.tgz",
|
||||
"integrity": "sha512-kBG+hSpgvZBrkIm9dt5T1Hd/7xGCPEX2npoxAWZfsK1FvjgaxySEh2WizjyIstWXriKo9K9uJ4u0OnsyLDUPXQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boltz-core/node_modules/bn.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
|
||||
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
|
|
@ -1916,6 +2124,25 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bs58": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz",
|
||||
"integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base-x": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bs58check": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz",
|
||||
"integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bs58": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
|
|
@ -2206,7 +2433,6 @@
|
|||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz",
|
||||
"integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"safe-buffer": "^5.2.1"
|
||||
|
|
@ -2547,7 +2773,6 @@
|
|||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.1",
|
||||
"inherits": "^2.0.1",
|
||||
|
|
@ -2561,7 +2786,6 @@
|
|||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.3",
|
||||
"create-hash": "^1.1.0",
|
||||
|
|
@ -2982,6 +3206,52 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ecpair": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ecpair/-/ecpair-3.0.0.tgz",
|
||||
"integrity": "sha512-kf4JxjsRQoD4EBzpYjGAcR0t9i/4oAeRPtyCpKvSwyotgkc6oA4E4M0/e+kep7cXe+mgxAvoeh/jdgH9h5+Wxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uint8array-tools": "^0.0.8",
|
||||
"valibot": "^0.37.0",
|
||||
"wif": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecpair/node_modules/uint8array-tools": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz",
|
||||
"integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecpair/node_modules/valibot": {
|
||||
"version": "0.37.0",
|
||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-0.37.0.tgz",
|
||||
"integrity": "sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ecpair/node_modules/wif": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wif/-/wif-5.0.0.tgz",
|
||||
"integrity": "sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bs58check": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
|
@ -3777,7 +4047,6 @@
|
|||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
|
||||
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.6.0",
|
||||
|
|
@ -3792,7 +4061,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
|
@ -3807,7 +4075,6 @@
|
|||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
|
|
@ -4355,6 +4622,88 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/liquidjs-lib": {
|
||||
"version": "6.0.2-liquid.37",
|
||||
"resolved": "https://registry.npmjs.org/liquidjs-lib/-/liquidjs-lib-6.0.2-liquid.37.tgz",
|
||||
"integrity": "sha512-AOPwqg9wtLMNxfqOf8afC+bYYrs3VBsTq/Mq/iOe0F2X3CcbgiLeSAcvOY34jbnUre6U+zmuXKP9bWmxnOG08A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/randombytes": "^2.0.0",
|
||||
"bech32": "^2.0.0",
|
||||
"bip174-liquid": "^1.0.3",
|
||||
"bip66": "^1.1.0",
|
||||
"bitcoinjs-lib": "^6.0.2",
|
||||
"bitset": "^5.1.1",
|
||||
"blech32": "^1.0.1",
|
||||
"bs58check": "^2.0.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"ecpair": "^2.1.0",
|
||||
"slip77": "^0.2.0",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/liquidjs-lib/node_modules/base-x": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
|
||||
"integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/liquidjs-lib/node_modules/bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
||||
"integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"base-x": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/liquidjs-lib/node_modules/bs58check": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
|
||||
"integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bs58": "^4.0.0",
|
||||
"create-hash": "^1.1.0",
|
||||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/liquidjs-lib/node_modules/ecpair": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz",
|
||||
"integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0",
|
||||
"typeforce": "^1.18.0",
|
||||
"wif": "^2.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/liquidjs-lib/node_modules/varuint-bitcoin": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz",
|
||||
"integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
|
|
@ -4535,7 +4884,6 @@
|
|||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
|
|
@ -5796,6 +6144,16 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
|
|
@ -6012,7 +6370,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1"
|
||||
|
|
@ -6413,6 +6770,28 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/slip77": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/slip77/-/slip77-0.2.0.tgz",
|
||||
"integrity": "sha512-LQaxb1Hef10kU36qvk71tlSt5BWph7GM0j+t2n5zs169X4QfnNbb6xqKZ38X3ETzGmCQaVdBwr925HDagHra/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^13.9.1",
|
||||
"create-hmac": "^1.1.7",
|
||||
"typeforce": "^1.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/slip77/node_modules/@types/node": {
|
||||
"version": "13.13.52",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz",
|
||||
"integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
|
|
@ -6813,6 +7192,27 @@
|
|||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-secp256k1": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.4.tgz",
|
||||
"integrity": "sha512-FoDTcToPqZE454Q04hH9o2EhxWsm7pOSpicyHkgTwKhdKWdsTUuqfP5MLq3g+VjAtl2vSx6JpXGdwA2qpYkI0Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uint8array-tools": "0.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-secp256k1/node_modules/uint8array-tools": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz",
|
||||
"integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-buffer": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
|
||||
|
|
@ -7034,6 +7434,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/typeforce": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
|
||||
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typeorm": {
|
||||
"version": "0.3.26",
|
||||
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.26.tgz",
|
||||
|
|
@ -7387,6 +7793,24 @@
|
|||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/varuint-bitcoin": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz",
|
||||
"integrity": "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uint8array-tools": "^0.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/varuint-bitcoin/node_modules/uint8array-tools": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz",
|
||||
"integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
@ -7489,6 +7913,44 @@
|
|||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/wif": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz",
|
||||
"integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bs58check": "<3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wif/node_modules/base-x": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
|
||||
"integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/wif/node_modules/bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
||||
"integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base-x": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/wif/node_modules/bs58check": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
|
||||
"integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bs58": "^4.0.0",
|
||||
"create-hash": "^1.1.0",
|
||||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -36,10 +36,13 @@
|
|||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/secp256k1": "^4.0.3",
|
||||
"@vulpemventures/secp256k1-zkp": "^3.2.1",
|
||||
"axios": "^1.9.0",
|
||||
"bech32": "^2.0.0",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"bitcoin-core": "^4.2.0",
|
||||
"bitcoinjs-lib": "^6.1.7",
|
||||
"boltz-core": "^3.0.0",
|
||||
"chai": "^4.3.7",
|
||||
"chai-string": "^1.5.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
|
|
@ -47,6 +50,7 @@
|
|||
"csv": "^6.3.8",
|
||||
"dotenv": "^16.4.5",
|
||||
"eccrypto": "^1.1.6",
|
||||
"ecpair": "^3.0.0",
|
||||
"express": "^4.21.2",
|
||||
"globby": "^13.1.2",
|
||||
"grpc-tools": "^1.12.4",
|
||||
|
|
@ -60,6 +64,7 @@
|
|||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.5",
|
||||
"secp256k1": "^5.0.1",
|
||||
"tiny-secp256k1": "^2.2.4",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-proto": "^1.131.2",
|
||||
"typeorm": "^0.3.26",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
- GetAdminTransactionSwapQuotes
|
||||
- auth type: __Admin__
|
||||
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||
- output: [TransactionSwapQuoteList](#TransactionSwapQuoteList)
|
||||
|
||||
- GetAppsMetrics
|
||||
- auth type: __Metrics__
|
||||
- input: [AppsMetricsRequest](#AppsMetricsRequest)
|
||||
|
|
@ -198,6 +203,11 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [SingleMetricReq](#SingleMetricReq)
|
||||
- output: [UsageMetricTlv](#UsageMetricTlv)
|
||||
|
||||
- GetTransactionSwapQuotes
|
||||
- auth type: __User__
|
||||
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||
- output: [TransactionSwapQuoteList](#TransactionSwapQuoteList)
|
||||
|
||||
- GetUsageMetrics
|
||||
- auth type: __Metrics__
|
||||
- input: [LatestUsageMetricReq](#LatestUsageMetricReq)
|
||||
|
|
@ -233,11 +243,21 @@ 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
|
||||
- auth type: __Admin__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
|
||||
- ListChannels
|
||||
- auth type: __Admin__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [LndChannels](#LndChannels)
|
||||
|
||||
- ListSwaps
|
||||
- auth type: __User__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
|
||||
- LndGetInfo
|
||||
- auth type: __Admin__
|
||||
- input: [LndGetInfoRequest](#LndGetInfoRequest)
|
||||
|
|
@ -270,6 +290,11 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [PayAddressRequest](#PayAddressRequest)
|
||||
- output: [PayAddressResponse](#PayAddressResponse)
|
||||
|
||||
- PayAdminTransactionSwap
|
||||
- auth type: __Admin__
|
||||
- input: [PayAdminTransactionSwapRequest](#PayAdminTransactionSwapRequest)
|
||||
- output: [AdminSwapResponse](#AdminSwapResponse)
|
||||
|
||||
- PayInvoice
|
||||
- auth type: __User__
|
||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
||||
|
|
@ -515,6 +540,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
|
||||
|
||||
- GetAdminTransactionSwapQuotes
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/transaction/quote__
|
||||
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||
- output: [TransactionSwapQuoteList](#TransactionSwapQuoteList)
|
||||
|
||||
- GetApp
|
||||
- auth type: __App__
|
||||
- http method: __post__
|
||||
|
|
@ -708,6 +740,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [SingleMetricReq](#SingleMetricReq)
|
||||
- output: [UsageMetricTlv](#UsageMetricTlv)
|
||||
|
||||
- GetTransactionSwapQuotes
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
- http route: __/api/user/swap/quote__
|
||||
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||
- output: [TransactionSwapQuoteList](#TransactionSwapQuoteList)
|
||||
|
||||
- GetUsageMetrics
|
||||
- auth type: __Metrics__
|
||||
- http method: __post__
|
||||
|
|
@ -795,6 +834,13 @@ 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
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/list__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
|
||||
- ListChannels
|
||||
- auth type: __Admin__
|
||||
- http method: __get__
|
||||
|
|
@ -802,6 +848,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- This methods has an __empty__ __request__ body
|
||||
- output: [LndChannels](#LndChannels)
|
||||
|
||||
- ListSwaps
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
- http route: __/api/user/swap/list__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
|
||||
- LndGetInfo
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
|
|
@ -846,6 +899,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- input: [PayAddressRequest](#PayAddressRequest)
|
||||
- output: [PayAddressResponse](#PayAddressResponse)
|
||||
|
||||
- PayAdminTransactionSwap
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
- http route: __/api/admin/swap/transaction/pay__
|
||||
- input: [PayAdminTransactionSwapRequest](#PayAdminTransactionSwapRequest)
|
||||
- output: [AdminSwapResponse](#AdminSwapResponse)
|
||||
|
||||
- PayAppUserInvoice
|
||||
- auth type: __App__
|
||||
- http method: __post__
|
||||
|
|
@ -1038,6 +1098,10 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### AdminSwapResponse
|
||||
- __network_fee__: _number_
|
||||
- __tx_id__: _string_
|
||||
|
||||
### AppMetrics
|
||||
- __app__: _[Application](#Application)_
|
||||
- __available__: _number_
|
||||
|
|
@ -1092,6 +1156,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __nostr_pub__: _string_
|
||||
- __user_identifier__: _string_
|
||||
|
||||
### BeaconData
|
||||
- __avatarUrl__: _string_ *this field is optional
|
||||
- __fees__: _[CumulativeFees](#CumulativeFees)_ *this field is optional
|
||||
- __name__: _string_
|
||||
- __nextRelay__: _string_ *this field is optional
|
||||
- __type__: _string_
|
||||
|
||||
### BundleData
|
||||
- __available_chunks__: ARRAY of: _number_
|
||||
- __base_64_data__: ARRAY of: _string_
|
||||
|
|
@ -1137,6 +1208,10 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
### CreateOneTimeInviteLinkResponse
|
||||
- __invitation_link__: _string_
|
||||
|
||||
### CumulativeFees
|
||||
- __serviceFeeBps__: _number_
|
||||
- __serviceFeeFloor__: _number_
|
||||
|
||||
### DebitAuthorization
|
||||
- __authorized__: _boolean_
|
||||
- __debit_id__: _string_
|
||||
|
|
@ -1275,6 +1350,7 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __request_id__: _string_
|
||||
|
||||
### LiveUserOperation
|
||||
- __latest_balance__: _number_
|
||||
- __operation__: _[UserOperation](#UserOperation)_
|
||||
|
||||
### LndChannels
|
||||
|
|
@ -1448,8 +1524,9 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
|
||||
### PayAddressRequest
|
||||
- __address__: _string_
|
||||
- __amoutSats__: _number_
|
||||
- __amountSats__: _number_
|
||||
- __satsPerVByte__: _number_
|
||||
- __swap_operation_id__: _string_ *this field is optional
|
||||
|
||||
### PayAddressResponse
|
||||
- __network_fee__: _number_
|
||||
|
|
@ -1457,19 +1534,26 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __service_fee__: _number_
|
||||
- __txId__: _string_
|
||||
|
||||
### PayAdminTransactionSwapRequest
|
||||
- __address__: _string_
|
||||
- __swap_operation_id__: _string_
|
||||
|
||||
### PayAppUserInvoiceRequest
|
||||
- __amount__: _number_
|
||||
- __debit_npub__: _string_ *this field is optional
|
||||
- __expected_fees__: _[CumulativeFees](#CumulativeFees)_ *this field is optional
|
||||
- __invoice__: _string_
|
||||
- __user_identifier__: _string_
|
||||
|
||||
### PayInvoiceRequest
|
||||
- __amount__: _number_
|
||||
- __debit_npub__: _string_ *this field is optional
|
||||
- __expected_fees__: _[CumulativeFees](#CumulativeFees)_ *this field is optional
|
||||
- __invoice__: _string_
|
||||
|
||||
### PayInvoiceResponse
|
||||
- __amount_paid__: _number_
|
||||
- __latest_balance__: _number_
|
||||
- __network_fee__: _number_
|
||||
- __operation_id__: _string_
|
||||
- __preimage__: _string_
|
||||
|
|
@ -1480,7 +1564,9 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
|
||||
### PaymentState
|
||||
- __amount__: _number_
|
||||
- __internal__: _boolean_
|
||||
- __network_fee__: _number_
|
||||
- __operation_id__: _string_
|
||||
- __paid_at_unix__: _number_
|
||||
- __service_fee__: _number_
|
||||
|
||||
|
|
@ -1554,6 +1640,31 @@ 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_
|
||||
- __service_fee_sats__: _number_
|
||||
- __service_url__: _string_
|
||||
- __swap_fee_sats__: _number_
|
||||
- __swap_operation_id__: _string_
|
||||
- __transaction_amount_sats__: _number_
|
||||
|
||||
### TransactionSwapQuoteList
|
||||
- __quotes__: ARRAY of: _[TransactionSwapQuote](#TransactionSwapQuote)_
|
||||
|
||||
### TransactionSwapRequest
|
||||
- __transaction_amount_sats__: _number_
|
||||
|
||||
### UpdateChannelPolicyRequest
|
||||
- __policy__: _[ChannelPolicy](#ChannelPolicy)_
|
||||
- __update__: _[UpdateChannelPolicyRequest_update](#UpdateChannelPolicyRequest_update)_
|
||||
|
|
|
|||
|
|
@ -66,81 +66,86 @@ type Client struct {
|
|||
BanDebit func(req DebitOperation) error
|
||||
BanUser func(req BanUserRequest) (*BanUserResponse, error)
|
||||
// batching method: BatchUser not implemented
|
||||
CloseChannel func(req CloseChannelRequest) (*CloseChannelResponse, error)
|
||||
CreateOneTimeInviteLink func(req CreateOneTimeInviteLinkRequest) (*CreateOneTimeInviteLinkResponse, error)
|
||||
DecodeInvoice func(req DecodeInvoiceRequest) (*DecodeInvoiceResponse, error)
|
||||
DeleteUserOffer func(req OfferId) error
|
||||
EditDebit func(req DebitAuthorizationRequest) error
|
||||
EncryptionExchange func(req EncryptionExchangeRequest) error
|
||||
EnrollAdminToken func(req EnrollAdminTokenRequest) error
|
||||
EnrollMessagingToken func(req MessagingToken) error
|
||||
GetApp func() (*Application, error)
|
||||
GetAppUser func(req GetAppUserRequest) (*AppUser, error)
|
||||
GetAppUserLNURLInfo func(req GetAppUserLNURLInfoRequest) (*LnurlPayInfoResponse, error)
|
||||
GetAppsMetrics func(req AppsMetricsRequest) (*AppsMetrics, error)
|
||||
GetBundleMetrics func(req LatestBundleMetricReq) (*BundleMetrics, error)
|
||||
GetDebitAuthorizations func() (*DebitAuthorizations, error)
|
||||
GetErrorStats func() (*ErrorStats, error)
|
||||
GetHttpCreds func() (*HttpCreds, error)
|
||||
GetInviteLinkState func(req GetInviteTokenStateRequest) (*GetInviteTokenStateResponse, error)
|
||||
GetLNURLChannelLink func() (*LnurlLinkResponse, error)
|
||||
GetLiveDebitRequests func() (*LiveDebitRequest, error)
|
||||
GetLiveManageRequests func() (*LiveManageRequest, error)
|
||||
GetLiveUserOperations func() (*LiveUserOperation, error)
|
||||
GetLndForwardingMetrics func(req LndMetricsRequest) (*LndForwardingMetrics, error)
|
||||
GetLndMetrics func(req LndMetricsRequest) (*LndMetrics, error)
|
||||
GetLnurlPayInfo func(query GetLnurlPayInfo_Query) (*LnurlPayInfoResponse, error)
|
||||
GetLnurlPayLink func() (*LnurlLinkResponse, error)
|
||||
GetLnurlWithdrawInfo func(query GetLnurlWithdrawInfo_Query) (*LnurlWithdrawInfoResponse, error)
|
||||
GetLnurlWithdrawLink func() (*LnurlLinkResponse, error)
|
||||
GetManageAuthorizations func() (*ManageAuthorizations, error)
|
||||
GetMigrationUpdate func() (*MigrationUpdate, error)
|
||||
GetNPubLinkingState func(req GetNPubLinking) (*NPubLinking, error)
|
||||
GetPaymentState func(req GetPaymentStateRequest) (*PaymentState, error)
|
||||
GetProvidersDisruption func() (*ProvidersDisruption, error)
|
||||
GetSeed func() (*LndSeed, error)
|
||||
GetSingleBundleMetrics func(req SingleMetricReq) (*BundleData, error)
|
||||
GetSingleUsageMetrics func(req SingleMetricReq) (*UsageMetricTlv, error)
|
||||
GetUsageMetrics func(req LatestUsageMetricReq) (*UsageMetrics, error)
|
||||
GetUserInfo func() (*UserInfo, error)
|
||||
GetUserOffer func(req OfferId) (*OfferConfig, error)
|
||||
GetUserOfferInvoices func(req GetUserOfferInvoicesReq) (*OfferInvoices, error)
|
||||
GetUserOffers func() (*UserOffers, error)
|
||||
GetUserOperations func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error)
|
||||
HandleLnurlAddress func(routeParams HandleLnurlAddress_RouteParams) (*LnurlPayInfoResponse, error)
|
||||
HandleLnurlPay func(query HandleLnurlPay_Query) (*HandleLnurlPayResponse, error)
|
||||
HandleLnurlWithdraw func(query HandleLnurlWithdraw_Query) error
|
||||
Health func() error
|
||||
LinkNPubThroughToken func(req LinkNPubThroughTokenRequest) error
|
||||
ListChannels func() (*LndChannels, 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)
|
||||
PayAppUserInvoice func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error)
|
||||
PayInvoice func(req PayInvoiceRequest) (*PayInvoiceResponse, error)
|
||||
PingSubProcesses func() error
|
||||
RequestNPubLinkingToken func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error)
|
||||
ResetDebit func(req DebitOperation) error
|
||||
ResetManage func(req ManageOperation) error
|
||||
ResetMetricsStorages func() error
|
||||
ResetNPubLinkingToken func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error)
|
||||
RespondToDebit func(req DebitResponse) error
|
||||
SendAppUserToAppPayment func(req SendAppUserToAppPaymentRequest) error
|
||||
SendAppUserToAppUserPayment func(req SendAppUserToAppUserPaymentRequest) error
|
||||
SetMockAppBalance func(req SetMockAppBalanceRequest) error
|
||||
SetMockAppUserBalance func(req SetMockAppUserBalanceRequest) error
|
||||
SetMockInvoiceAsPaid func(req SetMockInvoiceAsPaidRequest) error
|
||||
SubToWebRtcCandidates func() (*WebRtcCandidate, error)
|
||||
SubmitWebRtcMessage func(req WebRtcMessage) (*WebRtcAnswer, error)
|
||||
UpdateCallbackUrl func(req CallbackUrl) (*CallbackUrl, error)
|
||||
UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error
|
||||
UpdateUserOffer func(req OfferConfig) error
|
||||
UseInviteLink func(req UseInviteLinkRequest) error
|
||||
UserHealth func() (*UserHealthState, error)
|
||||
ZipMetricsStorages func() (*ZippedMetrics, error)
|
||||
CloseChannel func(req CloseChannelRequest) (*CloseChannelResponse, error)
|
||||
CreateOneTimeInviteLink func(req CreateOneTimeInviteLinkRequest) (*CreateOneTimeInviteLinkResponse, error)
|
||||
DecodeInvoice func(req DecodeInvoiceRequest) (*DecodeInvoiceResponse, error)
|
||||
DeleteUserOffer func(req OfferId) error
|
||||
EditDebit func(req DebitAuthorizationRequest) error
|
||||
EncryptionExchange func(req EncryptionExchangeRequest) error
|
||||
EnrollAdminToken func(req EnrollAdminTokenRequest) error
|
||||
EnrollMessagingToken func(req MessagingToken) error
|
||||
GetAdminTransactionSwapQuotes func(req TransactionSwapRequest) (*TransactionSwapQuoteList, error)
|
||||
GetApp func() (*Application, error)
|
||||
GetAppUser func(req GetAppUserRequest) (*AppUser, error)
|
||||
GetAppUserLNURLInfo func(req GetAppUserLNURLInfoRequest) (*LnurlPayInfoResponse, error)
|
||||
GetAppsMetrics func(req AppsMetricsRequest) (*AppsMetrics, error)
|
||||
GetBundleMetrics func(req LatestBundleMetricReq) (*BundleMetrics, error)
|
||||
GetDebitAuthorizations func() (*DebitAuthorizations, error)
|
||||
GetErrorStats func() (*ErrorStats, error)
|
||||
GetHttpCreds func() (*HttpCreds, error)
|
||||
GetInviteLinkState func(req GetInviteTokenStateRequest) (*GetInviteTokenStateResponse, error)
|
||||
GetLNURLChannelLink func() (*LnurlLinkResponse, error)
|
||||
GetLiveDebitRequests func() (*LiveDebitRequest, error)
|
||||
GetLiveManageRequests func() (*LiveManageRequest, error)
|
||||
GetLiveUserOperations func() (*LiveUserOperation, error)
|
||||
GetLndForwardingMetrics func(req LndMetricsRequest) (*LndForwardingMetrics, error)
|
||||
GetLndMetrics func(req LndMetricsRequest) (*LndMetrics, error)
|
||||
GetLnurlPayInfo func(query GetLnurlPayInfo_Query) (*LnurlPayInfoResponse, error)
|
||||
GetLnurlPayLink func() (*LnurlLinkResponse, error)
|
||||
GetLnurlWithdrawInfo func(query GetLnurlWithdrawInfo_Query) (*LnurlWithdrawInfoResponse, error)
|
||||
GetLnurlWithdrawLink func() (*LnurlLinkResponse, error)
|
||||
GetManageAuthorizations func() (*ManageAuthorizations, error)
|
||||
GetMigrationUpdate func() (*MigrationUpdate, error)
|
||||
GetNPubLinkingState func(req GetNPubLinking) (*NPubLinking, error)
|
||||
GetPaymentState func(req GetPaymentStateRequest) (*PaymentState, error)
|
||||
GetProvidersDisruption func() (*ProvidersDisruption, error)
|
||||
GetSeed func() (*LndSeed, error)
|
||||
GetSingleBundleMetrics func(req SingleMetricReq) (*BundleData, error)
|
||||
GetSingleUsageMetrics func(req SingleMetricReq) (*UsageMetricTlv, error)
|
||||
GetTransactionSwapQuotes func(req TransactionSwapRequest) (*TransactionSwapQuoteList, error)
|
||||
GetUsageMetrics func(req LatestUsageMetricReq) (*UsageMetrics, error)
|
||||
GetUserInfo func() (*UserInfo, error)
|
||||
GetUserOffer func(req OfferId) (*OfferConfig, error)
|
||||
GetUserOfferInvoices func(req GetUserOfferInvoicesReq) (*OfferInvoices, error)
|
||||
GetUserOffers func() (*UserOffers, error)
|
||||
GetUserOperations func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error)
|
||||
HandleLnurlAddress func(routeParams HandleLnurlAddress_RouteParams) (*LnurlPayInfoResponse, error)
|
||||
HandleLnurlPay func(query HandleLnurlPay_Query) (*HandleLnurlPayResponse, error)
|
||||
HandleLnurlWithdraw func(query HandleLnurlWithdraw_Query) error
|
||||
Health func() error
|
||||
LinkNPubThroughToken func(req LinkNPubThroughTokenRequest) error
|
||||
ListAdminSwaps func() (*SwapsList, error)
|
||||
ListChannels func() (*LndChannels, error)
|
||||
ListSwaps func() (*SwapsList, error)
|
||||
LndGetInfo func(req LndGetInfoRequest) (*LndGetInfoResponse, error)
|
||||
NewAddress func(req NewAddressRequest) (*NewAddressResponse, error)
|
||||
NewInvoice func(req NewInvoiceRequest) (*NewInvoiceResponse, error)
|
||||
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)
|
||||
PayAppUserInvoice func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error)
|
||||
PayInvoice func(req PayInvoiceRequest) (*PayInvoiceResponse, error)
|
||||
PingSubProcesses func() error
|
||||
RequestNPubLinkingToken func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error)
|
||||
ResetDebit func(req DebitOperation) error
|
||||
ResetManage func(req ManageOperation) error
|
||||
ResetMetricsStorages func() error
|
||||
ResetNPubLinkingToken func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error)
|
||||
RespondToDebit func(req DebitResponse) error
|
||||
SendAppUserToAppPayment func(req SendAppUserToAppPaymentRequest) error
|
||||
SendAppUserToAppUserPayment func(req SendAppUserToAppUserPaymentRequest) error
|
||||
SetMockAppBalance func(req SetMockAppBalanceRequest) error
|
||||
SetMockAppUserBalance func(req SetMockAppUserBalanceRequest) error
|
||||
SetMockInvoiceAsPaid func(req SetMockInvoiceAsPaidRequest) error
|
||||
SubToWebRtcCandidates func() (*WebRtcCandidate, error)
|
||||
SubmitWebRtcMessage func(req WebRtcMessage) (*WebRtcAnswer, error)
|
||||
UpdateCallbackUrl func(req CallbackUrl) (*CallbackUrl, error)
|
||||
UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error
|
||||
UpdateUserOffer func(req OfferConfig) error
|
||||
UseInviteLink func(req UseInviteLinkRequest) error
|
||||
UserHealth func() (*UserHealthState, error)
|
||||
ZipMetricsStorages func() (*ZippedMetrics, error)
|
||||
}
|
||||
|
||||
func NewClient(params ClientParams) *Client {
|
||||
|
|
@ -662,6 +667,35 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
GetAdminTransactionSwapQuotes: func(req TransactionSwapRequest) (*TransactionSwapQuoteList, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/transaction/quote"
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := ResultError{}
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := TransactionSwapQuoteList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
GetApp: func() (*Application, error) {
|
||||
auth, err := params.RetrieveAppAuth()
|
||||
if err != nil {
|
||||
|
|
@ -1285,6 +1319,35 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return &res, nil
|
||||
},
|
||||
GetTransactionSwapQuotes: func(req TransactionSwapRequest) (*TransactionSwapQuoteList, error) {
|
||||
auth, err := params.RetrieveUserAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/user/swap/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 := TransactionSwapQuoteList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
GetUsageMetrics: func(req LatestUsageMetricReq) (*UsageMetrics, error) {
|
||||
auth, err := params.RetrieveMetricsAuth()
|
||||
if err != nil {
|
||||
|
|
@ -1580,6 +1643,32 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
ListAdminSwaps: func() (*SwapsList, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/list"
|
||||
body := []byte{}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := ResultError{}
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := SwapsList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
ListChannels: func() (*LndChannels, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
|
|
@ -1602,6 +1691,32 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return &res, nil
|
||||
},
|
||||
ListSwaps: func() (*SwapsList, error) {
|
||||
auth, err := params.RetrieveUserAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/user/swap/list"
|
||||
body := []byte{}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := ResultError{}
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := SwapsList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
LndGetInfo: func(req LndGetInfoRequest) (*LndGetInfoResponse, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
|
|
@ -1777,6 +1892,35 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return &res, nil
|
||||
},
|
||||
PayAdminTransactionSwap: func(req PayAdminTransactionSwapRequest) (*AdminSwapResponse, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/admin/swap/transaction/pay"
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := ResultError{}
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := AdminSwapResponse{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
PayAppUserInvoice: func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error) {
|
||||
auth, err := params.RetrieveAppAuth()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,10 @@ type AddProductRequest struct {
|
|||
Name string `json:"name"`
|
||||
Price_sats int64 `json:"price_sats"`
|
||||
}
|
||||
type AdminSwapResponse struct {
|
||||
Network_fee int64 `json:"network_fee"`
|
||||
Tx_id string `json:"tx_id"`
|
||||
}
|
||||
type AppMetrics struct {
|
||||
App *Application `json:"app"`
|
||||
Available int64 `json:"available"`
|
||||
|
|
@ -177,6 +181,13 @@ type BannedAppUser struct {
|
|||
Nostr_pub string `json:"nostr_pub"`
|
||||
User_identifier string `json:"user_identifier"`
|
||||
}
|
||||
type BeaconData struct {
|
||||
Avatarurl string `json:"avatarUrl"`
|
||||
Fees *CumulativeFees `json:"fees"`
|
||||
Name string `json:"name"`
|
||||
Nextrelay string `json:"nextRelay"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type BundleData struct {
|
||||
Available_chunks []int64 `json:"available_chunks"`
|
||||
Base_64_data []string `json:"base_64_data"`
|
||||
|
|
@ -222,6 +233,10 @@ type CreateOneTimeInviteLinkRequest struct {
|
|||
type CreateOneTimeInviteLinkResponse struct {
|
||||
Invitation_link string `json:"invitation_link"`
|
||||
}
|
||||
type CumulativeFees struct {
|
||||
Servicefeebps int64 `json:"serviceFeeBps"`
|
||||
Servicefeefloor int64 `json:"serviceFeeFloor"`
|
||||
}
|
||||
type DebitAuthorization struct {
|
||||
Authorized bool `json:"authorized"`
|
||||
Debit_id string `json:"debit_id"`
|
||||
|
|
@ -360,7 +375,8 @@ type LiveManageRequest struct {
|
|||
Request_id string `json:"request_id"`
|
||||
}
|
||||
type LiveUserOperation struct {
|
||||
Operation *UserOperation `json:"operation"`
|
||||
Latest_balance int64 `json:"latest_balance"`
|
||||
Operation *UserOperation `json:"operation"`
|
||||
}
|
||||
type LndChannels struct {
|
||||
Open_channels []OpenChannel `json:"open_channels"`
|
||||
|
|
@ -532,9 +548,10 @@ type OperationsCursor struct {
|
|||
Ts int64 `json:"ts"`
|
||||
}
|
||||
type PayAddressRequest struct {
|
||||
Address string `json:"address"`
|
||||
Amoutsats int64 `json:"amoutSats"`
|
||||
Satspervbyte int64 `json:"satsPerVByte"`
|
||||
Address string `json:"address"`
|
||||
Amountsats int64 `json:"amountSats"`
|
||||
Satspervbyte int64 `json:"satsPerVByte"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
}
|
||||
type PayAddressResponse struct {
|
||||
Network_fee int64 `json:"network_fee"`
|
||||
|
|
@ -542,32 +559,41 @@ type PayAddressResponse struct {
|
|||
Service_fee int64 `json:"service_fee"`
|
||||
Txid string `json:"txId"`
|
||||
}
|
||||
type PayAdminTransactionSwapRequest struct {
|
||||
Address string `json:"address"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
}
|
||||
type PayAppUserInvoiceRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Debit_npub string `json:"debit_npub"`
|
||||
Invoice string `json:"invoice"`
|
||||
User_identifier string `json:"user_identifier"`
|
||||
Amount int64 `json:"amount"`
|
||||
Debit_npub string `json:"debit_npub"`
|
||||
Expected_fees *CumulativeFees `json:"expected_fees"`
|
||||
Invoice string `json:"invoice"`
|
||||
User_identifier string `json:"user_identifier"`
|
||||
}
|
||||
type PayInvoiceRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Debit_npub string `json:"debit_npub"`
|
||||
Invoice string `json:"invoice"`
|
||||
Amount int64 `json:"amount"`
|
||||
Debit_npub string `json:"debit_npub"`
|
||||
Expected_fees *CumulativeFees `json:"expected_fees"`
|
||||
Invoice string `json:"invoice"`
|
||||
}
|
||||
type PayInvoiceResponse struct {
|
||||
Amount_paid int64 `json:"amount_paid"`
|
||||
Network_fee int64 `json:"network_fee"`
|
||||
Operation_id string `json:"operation_id"`
|
||||
Preimage string `json:"preimage"`
|
||||
Service_fee int64 `json:"service_fee"`
|
||||
Amount_paid int64 `json:"amount_paid"`
|
||||
Latest_balance int64 `json:"latest_balance"`
|
||||
Network_fee int64 `json:"network_fee"`
|
||||
Operation_id string `json:"operation_id"`
|
||||
Preimage string `json:"preimage"`
|
||||
Service_fee int64 `json:"service_fee"`
|
||||
}
|
||||
type PayerData struct {
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
type PaymentState struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Network_fee int64 `json:"network_fee"`
|
||||
Paid_at_unix int64 `json:"paid_at_unix"`
|
||||
Service_fee int64 `json:"service_fee"`
|
||||
Amount int64 `json:"amount"`
|
||||
Internal bool `json:"internal"`
|
||||
Network_fee int64 `json:"network_fee"`
|
||||
Operation_id string `json:"operation_id"`
|
||||
Paid_at_unix int64 `json:"paid_at_unix"`
|
||||
Service_fee int64 `json:"service_fee"`
|
||||
}
|
||||
type Product struct {
|
||||
Id string `json:"id"`
|
||||
|
|
@ -639,6 +665,31 @@ 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"`
|
||||
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"`
|
||||
}
|
||||
type TransactionSwapQuoteList struct {
|
||||
Quotes []TransactionSwapQuote `json:"quotes"`
|
||||
}
|
||||
type TransactionSwapRequest struct {
|
||||
Transaction_amount_sats int64 `json:"transaction_amount_sats"`
|
||||
}
|
||||
type UpdateChannelPolicyRequest struct {
|
||||
Policy *ChannelPolicy `json:"policy"`
|
||||
Update *UpdateChannelPolicyRequest_update `json:"update"`
|
||||
|
|
|
|||
|
|
@ -477,6 +477,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetTransactionSwapQuotes':
|
||||
if (!methods.GetTransactionSwapQuotes) {
|
||||
throw new Error('method GetTransactionSwapQuotes not found' )
|
||||
} else {
|
||||
const error = Types.TransactionSwapRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.GetTransactionSwapQuotes({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
if (!methods.GetUserInfo) {
|
||||
throw new Error('method GetUserInfo not found' )
|
||||
|
|
@ -533,6 +545,16 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'ListSwaps':
|
||||
if (!methods.ListSwaps) {
|
||||
throw new Error('method ListSwaps not found' )
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.ListSwaps({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewAddress':
|
||||
if (!methods.NewAddress) {
|
||||
throw new Error('method NewAddress not found' )
|
||||
|
|
@ -847,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.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}
|
||||
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.GetAdminTransactionSwapQuotes) throw new Error('method: GetAdminTransactionSwapQuotes is not implemented')
|
||||
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
|
||||
authCtx = authContext
|
||||
stats.guard = process.hrtime.bigint()
|
||||
const request = req.body
|
||||
const error = Types.TransactionSwapRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
const response = await methods.GetAdminTransactionSwapQuotes({rpcName:'GetAdminTransactionSwapQuotes', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res.json({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.GetApp) throw new Error('method: GetApp is not implemented')
|
||||
app.post('/api/app/get', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetApp', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
@ -1317,6 +1361,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.GetTransactionSwapQuotes) throw new Error('method: GetTransactionSwapQuotes is not implemented')
|
||||
app.post('/api/user/swap/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 = {}
|
||||
try {
|
||||
if (!methods.GetTransactionSwapQuotes) throw new Error('method: GetTransactionSwapQuotes is not implemented')
|
||||
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
|
||||
authCtx = authContext
|
||||
stats.guard = process.hrtime.bigint()
|
||||
const request = req.body
|
||||
const error = Types.TransactionSwapRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
const response = await methods.GetTransactionSwapQuotes({rpcName:'GetTransactionSwapQuotes', 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.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
|
||||
app.post('/api/reports/usage', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'GetUsageMetrics', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
@ -1541,6 +1607,25 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.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}
|
||||
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')
|
||||
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 })
|
||||
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.ListChannels) throw new Error('method: ListChannels is not implemented')
|
||||
app.get('/api/admin/channels', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'ListChannels', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
@ -1560,6 +1645,25 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.ListSwaps) throw new Error('method: ListSwaps is not implemented')
|
||||
app.post('/api/user/swap/list', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'ListSwaps', batch: false, nostr: false, batchSize: 0}
|
||||
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
try {
|
||||
if (!methods.ListSwaps) throw new Error('method: ListSwaps is not implemented')
|
||||
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
|
||||
authCtx = authContext
|
||||
stats.guard = process.hrtime.bigint()
|
||||
stats.validate = stats.guard
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
const response = await methods.ListSwaps({rpcName:'ListSwaps', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res.json({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
|
||||
app.post('/api/admin/lnd/getinfo', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'LndGetInfo', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
@ -1689,6 +1793,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.PayAdminTransactionSwap) throw new Error('method: PayAdminTransactionSwap is not implemented')
|
||||
app.post('/api/admin/swap/transaction/pay', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'PayAdminTransactionSwap', batch: false, nostr: false, batchSize: 0}
|
||||
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
try {
|
||||
if (!methods.PayAdminTransactionSwap) throw new Error('method: PayAdminTransactionSwap is not implemented')
|
||||
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
|
||||
authCtx = authContext
|
||||
stats.guard = process.hrtime.bigint()
|
||||
const request = req.body
|
||||
const error = Types.PayAdminTransactionSwapRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
const response = await methods.PayAdminTransactionSwap({rpcName:'PayAdminTransactionSwap', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res.json({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.PayAppUserInvoice) throw new Error('method: PayAppUserInvoice is not implemented')
|
||||
app.post('/api/app/invoice/pay', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'PayAppUserInvoice', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
|
|||
|
|
@ -273,6 +273,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
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')
|
||||
let finalRoute = '/api/admin/swap/transaction/quote'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.TransactionSwapQuoteListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||
|
|
@ -603,6 +617,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
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'
|
||||
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.TransactionSwapQuoteListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUsageMetrics: async (request: Types.LatestUsageMetricReq): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
|
||||
const auth = await params.retrieveMetricsAuth()
|
||||
if (auth === null) throw new Error('retrieveMetricsAuth() returned null')
|
||||
|
|
@ -753,6 +781,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListAdminSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/list'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.SwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListChannels: async (): Promise<ResultError | ({ status: 'OK' }& Types.LndChannels)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
|
|
@ -767,6 +809,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
const auth = await params.retrieveUserAuth()
|
||||
if (auth === null) throw new Error('retrieveUserAuth() returned null')
|
||||
let finalRoute = '/api/user/swap/list'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.SwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
LndGetInfo: async (request: Types.LndGetInfoRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndGetInfoResponse)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
|
|
@ -853,6 +909,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAdminTransactionSwap: async (request: Types.PayAdminTransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminSwapResponse)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/swap/transaction/pay'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.AdminSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||
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' }
|
||||
},
|
||||
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')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetAdminTransactionSwapQuotes',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.TransactionSwapQuoteListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppsMetrics)> => {
|
||||
const auth = await params.retrieveNostrMetricsAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||
|
|
@ -536,6 +551,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetTransactionSwapQuotes: async (request: Types.TransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.TransactionSwapQuoteList)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetTransactionSwapQuotes',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.TransactionSwapQuoteListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUsageMetrics: async (request: Types.LatestUsageMetricReq): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
|
||||
const auth = await params.retrieveNostrMetricsAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||
|
|
@ -636,6 +666,20 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListAdminSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
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 })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.SwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListChannels: async (): Promise<ResultError | ({ status: 'OK' }& Types.LndChannels)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
|
|
@ -650,6 +694,20 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'ListSwaps',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.SwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
LndGetInfo: async (request: Types.LndGetInfoRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndGetInfoResponse)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
|
|
@ -740,6 +798,21 @@ 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)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayAdminTransactionSwap',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.AdminSwapResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayInvoice: async (request: Types.PayInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -359,6 +359,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetTransactionSwapQuotes':
|
||||
if (!methods.GetTransactionSwapQuotes) {
|
||||
throw new Error('method not defined: GetTransactionSwapQuotes')
|
||||
} else {
|
||||
const error = Types.TransactionSwapRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.GetTransactionSwapQuotes({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
if (!methods.GetUserInfo) {
|
||||
throw new Error('method not defined: GetUserInfo')
|
||||
|
|
@ -415,6 +427,16 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'ListSwaps':
|
||||
if (!methods.ListSwaps) {
|
||||
throw new Error('method not defined: ListSwaps')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.ListSwaps({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewAddress':
|
||||
if (!methods.NewAddress) {
|
||||
throw new Error('method not defined: NewAddress')
|
||||
|
|
@ -665,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 'GetAdminTransactionSwapQuotes':
|
||||
try {
|
||||
if (!methods.GetAdminTransactionSwapQuotes) throw new Error('method: GetAdminTransactionSwapQuotes is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.TransactionSwapRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetAdminTransactionSwapQuotes({rpcName:'GetAdminTransactionSwapQuotes', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetAppsMetrics':
|
||||
try {
|
||||
if (!methods.GetAppsMetrics) throw new Error('method: GetAppsMetrics is not implemented')
|
||||
|
|
@ -962,6 +1000,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 'GetTransactionSwapQuotes':
|
||||
try {
|
||||
if (!methods.GetTransactionSwapQuotes) throw new Error('method: GetTransactionSwapQuotes is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.TransactionSwapRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetTransactionSwapQuotes({rpcName:'GetTransactionSwapQuotes', 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 'GetUsageMetrics':
|
||||
try {
|
||||
if (!methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
|
||||
|
|
@ -1068,6 +1122,19 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'ListAdminSwaps':
|
||||
try {
|
||||
if (!methods.ListAdminSwaps) throw new Error('method: ListAdminSwaps 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 })
|
||||
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 'ListChannels':
|
||||
try {
|
||||
if (!methods.ListChannels) throw new Error('method: ListChannels is not implemented')
|
||||
|
|
@ -1081,6 +1148,19 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'ListSwaps':
|
||||
try {
|
||||
if (!methods.ListSwaps) throw new Error('method: ListSwaps is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.ListSwaps({rpcName:'ListSwaps', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'LndGetInfo':
|
||||
try {
|
||||
if (!methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
|
||||
|
|
@ -1174,6 +1254,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 'PayAdminTransactionSwap':
|
||||
try {
|
||||
if (!methods.PayAdminTransactionSwap) throw new Error('method: PayAdminTransactionSwap is not implemented')
|
||||
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.PayAdminTransactionSwapRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.PayAdminTransactionSwap({rpcName:'PayAdminTransactionSwap', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayInvoice':
|
||||
try {
|
||||
if (!methods.PayInvoice) throw new Error('method: PayInvoice is not implemented')
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export type RequestMetric = AuthContext & RequestInfo & RequestStats & { error?:
|
|||
export type AdminContext = {
|
||||
admin_id: string
|
||||
}
|
||||
export type AdminMethodInputs = AddApp_Input | AddPeer_Input | AuthApp_Input | BanUser_Input | CloseChannel_Input | CreateOneTimeInviteLink_Input | GetInviteLinkState_Input | GetSeed_Input | ListChannels_Input | LndGetInfo_Input | OpenChannel_Input | UpdateChannelPolicy_Input
|
||||
export type AdminMethodOutputs = AddApp_Output | AddPeer_Output | AuthApp_Output | BanUser_Output | CloseChannel_Output | CreateOneTimeInviteLink_Output | GetInviteLinkState_Output | GetSeed_Output | ListChannels_Output | LndGetInfo_Output | OpenChannel_Output | UpdateChannelPolicy_Output
|
||||
export type 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 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 | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | ResetManage_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input
|
||||
export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeManage_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | EnrollMessagingToken_Output | GetDebitAuthorizations_Output | GetHttpCreds_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetManageAuthorizations_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | ResetManage_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output
|
||||
export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeManage_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | EnrollMessagingToken_Input | GetDebitAuthorizations_Input | GetHttpCreds_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetManageAuthorizations_Input | GetPaymentState_Input | 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 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 GetAdminTransactionSwapQuotes_Input = {rpcName:'GetAdminTransactionSwapQuotes', req: TransactionSwapRequest}
|
||||
export type GetAdminTransactionSwapQuotes_Output = ResultError | ({ status: 'OK' } & TransactionSwapQuoteList)
|
||||
|
||||
export type GetApp_Input = {rpcName:'GetApp'}
|
||||
export type GetApp_Output = ResultError | ({ status: 'OK' } & Application)
|
||||
|
||||
|
|
@ -186,6 +189,9 @@ export type GetSingleBundleMetrics_Output = ResultError | ({ status: 'OK' } & Bu
|
|||
export type GetSingleUsageMetrics_Input = {rpcName:'GetSingleUsageMetrics', req: SingleMetricReq}
|
||||
export type GetSingleUsageMetrics_Output = ResultError | ({ status: 'OK' } & UsageMetricTlv)
|
||||
|
||||
export type GetTransactionSwapQuotes_Input = {rpcName:'GetTransactionSwapQuotes', req: TransactionSwapRequest}
|
||||
export type GetTransactionSwapQuotes_Output = ResultError | ({ status: 'OK' } & TransactionSwapQuoteList)
|
||||
|
||||
export type GetUsageMetrics_Input = {rpcName:'GetUsageMetrics', req: LatestUsageMetricReq}
|
||||
export type GetUsageMetrics_Output = ResultError | ({ status: 'OK' } & UsageMetrics)
|
||||
|
||||
|
|
@ -232,9 +238,15 @@ 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 ListChannels_Input = {rpcName:'ListChannels'}
|
||||
export type ListChannels_Output = ResultError | ({ status: 'OK' } & LndChannels)
|
||||
|
||||
export type ListSwaps_Input = {rpcName:'ListSwaps'}
|
||||
export type ListSwaps_Output = ResultError | ({ status: 'OK' } & SwapsList)
|
||||
|
||||
export type LndGetInfo_Input = {rpcName:'LndGetInfo', req: LndGetInfoRequest}
|
||||
export type LndGetInfo_Output = ResultError | ({ status: 'OK' } & LndGetInfoResponse)
|
||||
|
||||
|
|
@ -256,6 +268,9 @@ 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 PayAdminTransactionSwap_Input = {rpcName:'PayAdminTransactionSwap', req: PayAdminTransactionSwapRequest}
|
||||
export type PayAdminTransactionSwap_Output = ResultError | ({ status: 'OK' } & AdminSwapResponse)
|
||||
|
||||
export type PayAppUserInvoice_Input = {rpcName:'PayAppUserInvoice', req: PayAppUserInvoiceRequest}
|
||||
export type PayAppUserInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResponse)
|
||||
|
||||
|
|
@ -342,6 +357,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>
|
||||
GetAdminTransactionSwapQuotes?: (req: GetAdminTransactionSwapQuotes_Input & {ctx: AdminContext }) => Promise<TransactionSwapQuoteList>
|
||||
GetApp?: (req: GetApp_Input & {ctx: AppContext }) => Promise<Application>
|
||||
GetAppUser?: (req: GetAppUser_Input & {ctx: AppContext }) => Promise<AppUser>
|
||||
GetAppUserLNURLInfo?: (req: GetAppUserLNURLInfo_Input & {ctx: AppContext }) => Promise<LnurlPayInfoResponse>
|
||||
|
|
@ -369,6 +385,7 @@ export type ServerMethods = {
|
|||
GetSeed?: (req: GetSeed_Input & {ctx: AdminContext }) => Promise<LndSeed>
|
||||
GetSingleBundleMetrics?: (req: GetSingleBundleMetrics_Input & {ctx: MetricsContext }) => Promise<BundleData>
|
||||
GetSingleUsageMetrics?: (req: GetSingleUsageMetrics_Input & {ctx: MetricsContext }) => Promise<UsageMetricTlv>
|
||||
GetTransactionSwapQuotes?: (req: GetTransactionSwapQuotes_Input & {ctx: UserContext }) => Promise<TransactionSwapQuoteList>
|
||||
GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise<UsageMetrics>
|
||||
GetUserInfo?: (req: GetUserInfo_Input & {ctx: UserContext }) => Promise<UserInfo>
|
||||
GetUserOffer?: (req: GetUserOffer_Input & {ctx: UserContext }) => Promise<OfferConfig>
|
||||
|
|
@ -380,13 +397,16 @@ 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>
|
||||
ListChannels?: (req: ListChannels_Input & {ctx: AdminContext }) => Promise<LndChannels>
|
||||
ListSwaps?: (req: ListSwaps_Input & {ctx: UserContext }) => Promise<SwapsList>
|
||||
LndGetInfo?: (req: LndGetInfo_Input & {ctx: AdminContext }) => Promise<LndGetInfoResponse>
|
||||
NewAddress?: (req: NewAddress_Input & {ctx: UserContext }) => Promise<NewAddressResponse>
|
||||
NewInvoice?: (req: NewInvoice_Input & {ctx: UserContext }) => Promise<NewInvoiceResponse>
|
||||
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>
|
||||
PayAppUserInvoice?: (req: PayAppUserInvoice_Input & {ctx: AppContext }) => Promise<PayInvoiceResponse>
|
||||
PayInvoice?: (req: PayInvoice_Input & {ctx: UserContext }) => Promise<PayInvoiceResponse>
|
||||
PingSubProcesses?: (req: PingSubProcesses_Input & {ctx: MetricsContext }) => Promise<void>
|
||||
|
|
@ -651,6 +671,29 @@ export const AddProductRequestValidate = (o?: AddProductRequest, opts: AddProduc
|
|||
return null
|
||||
}
|
||||
|
||||
export type AdminSwapResponse = {
|
||||
network_fee: number
|
||||
tx_id: string
|
||||
}
|
||||
export const AdminSwapResponseOptionalFields: [] = []
|
||||
export type AdminSwapResponseOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
network_fee_CustomCheck?: (v: number) => boolean
|
||||
tx_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const AdminSwapResponseValidate = (o?: AdminSwapResponse, opts: AdminSwapResponseOptions = {}, path: string = 'AdminSwapResponse::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
|
||||
|
||||
if (typeof o.network_fee !== 'number') return new Error(`${path}.network_fee: is not a number`)
|
||||
if (opts.network_fee_CustomCheck && !opts.network_fee_CustomCheck(o.network_fee)) return new Error(`${path}.network_fee: custom check failed`)
|
||||
|
||||
if (typeof o.tx_id !== 'string') return new Error(`${path}.tx_id: is not a string`)
|
||||
if (opts.tx_id_CustomCheck && !opts.tx_id_CustomCheck(o.tx_id)) return new Error(`${path}.tx_id: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type AppMetrics = {
|
||||
app: Application
|
||||
available: number
|
||||
|
|
@ -979,6 +1022,48 @@ export const BannedAppUserValidate = (o?: BannedAppUser, opts: BannedAppUserOpti
|
|||
return null
|
||||
}
|
||||
|
||||
export type BeaconData = {
|
||||
avatarUrl?: string
|
||||
fees?: CumulativeFees
|
||||
name: string
|
||||
nextRelay?: string
|
||||
type: string
|
||||
}
|
||||
export type BeaconDataOptionalField = 'avatarUrl' | 'fees' | 'nextRelay'
|
||||
export const BeaconDataOptionalFields: BeaconDataOptionalField[] = ['avatarUrl', 'fees', 'nextRelay']
|
||||
export type BeaconDataOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: BeaconDataOptionalField[]
|
||||
avatarUrl_CustomCheck?: (v?: string) => boolean
|
||||
fees_Options?: CumulativeFeesOptions
|
||||
name_CustomCheck?: (v: string) => boolean
|
||||
nextRelay_CustomCheck?: (v?: string) => boolean
|
||||
type_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const BeaconDataValidate = (o?: BeaconData, opts: BeaconDataOptions = {}, path: string = 'BeaconData::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.avatarUrl || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('avatarUrl')) && typeof o.avatarUrl !== 'string') return new Error(`${path}.avatarUrl: is not a string`)
|
||||
if (opts.avatarUrl_CustomCheck && !opts.avatarUrl_CustomCheck(o.avatarUrl)) return new Error(`${path}.avatarUrl: custom check failed`)
|
||||
|
||||
if (typeof o.fees === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('fees')) {
|
||||
const feesErr = CumulativeFeesValidate(o.fees, opts.fees_Options, `${path}.fees`)
|
||||
if (feesErr !== null) return feesErr
|
||||
}
|
||||
|
||||
|
||||
if (typeof o.name !== 'string') return new Error(`${path}.name: is not a string`)
|
||||
if (opts.name_CustomCheck && !opts.name_CustomCheck(o.name)) return new Error(`${path}.name: custom check failed`)
|
||||
|
||||
if ((o.nextRelay || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('nextRelay')) && typeof o.nextRelay !== 'string') return new Error(`${path}.nextRelay: is not a string`)
|
||||
if (opts.nextRelay_CustomCheck && !opts.nextRelay_CustomCheck(o.nextRelay)) return new Error(`${path}.nextRelay: custom check failed`)
|
||||
|
||||
if (typeof o.type !== 'string') return new Error(`${path}.type: is not a string`)
|
||||
if (opts.type_CustomCheck && !opts.type_CustomCheck(o.type)) return new Error(`${path}.type: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type BundleData = {
|
||||
available_chunks: number[]
|
||||
base_64_data: string[]
|
||||
|
|
@ -1252,6 +1337,29 @@ export const CreateOneTimeInviteLinkResponseValidate = (o?: CreateOneTimeInviteL
|
|||
return null
|
||||
}
|
||||
|
||||
export type CumulativeFees = {
|
||||
serviceFeeBps: number
|
||||
serviceFeeFloor: number
|
||||
}
|
||||
export const CumulativeFeesOptionalFields: [] = []
|
||||
export type CumulativeFeesOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
serviceFeeBps_CustomCheck?: (v: number) => boolean
|
||||
serviceFeeFloor_CustomCheck?: (v: number) => boolean
|
||||
}
|
||||
export const CumulativeFeesValidate = (o?: CumulativeFees, opts: CumulativeFeesOptions = {}, path: string = 'CumulativeFees::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.serviceFeeBps !== 'number') return new Error(`${path}.serviceFeeBps: is not a number`)
|
||||
if (opts.serviceFeeBps_CustomCheck && !opts.serviceFeeBps_CustomCheck(o.serviceFeeBps)) return new Error(`${path}.serviceFeeBps: custom check failed`)
|
||||
|
||||
if (typeof o.serviceFeeFloor !== 'number') return new Error(`${path}.serviceFeeFloor: is not a number`)
|
||||
if (opts.serviceFeeFloor_CustomCheck && !opts.serviceFeeFloor_CustomCheck(o.serviceFeeFloor)) return new Error(`${path}.serviceFeeFloor: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type DebitAuthorization = {
|
||||
authorized: boolean
|
||||
debit_id: string
|
||||
|
|
@ -2089,17 +2197,22 @@ export const LiveManageRequestValidate = (o?: LiveManageRequest, opts: LiveManag
|
|||
}
|
||||
|
||||
export type LiveUserOperation = {
|
||||
latest_balance: number
|
||||
operation: UserOperation
|
||||
}
|
||||
export const LiveUserOperationOptionalFields: [] = []
|
||||
export type LiveUserOperationOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
latest_balance_CustomCheck?: (v: number) => boolean
|
||||
operation_Options?: UserOperationOptions
|
||||
}
|
||||
export const LiveUserOperationValidate = (o?: LiveUserOperation, opts: LiveUserOperationOptions = {}, path: string = 'LiveUserOperation::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.latest_balance !== 'number') return new Error(`${path}.latest_balance: is not a number`)
|
||||
if (opts.latest_balance_CustomCheck && !opts.latest_balance_CustomCheck(o.latest_balance)) return new Error(`${path}.latest_balance: custom check failed`)
|
||||
|
||||
const operationErr = UserOperationValidate(o.operation, opts.operation_Options, `${path}.operation`)
|
||||
if (operationErr !== null) return operationErr
|
||||
|
||||
|
|
@ -3130,15 +3243,18 @@ export const OperationsCursorValidate = (o?: OperationsCursor, opts: OperationsC
|
|||
|
||||
export type PayAddressRequest = {
|
||||
address: string
|
||||
amoutSats: number
|
||||
amountSats: number
|
||||
satsPerVByte: number
|
||||
swap_operation_id?: string
|
||||
}
|
||||
export const PayAddressRequestOptionalFields: [] = []
|
||||
export type PayAddressRequestOptionalField = 'swap_operation_id'
|
||||
export const PayAddressRequestOptionalFields: PayAddressRequestOptionalField[] = ['swap_operation_id']
|
||||
export type PayAddressRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
checkOptionalsAreSet?: PayAddressRequestOptionalField[]
|
||||
address_CustomCheck?: (v: string) => boolean
|
||||
amoutSats_CustomCheck?: (v: number) => boolean
|
||||
amountSats_CustomCheck?: (v: number) => boolean
|
||||
satsPerVByte_CustomCheck?: (v: number) => boolean
|
||||
swap_operation_id_CustomCheck?: (v?: string) => boolean
|
||||
}
|
||||
export const PayAddressRequestValidate = (o?: PayAddressRequest, opts: PayAddressRequestOptions = {}, path: string = 'PayAddressRequest::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
|
|
@ -3147,12 +3263,15 @@ export const PayAddressRequestValidate = (o?: PayAddressRequest, opts: PayAddres
|
|||
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.amoutSats !== 'number') return new Error(`${path}.amoutSats: is not a number`)
|
||||
if (opts.amoutSats_CustomCheck && !opts.amoutSats_CustomCheck(o.amoutSats)) return new Error(`${path}.amoutSats: custom check failed`)
|
||||
if (typeof o.amountSats !== 'number') return new Error(`${path}.amountSats: is not a number`)
|
||||
if (opts.amountSats_CustomCheck && !opts.amountSats_CustomCheck(o.amountSats)) return new Error(`${path}.amountSats: custom check failed`)
|
||||
|
||||
if (typeof o.satsPerVByte !== 'number') return new Error(`${path}.satsPerVByte: is not a number`)
|
||||
if (opts.satsPerVByte_CustomCheck && !opts.satsPerVByte_CustomCheck(o.satsPerVByte)) return new Error(`${path}.satsPerVByte: custom check failed`)
|
||||
|
||||
if ((o.swap_operation_id || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('swap_operation_id')) && 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
|
||||
}
|
||||
|
||||
|
|
@ -3189,18 +3308,43 @@ export const PayAddressResponseValidate = (o?: PayAddressResponse, opts: PayAddr
|
|||
return null
|
||||
}
|
||||
|
||||
export type PayAdminTransactionSwapRequest = {
|
||||
address: string
|
||||
swap_operation_id: string
|
||||
}
|
||||
export const PayAdminTransactionSwapRequestOptionalFields: [] = []
|
||||
export type PayAdminTransactionSwapRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
address_CustomCheck?: (v: string) => boolean
|
||||
swap_operation_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const PayAdminTransactionSwapRequestValidate = (o?: PayAdminTransactionSwapRequest, opts: PayAdminTransactionSwapRequestOptions = {}, path: string = 'PayAdminTransactionSwapRequest::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
|
||||
|
||||
if (typeof o.address !== 'string') return new Error(`${path}.address: is not a string`)
|
||||
if (opts.address_CustomCheck && !opts.address_CustomCheck(o.address)) return new Error(`${path}.address: custom check failed`)
|
||||
|
||||
if (typeof o.swap_operation_id !== 'string') return new Error(`${path}.swap_operation_id: is not a string`)
|
||||
if (opts.swap_operation_id_CustomCheck && !opts.swap_operation_id_CustomCheck(o.swap_operation_id)) return new Error(`${path}.swap_operation_id: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type PayAppUserInvoiceRequest = {
|
||||
amount: number
|
||||
debit_npub?: string
|
||||
expected_fees?: CumulativeFees
|
||||
invoice: string
|
||||
user_identifier: string
|
||||
}
|
||||
export type PayAppUserInvoiceRequestOptionalField = 'debit_npub'
|
||||
export const PayAppUserInvoiceRequestOptionalFields: PayAppUserInvoiceRequestOptionalField[] = ['debit_npub']
|
||||
export type PayAppUserInvoiceRequestOptionalField = 'debit_npub' | 'expected_fees'
|
||||
export const PayAppUserInvoiceRequestOptionalFields: PayAppUserInvoiceRequestOptionalField[] = ['debit_npub', 'expected_fees']
|
||||
export type PayAppUserInvoiceRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: PayAppUserInvoiceRequestOptionalField[]
|
||||
amount_CustomCheck?: (v: number) => boolean
|
||||
debit_npub_CustomCheck?: (v?: string) => boolean
|
||||
expected_fees_Options?: CumulativeFeesOptions
|
||||
invoice_CustomCheck?: (v: string) => boolean
|
||||
user_identifier_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
|
|
@ -3214,6 +3358,12 @@ export const PayAppUserInvoiceRequestValidate = (o?: PayAppUserInvoiceRequest, o
|
|||
if ((o.debit_npub || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('debit_npub')) && typeof o.debit_npub !== 'string') return new Error(`${path}.debit_npub: is not a string`)
|
||||
if (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`)
|
||||
|
||||
if (typeof o.expected_fees === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('expected_fees')) {
|
||||
const expected_feesErr = CumulativeFeesValidate(o.expected_fees, opts.expected_fees_Options, `${path}.expected_fees`)
|
||||
if (expected_feesErr !== null) return expected_feesErr
|
||||
}
|
||||
|
||||
|
||||
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`)
|
||||
|
||||
|
|
@ -3226,14 +3376,16 @@ export const PayAppUserInvoiceRequestValidate = (o?: PayAppUserInvoiceRequest, o
|
|||
export type PayInvoiceRequest = {
|
||||
amount: number
|
||||
debit_npub?: string
|
||||
expected_fees?: CumulativeFees
|
||||
invoice: string
|
||||
}
|
||||
export type PayInvoiceRequestOptionalField = 'debit_npub'
|
||||
export const PayInvoiceRequestOptionalFields: PayInvoiceRequestOptionalField[] = ['debit_npub']
|
||||
export type PayInvoiceRequestOptionalField = 'debit_npub' | 'expected_fees'
|
||||
export const PayInvoiceRequestOptionalFields: PayInvoiceRequestOptionalField[] = ['debit_npub', 'expected_fees']
|
||||
export type PayInvoiceRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: PayInvoiceRequestOptionalField[]
|
||||
amount_CustomCheck?: (v: number) => boolean
|
||||
debit_npub_CustomCheck?: (v?: string) => boolean
|
||||
expected_fees_Options?: CumulativeFeesOptions
|
||||
invoice_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoiceRequestOptions = {}, path: string = 'PayInvoiceRequest::root.'): Error | null => {
|
||||
|
|
@ -3246,6 +3398,12 @@ export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoic
|
|||
if ((o.debit_npub || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('debit_npub')) && typeof o.debit_npub !== 'string') return new Error(`${path}.debit_npub: is not a string`)
|
||||
if (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`)
|
||||
|
||||
if (typeof o.expected_fees === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('expected_fees')) {
|
||||
const expected_feesErr = CumulativeFeesValidate(o.expected_fees, opts.expected_fees_Options, `${path}.expected_fees`)
|
||||
if (expected_feesErr !== null) return expected_feesErr
|
||||
}
|
||||
|
||||
|
||||
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`)
|
||||
|
||||
|
|
@ -3254,6 +3412,7 @@ export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoic
|
|||
|
||||
export type PayInvoiceResponse = {
|
||||
amount_paid: number
|
||||
latest_balance: number
|
||||
network_fee: number
|
||||
operation_id: string
|
||||
preimage: string
|
||||
|
|
@ -3263,6 +3422,7 @@ export const PayInvoiceResponseOptionalFields: [] = []
|
|||
export type PayInvoiceResponseOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
amount_paid_CustomCheck?: (v: number) => boolean
|
||||
latest_balance_CustomCheck?: (v: number) => boolean
|
||||
network_fee_CustomCheck?: (v: number) => boolean
|
||||
operation_id_CustomCheck?: (v: string) => boolean
|
||||
preimage_CustomCheck?: (v: string) => boolean
|
||||
|
|
@ -3275,6 +3435,9 @@ export const PayInvoiceResponseValidate = (o?: PayInvoiceResponse, opts: PayInvo
|
|||
if (typeof o.amount_paid !== 'number') return new Error(`${path}.amount_paid: is not a number`)
|
||||
if (opts.amount_paid_CustomCheck && !opts.amount_paid_CustomCheck(o.amount_paid)) return new Error(`${path}.amount_paid: custom check failed`)
|
||||
|
||||
if (typeof o.latest_balance !== 'number') return new Error(`${path}.latest_balance: is not a number`)
|
||||
if (opts.latest_balance_CustomCheck && !opts.latest_balance_CustomCheck(o.latest_balance)) return new Error(`${path}.latest_balance: custom check failed`)
|
||||
|
||||
if (typeof o.network_fee !== 'number') return new Error(`${path}.network_fee: is not a number`)
|
||||
if (opts.network_fee_CustomCheck && !opts.network_fee_CustomCheck(o.network_fee)) return new Error(`${path}.network_fee: custom check failed`)
|
||||
|
||||
|
|
@ -3312,7 +3475,9 @@ export const PayerDataValidate = (o?: PayerData, opts: PayerDataOptions = {}, pa
|
|||
|
||||
export type PaymentState = {
|
||||
amount: number
|
||||
internal: boolean
|
||||
network_fee: number
|
||||
operation_id: string
|
||||
paid_at_unix: number
|
||||
service_fee: number
|
||||
}
|
||||
|
|
@ -3320,7 +3485,9 @@ export const PaymentStateOptionalFields: [] = []
|
|||
export type PaymentStateOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
amount_CustomCheck?: (v: number) => boolean
|
||||
internal_CustomCheck?: (v: boolean) => boolean
|
||||
network_fee_CustomCheck?: (v: number) => boolean
|
||||
operation_id_CustomCheck?: (v: string) => boolean
|
||||
paid_at_unix_CustomCheck?: (v: number) => boolean
|
||||
service_fee_CustomCheck?: (v: number) => boolean
|
||||
}
|
||||
|
|
@ -3331,9 +3498,15 @@ export const PaymentStateValidate = (o?: PaymentState, opts: PaymentStateOptions
|
|||
if (typeof o.amount !== 'number') return new Error(`${path}.amount: is not a number`)
|
||||
if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`)
|
||||
|
||||
if (typeof o.internal !== 'boolean') return new Error(`${path}.internal: is not a boolean`)
|
||||
if (opts.internal_CustomCheck && !opts.internal_CustomCheck(o.internal)) return new Error(`${path}.internal: custom check failed`)
|
||||
|
||||
if (typeof o.network_fee !== 'number') return new Error(`${path}.network_fee: is not a number`)
|
||||
if (opts.network_fee_CustomCheck && !opts.network_fee_CustomCheck(o.network_fee)) return new Error(`${path}.network_fee: custom check failed`)
|
||||
|
||||
if (typeof o.operation_id !== 'string') return new Error(`${path}.operation_id: is not a string`)
|
||||
if (opts.operation_id_CustomCheck && !opts.operation_id_CustomCheck(o.operation_id)) return new Error(`${path}.operation_id: custom check failed`)
|
||||
|
||||
if (typeof o.paid_at_unix !== 'number') return new Error(`${path}.paid_at_unix: is not a number`)
|
||||
if (opts.paid_at_unix_CustomCheck && !opts.paid_at_unix_CustomCheck(o.paid_at_unix)) return new Error(`${path}.paid_at_unix: custom check failed`)
|
||||
|
||||
|
|
@ -3744,6 +3917,165 @@ 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
|
||||
service_fee_sats: number
|
||||
service_url: string
|
||||
swap_fee_sats: number
|
||||
swap_operation_id: string
|
||||
transaction_amount_sats: number
|
||||
}
|
||||
export const TransactionSwapQuoteOptionalFields: [] = []
|
||||
export type TransactionSwapQuoteOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
chain_fee_sats_CustomCheck?: (v: number) => 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
|
||||
}
|
||||
export const TransactionSwapQuoteValidate = (o?: TransactionSwapQuote, opts: TransactionSwapQuoteOptions = {}, path: string = 'TransactionSwapQuote::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.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_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`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type TransactionSwapQuoteList = {
|
||||
quotes: TransactionSwapQuote[]
|
||||
}
|
||||
export const TransactionSwapQuoteListOptionalFields: [] = []
|
||||
export type TransactionSwapQuoteListOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
quotes_ItemOptions?: TransactionSwapQuoteOptions
|
||||
quotes_CustomCheck?: (v: TransactionSwapQuote[]) => boolean
|
||||
}
|
||||
export const TransactionSwapQuoteListValidate = (o?: TransactionSwapQuoteList, opts: TransactionSwapQuoteListOptions = {}, path: string = 'TransactionSwapQuoteList::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`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type TransactionSwapRequest = {
|
||||
transaction_amount_sats: number
|
||||
}
|
||||
export const TransactionSwapRequestOptionalFields: [] = []
|
||||
export type TransactionSwapRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
transaction_amount_sats_CustomCheck?: (v: number) => boolean
|
||||
}
|
||||
export const TransactionSwapRequestValidate = (o?: TransactionSwapRequest, opts: TransactionSwapRequestOptions = {}, path: string = 'TransactionSwapRequest::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.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`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type UpdateChannelPolicyRequest = {
|
||||
policy: ChannelPolicy
|
||||
update: UpdateChannelPolicyRequest_update
|
||||
|
|
|
|||
|
|
@ -175,6 +175,27 @@ service LightningPub {
|
|||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetAdminTransactionSwapQuotes(structs.TransactionSwapRequest) returns (structs.TransactionSwapQuoteList) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/transaction/quote";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc PayAdminTransactionSwap(structs.PayAdminTransactionSwapRequest) returns (structs.AdminSwapResponse) {
|
||||
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) {
|
||||
option (auth_type) = "Admin";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/swap/list";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetUsageMetrics(structs.LatestUsageMetricReq) returns (structs.UsageMetrics) {
|
||||
option (auth_type) = "Metrics";
|
||||
option (http_method) = "post";
|
||||
|
|
@ -496,6 +517,20 @@ service LightningPub {
|
|||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc GetTransactionSwapQuotes(structs.TransactionSwapRequest) returns (structs.TransactionSwapQuoteList){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/swap/quote";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc ListSwaps(structs.Empty) returns (structs.SwapsList){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/swap/list";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc NewInvoice(structs.NewInvoiceRequest) returns (structs.NewInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ message PayAppUserInvoiceRequest {
|
|||
string invoice = 2;
|
||||
int64 amount = 3;
|
||||
optional string debit_npub = 4;
|
||||
optional CumulativeFees expected_fees = 5;
|
||||
}
|
||||
|
||||
message SendAppUserToAppUserPaymentRequest {
|
||||
|
|
@ -430,8 +431,9 @@ message NewAddressResponse{
|
|||
}
|
||||
message PayAddressRequest{
|
||||
string address = 1;
|
||||
int64 amoutSats = 2;
|
||||
int64 amountSats = 2;
|
||||
int64 satsPerVByte = 3;
|
||||
optional string swap_operation_id = 4;
|
||||
}
|
||||
|
||||
message PayAddressResponse{
|
||||
|
|
@ -466,6 +468,7 @@ message PayInvoiceRequest{
|
|||
string invoice = 1;
|
||||
int64 amount = 2;
|
||||
optional string debit_npub = 3;
|
||||
optional CumulativeFees expected_fees = 4;
|
||||
}
|
||||
|
||||
message PayInvoiceResponse{
|
||||
|
|
@ -474,6 +477,7 @@ message PayInvoiceResponse{
|
|||
string operation_id = 3;
|
||||
int64 service_fee = 4;
|
||||
int64 network_fee = 5;
|
||||
int64 latest_balance = 6;
|
||||
}
|
||||
|
||||
message GetPaymentStateRequest{
|
||||
|
|
@ -486,6 +490,9 @@ message PaymentState{
|
|||
int64 amount = 2;
|
||||
int64 service_fee = 3;
|
||||
int64 network_fee = 4;
|
||||
bool internal = 5;
|
||||
string operation_id = 6;
|
||||
|
||||
}
|
||||
|
||||
message LnurlLinkResponse{
|
||||
|
|
@ -604,6 +611,7 @@ message GetProductBuyLinkResponse {
|
|||
|
||||
message LiveUserOperation {
|
||||
UserOperation operation = 1;
|
||||
int64 latest_balance = 2;
|
||||
}
|
||||
message MigrationUpdate {
|
||||
optional ClosureMigration closure = 1;
|
||||
|
|
@ -824,3 +832,57 @@ message MessagingToken {
|
|||
string device_id = 1;
|
||||
string firebase_messaging_token = 2;
|
||||
}
|
||||
|
||||
message TransactionSwapRequest {
|
||||
int64 transaction_amount_sats = 2;
|
||||
}
|
||||
|
||||
message PayAdminTransactionSwapRequest {
|
||||
string address = 1;
|
||||
string swap_operation_id = 2;
|
||||
}
|
||||
|
||||
message TransactionSwapQuote {
|
||||
string swap_operation_id = 1;
|
||||
int64 invoice_amount_sats = 2;
|
||||
int64 transaction_amount_sats = 3;
|
||||
|
||||
int64 swap_fee_sats = 4;
|
||||
int64 chain_fee_sats = 5;
|
||||
int64 service_fee_sats = 7;
|
||||
string service_url = 8;
|
||||
}
|
||||
|
||||
message TransactionSwapQuoteList {
|
||||
repeated TransactionSwapQuote quotes = 1;
|
||||
}
|
||||
|
||||
message AdminSwapResponse {
|
||||
string tx_id = 1;
|
||||
int64 network_fee = 2;
|
||||
}
|
||||
|
||||
message SwapOperation {
|
||||
string swap_operation_id = 1;
|
||||
optional UserOperation operation_payment = 2;
|
||||
optional string failure_reason = 3;
|
||||
string address_paid = 4;
|
||||
}
|
||||
|
||||
message SwapsList {
|
||||
repeated SwapOperation swaps = 1;
|
||||
repeated TransactionSwapQuote quotes = 2;
|
||||
}
|
||||
|
||||
message CumulativeFees {
|
||||
int64 serviceFeeFloor = 2;
|
||||
int64 serviceFeeBps = 3;
|
||||
}
|
||||
|
||||
message BeaconData {
|
||||
string type = 1;
|
||||
string name = 2;
|
||||
optional string avatarUrl = 3;
|
||||
optional string nextRelay = 4;
|
||||
optional CumulativeFees fees = 5;
|
||||
}
|
||||
24
src/e2e.ts
24
src/e2e.ts
|
|
@ -7,6 +7,7 @@ import { getLogger } from './services/helpers/logger.js';
|
|||
import { initMainHandler, initSettings } from './services/main/init.js';
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { LoadStorageSettingsFromEnv } from './services/storage/index.js';
|
||||
import { AppInfo } from './services/nostr/nostrPool.js';
|
||||
//@ts-ignore
|
||||
const { nprofileEncode } = nip19
|
||||
|
||||
|
|
@ -20,18 +21,35 @@ const start = async () => {
|
|||
return
|
||||
}
|
||||
|
||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||
const { mainHandler, localProviderClient, wizard, adminManager } = keepOn
|
||||
const serverMethods = GetServerMethods(mainHandler)
|
||||
const nostrSettings = settingsManager.getSettings().nostrRelaySettings
|
||||
log("initializing nostr middleware")
|
||||
const relays = settingsManager.getSettings().nostrRelaySettings.relays
|
||||
const maxEventContentLength = settingsManager.getSettings().nostrRelaySettings.maxEventContentLength
|
||||
const apps: AppInfo[] = keepOn.apps.map(app => {
|
||||
return {
|
||||
appId: app.appId,
|
||||
privateKey: app.privateKey,
|
||||
publicKey: app.publicKey,
|
||||
name: app.name,
|
||||
provider: app.publicKey === localProviderClient.publicKey ? {
|
||||
clientId: `client_${localProviderClient.appId}`,
|
||||
pubkey: settingsManager.getSettings().liquiditySettings.liquidityProviderPub,
|
||||
relayUrl: settingsManager.getSettings().liquiditySettings.providerRelayUrl
|
||||
} : undefined
|
||||
}
|
||||
})
|
||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
||||
{
|
||||
relays, maxEventContentLength, apps
|
||||
},
|
||||
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
||||
)
|
||||
log("starting server")
|
||||
mainHandler.attachNostrSend(Send)
|
||||
mainHandler.StartBeacons()
|
||||
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays: nostrSettings.relays })
|
||||
const appNprofile = nprofileEncode({ pubkey: localProviderClient.publicKey, relays: nostrSettings.relays })
|
||||
if (wizard) {
|
||||
wizard.AddConnectInfo(appNprofile, nostrSettings.relays)
|
||||
}
|
||||
|
|
|
|||
28
src/index.ts
28
src/index.ts
|
|
@ -7,6 +7,7 @@ import { getLogger } from './services/helpers/logger.js';
|
|||
import { initMainHandler, initSettings } from './services/main/init.js';
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { LoadStorageSettingsFromEnv } from './services/storage/index.js';
|
||||
import { AppInfo } from './services/nostr/nostrPool.js';
|
||||
//@ts-ignore
|
||||
const { nprofileEncode } = nip19
|
||||
|
||||
|
|
@ -22,13 +23,28 @@ const start = async () => {
|
|||
return
|
||||
}
|
||||
|
||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||
const { mainHandler, localProviderClient, wizard, adminManager } = keepOn
|
||||
const serverMethods = GetServerMethods(mainHandler)
|
||||
log("initializing nostr middleware")
|
||||
const relays = mainHandler.settings.getSettings().nostrRelaySettings.relays
|
||||
const maxEventContentLength = mainHandler.settings.getSettings().nostrRelaySettings.maxEventContentLength
|
||||
const relays = settingsManager.getSettings().nostrRelaySettings.relays
|
||||
const maxEventContentLength = settingsManager.getSettings().nostrRelaySettings.maxEventContentLength
|
||||
const apps: AppInfo[] = keepOn.apps.map(app => {
|
||||
return {
|
||||
appId: app.appId,
|
||||
privateKey: app.privateKey,
|
||||
publicKey: app.publicKey,
|
||||
name: app.name,
|
||||
provider: app.publicKey === localProviderClient.publicKey ? {
|
||||
clientId: `client_${localProviderClient.appId}`,
|
||||
pubkey: settingsManager.getSettings().liquiditySettings.liquidityProviderPub,
|
||||
relayUrl: settingsManager.getSettings().liquiditySettings.providerRelayUrl
|
||||
} : undefined
|
||||
}
|
||||
})
|
||||
const { Send, Stop, Ping, Reset } = nostrMiddleware(serverMethods, mainHandler,
|
||||
{ relays, maxEventContentLength, apps, clients: [liquidityProviderInfo] },
|
||||
{
|
||||
relays, maxEventContentLength, apps
|
||||
},
|
||||
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
||||
)
|
||||
exitHandler(() => { Stop(); mainHandler.Stop() })
|
||||
|
|
@ -37,13 +53,13 @@ const start = async () => {
|
|||
mainHandler.attachNostrProcessPing(Ping)
|
||||
mainHandler.attachNostrReset(Reset)
|
||||
mainHandler.StartBeacons()
|
||||
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays })
|
||||
const appNprofile = nprofileEncode({ pubkey: localProviderClient.publicKey, relays })
|
||||
if (wizard) {
|
||||
wizard.AddConnectInfo(appNprofile, relays)
|
||||
}
|
||||
adminManager.setAppNprofile(appNprofile)
|
||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||
Server.Listen(mainHandler.settings.getSettings().serviceSettings.servicePort)
|
||||
Server.Listen(settingsManager.getSettings().serviceSettings.servicePort)
|
||||
}
|
||||
start()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Main from "./services/main/index.js"
|
||||
import Nostr from "./services/nostr/index.js"
|
||||
import { NostrEvent, NostrSend, NostrSettings } from "./services/nostr/handler.js"
|
||||
import { NostrEvent, NostrSend, NostrSettings } from "./services/nostr/nostrPool.js"
|
||||
import * as Types from '../proto/autogenerated/ts/types.js'
|
||||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||
|
|
@ -50,6 +50,10 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
return
|
||||
}
|
||||
if (event.kind === 21001) {
|
||||
if (event.relayConstraint === 'provider') {
|
||||
log("got noffer request on provider only relay, ignoring")
|
||||
return
|
||||
}
|
||||
const offerReq = j as NofferData
|
||||
log("🎯 [NOSTR EVENT] Received offer request (kind 21001)", {
|
||||
fromPub: event.pub,
|
||||
|
|
@ -60,18 +64,33 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
mainHandler.offerManager.handleClinkOffer(offerReq, event)
|
||||
return
|
||||
} else if (event.kind === 21002) {
|
||||
if (event.relayConstraint === 'provider') {
|
||||
log("got debit request on provider only relay, ignoring")
|
||||
return
|
||||
}
|
||||
const debitReq = j as NdebitData
|
||||
mainHandler.debitManager.handleNip68Debit(debitReq, event)
|
||||
return
|
||||
} else if (event.kind === 21003) {
|
||||
if (event.relayConstraint === 'provider') {
|
||||
log("got management request on provider only relay, ignoring")
|
||||
return
|
||||
}
|
||||
const nmanageReq = j as NmanageRequest
|
||||
mainHandler.managementManager.handleRequest(nmanageReq, event);
|
||||
return;
|
||||
}
|
||||
if (!j.rpcName) {
|
||||
if (event.relayConstraint === 'service') {
|
||||
log("got relay response on service only relay, ignoring")
|
||||
}
|
||||
onClientEvent(j as { requestId: string }, event.pub)
|
||||
return
|
||||
}
|
||||
if (event.relayConstraint === 'provider') {
|
||||
log("got service request on provider only relay, ignoring")
|
||||
return
|
||||
}
|
||||
if (j.authIdentifier !== event.pub) {
|
||||
log(ERROR, "authIdentifier does not match", j.authIdentifier || "--", event.pub)
|
||||
return
|
||||
|
|
@ -79,7 +98,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
nostrTransport({ ...j, appId: event.appId }, res => {
|
||||
nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
|
||||
}, event.startAtNano, event.startAtMs)
|
||||
})
|
||||
}, beacon => mainHandler.liquidityProvider.onBeaconEvent(beacon))
|
||||
|
||||
// Mark nostr connected/ready after initial subscription tick
|
||||
mainHandler.adminManager.setNostrConnected(true)
|
||||
|
|
|
|||
|
|
@ -14,10 +14,22 @@ if (logLevel !== "DEBUG" && logLevel !== "WARN" && logLevel !== "ERROR") {
|
|||
throw new Error("Invalid log level " + logLevel + " must be one of (DEBUG, WARN, ERROR)")
|
||||
}
|
||||
const z = (n: number) => n < 10 ? `0${n}` : `${n}`
|
||||
// Sanitize filename to remove invalid characters for filesystem
|
||||
const sanitizeFileName = (fileName: string): string => {
|
||||
// Replace invalid filename characters with underscores
|
||||
// Invalid on most filesystems: / \ : * ? " < > |
|
||||
return fileName.replace(/[/\\:*?"<>|]/g, '_')
|
||||
}
|
||||
const openWriter = (fileName: string): Writer => {
|
||||
const now = new Date()
|
||||
const date = `${now.getFullYear()}-${z(now.getMonth() + 1)}-${z(now.getDate())}`
|
||||
const logStream = fs.createWriteStream(`${logsDir}/${fileName}_${date}.log`, { flags: 'a' });
|
||||
const logPath = `${logsDir}/${fileName}_${date}.log`
|
||||
// Ensure parent directory exists
|
||||
const dirPath = logPath.substring(0, logPath.lastIndexOf('/'))
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true })
|
||||
}
|
||||
const logStream = fs.createWriteStream(logPath, { flags: 'a' });
|
||||
return (message) => {
|
||||
logStream.write(message + "\n")
|
||||
}
|
||||
|
|
@ -35,13 +47,13 @@ if (!fs.existsSync(`${logsDir}/components`)) {
|
|||
export const getLogger = (params: LoggerParams): PubLogger => {
|
||||
const writers: Writer[] = []
|
||||
if (params.appName) {
|
||||
writers.push(openWriter(`apps/${params.appName}`))
|
||||
writers.push(openWriter(`apps/${sanitizeFileName(params.appName)}`))
|
||||
}
|
||||
if (params.userId) {
|
||||
writers.push(openWriter(`users/${params.userId}`))
|
||||
writers.push(openWriter(`users/${sanitizeFileName(params.userId)}`))
|
||||
}
|
||||
if (params.component) {
|
||||
writers.push(openWriter(`components/${params.component}`))
|
||||
writers.push(openWriter(`components/${sanitizeFileName(params.component)}`))
|
||||
}
|
||||
if (writers.length === 0) {
|
||||
writers.push(rootWriter)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { StateBundler } from "../storage/tlv/stateBundler.js";
|
||||
import { TlvStorageFactory } from "../storage/tlv/tlvFilesStorageFactory.js";
|
||||
import { NostrSend } from "../nostr/handler.js";
|
||||
import { ProcessMetricsCollector } from "../storage/tlv/processMetricsCollector.js";
|
||||
import { NostrSender } from "../nostr/sender.js";
|
||||
type UtilsSettings = {
|
||||
noCollector?: boolean
|
||||
dataDir: string,
|
||||
|
|
@ -10,9 +10,11 @@ type UtilsSettings = {
|
|||
export class Utils {
|
||||
tlvStorageFactory: TlvStorageFactory
|
||||
stateBundler: StateBundler
|
||||
_nostrSend: NostrSend = () => { throw new Error('nostr send not initialized yet') }
|
||||
constructor({ noCollector, dataDir, allowResetMetricsStorages }: UtilsSettings) {
|
||||
this.tlvStorageFactory = new TlvStorageFactory(allowResetMetricsStorages)
|
||||
nostrSender: NostrSender
|
||||
|
||||
constructor({ noCollector, dataDir, allowResetMetricsStorages }: UtilsSettings, nostrSender: NostrSender) {
|
||||
this.nostrSender = nostrSender
|
||||
this.tlvStorageFactory = new TlvStorageFactory(allowResetMetricsStorages, nostrSender)
|
||||
this.stateBundler = new StateBundler(dataDir, this.tlvStorageFactory)
|
||||
if (!noCollector) {
|
||||
new ProcessMetricsCollector((metrics) => {
|
||||
|
|
@ -21,11 +23,6 @@ export class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this._nostrSend = f
|
||||
this.tlvStorageFactory.attachNostrSend(f)
|
||||
}
|
||||
|
||||
Stop() {
|
||||
this.stateBundler.Stop()
|
||||
this.tlvStorageFactory.disconnect()
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@ import { SendCoinsReq } from './sendCoinsReq.js';
|
|||
import { AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo, ChannelEventCb } from './settings.js';
|
||||
import { ERROR, getLogger } from '../helpers/logger.js';
|
||||
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
||||
import { LiquidityProvider } from '../main/liquidityProvider.js';
|
||||
import { Utils } from '../helpers/utilsWrapper.js';
|
||||
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';
|
||||
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
const deadLndRetrySeconds = 20
|
||||
|
|
@ -32,6 +33,7 @@ type NodeSettingsOverride = {
|
|||
lndCertPath: string
|
||||
lndMacaroonPath: string
|
||||
}
|
||||
export type LndAddress = { address: string, change: boolean }
|
||||
export default class {
|
||||
lightning: LightningClient
|
||||
invoices: InvoicesClient
|
||||
|
|
@ -53,6 +55,7 @@ export default class {
|
|||
liquidProvider: LiquidityProvider
|
||||
utils: Utils
|
||||
unlockLnd: () => Promise<void>
|
||||
addressesCache: Record<string, { isChange: boolean }> = {}
|
||||
constructor(getSettings: () => { lndSettings: LndSettings, lndNodeSettings: LndNodeSettings }, liquidProvider: LiquidityProvider, unlockLnd: () => Promise<any>, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb, channelEventCb: ChannelEventCb) {
|
||||
this.getSettings = getSettings
|
||||
this.utils = utils
|
||||
|
|
@ -331,6 +334,26 @@ export default class {
|
|||
})
|
||||
}
|
||||
|
||||
async IsChangeAddress(address: string): Promise<boolean> {
|
||||
const cached = this.addressesCache[address]
|
||||
if (cached) {
|
||||
return cached.isChange
|
||||
}
|
||||
const addresses = await this.ListAddresses()
|
||||
const addr = addresses.find(a => a.address === address)
|
||||
if (!addr) {
|
||||
throw new Error(`address ${address} not found in list of addresses`)
|
||||
}
|
||||
return addr.change
|
||||
}
|
||||
|
||||
async ListAddresses(): Promise<LndAddress[]> {
|
||||
const res = await this.walletKit.listAddresses({ accountName: "", showCustomAccounts: false }, DeadLineMetadata())
|
||||
const addresses = res.response.accountWithAddresses.map(a => a.addresses.map(a => ({ address: a.address, change: a.isInternal }))).flat()
|
||||
addresses.forEach(a => this.addressesCache[a.address] = { isChange: a.change })
|
||||
return addresses
|
||||
}
|
||||
|
||||
async NewAddress(addressType: Types.AddressType, { useProvider, from }: TxActionOptions): Promise<NewAddressResponse> {
|
||||
// Force use of provider when bypass is enabled (addresses not supported by provider, but we should fail gracefully)
|
||||
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||
|
|
@ -372,8 +395,8 @@ export default class {
|
|||
if (mustUseProvider) {
|
||||
console.log("using provider")
|
||||
const invoice = await this.liquidProvider.AddInvoice(value, memo, from, expiry)
|
||||
const providerDst = this.liquidProvider.GetProviderDestination()
|
||||
return { payRequest: invoice, providerDst }
|
||||
const providerPubkey = this.liquidProvider.GetProviderPubkey()
|
||||
return { payRequest: invoice, providerPubkey }
|
||||
}
|
||||
try {
|
||||
const res = await this.lightning.addInvoice(AddInvoiceReq(value, expiry, true, memo, blind), DeadLineMetadata())
|
||||
|
|
@ -416,14 +439,6 @@ export default class {
|
|||
return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash }
|
||||
}
|
||||
|
||||
GetFeeLimitAmount(amount: number): number {
|
||||
return Math.ceil(amount * this.getSettings().lndSettings.feeRateLimit + this.getSettings().lndSettings.feeFixedLimit);
|
||||
}
|
||||
|
||||
GetMaxWithinLimit(amount: number): number {
|
||||
return Math.max(0, Math.floor(amount * (1 - this.getSettings().lndSettings.feeRateLimit) - this.getSettings().lndSettings.feeFixedLimit))
|
||||
}
|
||||
|
||||
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
||||
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||
return { local: 0, remote: 0 }
|
||||
|
|
@ -433,7 +448,7 @@ export default class {
|
|||
const r = res.response
|
||||
return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 }
|
||||
}
|
||||
async PayInvoice(invoice: string, amount: number, feeLimit: number, decodedAmount: number, { useProvider, from }: TxActionOptions, paymentIndexCb?: (index: number) => void): Promise<PaidInvoice> {
|
||||
async PayInvoice(invoice: string, amount: number, { routingFeeLimit, serviceFee }: { routingFeeLimit: number, serviceFee: number }, decodedAmount: number, { useProvider, from }: TxActionOptions, paymentIndexCb?: (index: number) => void): Promise<PaidInvoice> {
|
||||
// console.log("Paying invoice")
|
||||
if (this.outgoingOpsLocked) {
|
||||
this.log("outgoing ops locked, rejecting payment request")
|
||||
|
|
@ -442,14 +457,14 @@ export default class {
|
|||
// Force use of provider when bypass is enabled
|
||||
const mustUseProvider = this.liquidProvider.getSettings().useOnlyLiquidityProvider || useProvider
|
||||
if (mustUseProvider) {
|
||||
const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from)
|
||||
const providerDst = this.liquidProvider.GetProviderDestination()
|
||||
return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst }
|
||||
const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from, serviceFee)
|
||||
const providerPubkey = this.liquidProvider.GetProviderPubkey()
|
||||
return { feeSat: res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerPubkey }
|
||||
}
|
||||
await this.Health()
|
||||
try {
|
||||
const abortController = new AbortController()
|
||||
const req = PayInvoiceReq(invoice, amount, feeLimit)
|
||||
const req = PayInvoiceReq(invoice, amount, routingFeeLimit)
|
||||
const stream = this.router.sendPaymentV2(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onError(error => {
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export class FlashsatsLSP extends LSP {
|
|||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11_invoice, decoded.numSatoshis, 'system')
|
||||
const fees = +order.payment.fee_total_sat + res.network_fee + res.service_fee
|
||||
const fees = +order.payment.fee_total_sat + res.service_fee
|
||||
this.log("paid", res.amount_paid, "to open channel, and a fee of", fees)
|
||||
return { orderId: order.order_id, invoice: order.payment.bolt11_invoice, totalSats: +order.payment.order_total_sat, fees }
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ export class OlympusLSP extends LSP {
|
|||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11.invoice, decoded.numSatoshis, 'system')
|
||||
const fees = +order.payment.bolt11.fee_total_sat + res.network_fee + res.service_fee
|
||||
const fees = +order.payment.bolt11.fee_total_sat + res.service_fee
|
||||
this.log("paid", res.amount_paid, "to open channel, and a fee of", fees)
|
||||
return { orderId: order.order_id, invoice: order.payment.bolt11.invoice, totalSats: +order.payment.bolt11.order_total_sat, fees }
|
||||
}
|
||||
|
|
@ -279,7 +279,7 @@ export class VoltageLSP extends LSP {
|
|||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(proposalRes.jit_bolt11, decoded.numSatoshis, 'system')
|
||||
const fees = feeSats + res.network_fee + res.service_fee
|
||||
const fees = feeSats + res.service_fee
|
||||
this.log("paid", res.amount_paid, "to open channel, and a fee of", fees)
|
||||
return { orderId: fee.id, invoice: proposalRes.jit_bolt11, totalSats: decoded.numSatoshis, fees }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export type NodeInfo = {
|
|||
}
|
||||
export type Invoice = {
|
||||
payRequest: string
|
||||
providerDst?: string
|
||||
providerPubkey?: string
|
||||
}
|
||||
export type DecodedInvoice = {
|
||||
numSatoshis: number
|
||||
|
|
@ -45,7 +45,7 @@ export type PaidInvoice = {
|
|||
feeSat: number
|
||||
valueSat: number
|
||||
paymentPreimage: string
|
||||
providerDst?: string
|
||||
providerPubkey?: string
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
657
src/services/lnd/swaps.ts
Normal file
657
src/services/lnd/swaps.ts
Normal file
|
|
@ -0,0 +1,657 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -5,7 +5,9 @@ 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";
|
||||
export class AdminManager {
|
||||
settings: SettingsManager
|
||||
storage: Storage
|
||||
log = getLogger({ component: "adminManager" })
|
||||
adminNpub = ""
|
||||
|
|
@ -17,10 +19,13 @@ export class AdminManager {
|
|||
interval: NodeJS.Timer
|
||||
appNprofile: string
|
||||
lnd: LND
|
||||
swaps: Swaps
|
||||
nostrConnected: boolean = false
|
||||
private nostrReset: () => Promise<void> = async () => { this.log("nostr reset not initialized yet") }
|
||||
constructor(settings: SettingsManager, storage: Storage) {
|
||||
constructor(settings: SettingsManager, storage: Storage, swaps: Swaps) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
this.swaps = swaps
|
||||
this.dataDir = settings.getStorageSettings().dataDir
|
||||
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
|
||||
this.adminEnrollTokenPath = getDataPath(this.dataDir, 'admin.enroll')
|
||||
|
|
@ -45,6 +50,7 @@ export class AdminManager {
|
|||
|
||||
setLND = (lnd: LND) => {
|
||||
this.lnd = lnd
|
||||
this.swaps.SetLnd(lnd)
|
||||
}
|
||||
|
||||
setNostrConnected = (connected: boolean) => {
|
||||
|
|
@ -253,6 +259,29 @@ export class AdminManager {
|
|||
closing_txid: Buffer.from(res.txid).toString('hex')
|
||||
}
|
||||
}
|
||||
|
||||
async ListAdminSwaps(): Promise<Types.SwapsList> {
|
||||
return this.swaps.ListSwaps("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> {
|
||||
const routingFloor = this.settings.getSettings().lndSettings.routingFeeFloor
|
||||
const routingLimit = this.settings.getSettings().lndSettings.routingFeeLimitBps / 10000
|
||||
|
||||
const swap = await this.swaps.PayAddrWithSwap("admin", req.swap_operation_id, req.address, async (invoice, amt) => {
|
||||
const r = Math.max(Math.ceil(routingLimit * amt), routingFloor)
|
||||
const payment = await this.lnd.PayInvoice(invoice, 0, { routingFeeLimit: r, serviceFee: 0 }, amt, { useProvider: false, from: 'system' })
|
||||
await this.storage.metricsStorage.AddRootOperation("invoice_payment", invoice, amt + payment.feeSat)
|
||||
})
|
||||
return {
|
||||
tx_id: swap.txId,
|
||||
network_fee: swap.network_fee,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getDataPath = (dataDir: string, dataPath: string) => {
|
||||
|
|
|
|||
|
|
@ -69,14 +69,15 @@ export default class {
|
|||
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
||||
}
|
||||
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||
const { max, serviceFeeFloor, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats)
|
||||
return {
|
||||
userId: ctx.user_id,
|
||||
balance: user.balance_sats,
|
||||
max_withdrawable: this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true),
|
||||
max_withdrawable: max,
|
||||
user_identifier: appUser.identifier,
|
||||
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps,
|
||||
network_max_fee_bps: 0,
|
||||
network_max_fee_fixed: serviceFeeFloor,
|
||||
service_fee_bps: serviceFeeBps,
|
||||
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
||||
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),
|
||||
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),
|
||||
|
|
@ -104,14 +105,9 @@ export default class {
|
|||
return this.applicationManager.PayAppUserInvoice(ctx.app_id, {
|
||||
amount: req.amount,
|
||||
invoice: req.invoice,
|
||||
user_identifier: ctx.app_user_id
|
||||
})
|
||||
}
|
||||
async PayAddress(ctx: Types.UserContext, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
return this.applicationManager.PayAppUserInvoice(ctx.app_id, {
|
||||
amount: req.amount,
|
||||
invoice: req.invoice,
|
||||
user_identifier: ctx.app_user_id
|
||||
user_identifier: ctx.app_user_id,
|
||||
debit_npub: req.debit_npub,
|
||||
expected_fees: req.expected_fees,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ type NsecLinkingData = {
|
|||
expiry: number
|
||||
}
|
||||
export default class {
|
||||
|
||||
storage: Storage
|
||||
settings: SettingsManager
|
||||
paymentManager: PaymentManager
|
||||
|
|
@ -47,12 +46,13 @@ export default class {
|
|||
}, 60 * 1000); // 1 minute
|
||||
}
|
||||
|
||||
async StartAppsServiceBeacon(publishBeacon: (app: Application) => void) {
|
||||
async StartAppsServiceBeacon(publishBeacon: (app: Application, fees: Types.CumulativeFees) => void) {
|
||||
this.serviceBeaconInterval = setInterval(async () => {
|
||||
try {
|
||||
const fees = this.paymentManager.GetFees()
|
||||
const apps = await this.storage.applicationStorage.GetApplications()
|
||||
apps.forEach(app => {
|
||||
publishBeacon(app)
|
||||
publishBeacon(app, fees)
|
||||
})
|
||||
} catch (e) {
|
||||
this.log("error in beacon", (e as any).message)
|
||||
|
|
@ -154,17 +154,17 @@ export default class {
|
|||
|
||||
const ndebitString = ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] })
|
||||
log("🔗 [DEBUG] Generated ndebit for user", { userId: u.user.user_id, ndebit: ndebitString })
|
||||
|
||||
const { max, serviceFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
|
||||
return {
|
||||
identifier: u.identifier,
|
||||
info: {
|
||||
userId: u.user.user_id,
|
||||
balance: u.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true),
|
||||
max_withdrawable: max,
|
||||
user_identifier: u.identifier,
|
||||
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps,
|
||||
network_max_fee_bps: 0,
|
||||
network_max_fee_fixed: serviceFeeFloor,
|
||||
service_fee_bps: serviceFeeBps,
|
||||
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
||||
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
|
||||
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
|
||||
|
|
@ -172,7 +172,7 @@ export default class {
|
|||
bridge_url: this.settings.getSettings().serviceSettings.bridgeUrl
|
||||
|
||||
},
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
|
||||
max_withdrawable: max
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,16 +213,16 @@ export default class {
|
|||
async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
||||
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||
const { max, serviceFeeFloor, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
|
||||
return {
|
||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true),
|
||||
max_withdrawable: max,
|
||||
user_identifier: user.identifier,
|
||||
network_max_fee_bps: this.settings.getSettings().lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.getSettings().lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFeeBps,
|
||||
network_max_fee_bps: 0,
|
||||
network_max_fee_fixed: serviceFeeFloor,
|
||||
service_fee_bps: serviceFeeBps,
|
||||
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
|
||||
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),
|
||||
nmanage: nmanageEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),
|
||||
|
|
@ -235,9 +235,31 @@ export default class {
|
|||
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||
const paid = await this.paymentManager.PayInvoice(appUser.user.user_id, req, app)
|
||||
getLogger({ appName: app.name })(appUser.identifier, "invoice paid", paid.amount_paid, "sats")
|
||||
return paid
|
||||
try {
|
||||
const paid = await this.paymentManager.PayInvoice(appUser.user.user_id, req, app, {
|
||||
ack: pendingOp => { this.notifyAppUserPayment(appUser, pendingOp) }
|
||||
})
|
||||
this.notifyAppUserPayment(appUser, paid.operation)
|
||||
getLogger({ appName: app.name })(appUser.identifier, "invoice paid", paid.amount_paid, "sats")
|
||||
return paid
|
||||
} catch (e) {
|
||||
const failedOp: Types.UserOperation = {
|
||||
type: Types.UserOperationType.OUTGOING_INVOICE,
|
||||
paidAtUnix: -1, amount: 0, confirmed: false, identifier: req.invoice, operationId: "",
|
||||
inbound: false, internal: false, network_fee: 0, service_fee: 0, tx_hash: "",
|
||||
}
|
||||
this.notifyAppUserPayment(appUser, failedOp)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
notifyAppUserPayment = (appUser: ApplicationUser, op: Types.UserOperation) => {
|
||||
const balance = appUser.user.balance_sats
|
||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } =
|
||||
{ operation: op, requestId: "GetLiveUserOperations", status: 'OK', latest_balance: balance }
|
||||
if (appUser.nostr_public_key) { // TODO - fix before support for http streams
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: appUser.application.app_id }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
|
||||
}
|
||||
}
|
||||
|
||||
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -6,20 +6,16 @@ import { ERROR, getLogger } from "../helpers/logger.js";
|
|||
import { DebitAccess, DebitAccessRules } from '../storage/entity/DebitAccess.js';
|
||||
import { Application } from '../storage/entity/Application.js';
|
||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js';
|
||||
import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js';
|
||||
import { UnsignedEvent } from 'nostr-tools';
|
||||
import { NostrEvent } from '../nostr/nostrPool.js';
|
||||
import { Ndebit, NdebitData, NdebitFailure, NdebitSuccess, RecurringDebitTimeUnit } from "@shocknet/clink-sdk";
|
||||
import { debitAccessRulesToDebitRules, newNdebitResponse,debitRulesToDebitAccessRules,
|
||||
import {
|
||||
debitAccessRulesToDebitRules, newNdebitResponse, debitRulesToDebitAccessRules,
|
||||
nofferErrors, AuthRequiredRes, HandleNdebitRes, expirationRuleName,
|
||||
frequencyRuleName,IntervalTypeToSeconds,unitToIntervalType } from "./debitTypes.js";
|
||||
frequencyRuleName, IntervalTypeToSeconds, unitToIntervalType
|
||||
} from "./debitTypes.js";
|
||||
|
||||
export class DebitManager {
|
||||
|
||||
|
||||
_nostrSend: NostrSend | null = null
|
||||
|
||||
applicationManager: ApplicationManager
|
||||
|
||||
storage: Storage
|
||||
lnd: LND
|
||||
logger = getLogger({ component: 'DebitManager' })
|
||||
|
|
@ -29,16 +25,6 @@ export class DebitManager {
|
|||
this.applicationManager = applicationManager
|
||||
}
|
||||
|
||||
attachNostrSend = (nostrSend: NostrSend) => {
|
||||
this._nostrSend = nostrSend
|
||||
}
|
||||
nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
|
||||
if (!this._nostrSend) {
|
||||
throw new Error("No nostrSend attached")
|
||||
}
|
||||
this._nostrSend(initiator, data, relays)
|
||||
}
|
||||
|
||||
GetDebitAuthorizations = async (ctx: Types.UserContext): Promise<Types.DebitAuthorizations> => {
|
||||
const allDebitsAccesses = await this.storage.debitStorage.GetAllUserDebitAccess(ctx.app_user_id)
|
||||
const debits: Types.DebitAuthorization[] = allDebitsAccesses.map(access => ({
|
||||
|
|
@ -72,7 +58,7 @@ export class DebitManager {
|
|||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: req.npub, id: req.request_id, appId: ctx.app_id })
|
||||
return
|
||||
case Types.DebitResponse_response_type.INVOICE:
|
||||
await this.paySingleInvoice(ctx, {invoice: req.response.invoice, npub: req.npub, request_id: req.request_id})
|
||||
await this.paySingleInvoice(ctx, { invoice: req.response.invoice, npub: req.npub, request_id: req.request_id })
|
||||
return
|
||||
case Types.DebitResponse_response_type.AUTHORIZE:
|
||||
await this.handleAuthorization(ctx, req.response.authorize, { npub: req.npub, request_id: req.request_id })
|
||||
|
|
@ -82,14 +68,12 @@ export class DebitManager {
|
|||
}
|
||||
}
|
||||
|
||||
paySingleInvoice = async (ctx: Types.UserContext, {invoice,npub,request_id}:{invoice:string, npub:string, request_id:string}) => {
|
||||
paySingleInvoice = async (ctx: Types.UserContext, { invoice, npub, request_id }: { invoice: string, npub: string, request_id: string }) => {
|
||||
try {
|
||||
this.logger("🔍 [DEBIT REQUEST] Paying single invoice")
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id)
|
||||
const { op, payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
||||
const { payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
||||
const debitRes: NdebitSuccess = { res: 'ok', preimage: payment.preimage }
|
||||
this.notifyPaymentSuccess(appUser, debitRes, op, { appId: ctx.app_id, pub: npub, id: request_id })
|
||||
this.notifyPaymentSuccess(debitRes, { appId: ctx.app_id, pub: npub, id: request_id })
|
||||
} catch (e: any) {
|
||||
this.logger("❌ [DEBIT REQUEST] Error in single invoice payment")
|
||||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: npub, id: request_id, appId: ctx.app_id })
|
||||
|
|
@ -97,7 +81,7 @@ export class DebitManager {
|
|||
}
|
||||
}
|
||||
|
||||
handleAuthorization = async (ctx: Types.UserContext,debit:Types.DebitToAuthorize, {npub,request_id}:{ npub:string, request_id:string})=>{
|
||||
handleAuthorization = async (ctx: Types.UserContext, debit: Types.DebitToAuthorize, { npub, request_id }: { npub: string, request_id: string }) => {
|
||||
this.logger("🔍 [DEBIT REQUEST] Handling authorization", {
|
||||
npub,
|
||||
request_id,
|
||||
|
|
@ -122,9 +106,9 @@ export class DebitManager {
|
|||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id)
|
||||
this.validateAccessRules(access, app, appUser)
|
||||
this.logger("🔍 [DEBIT REQUEST] Sending debit payment")
|
||||
const { op, payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
||||
const { payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
||||
const debitRes: NdebitSuccess = { res: 'ok', preimage: payment.preimage }
|
||||
this.notifyPaymentSuccess(appUser, debitRes, op, { appId: ctx.app_id, pub: npub, id: request_id })
|
||||
this.notifyPaymentSuccess(debitRes, { appId: ctx.app_id, pub: npub, id: request_id })
|
||||
} catch (e: any) {
|
||||
this.logger("❌ [DEBIT REQUEST] Error in debit authorization")
|
||||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: npub, id: request_id, appId: ctx.app_id })
|
||||
|
|
@ -134,8 +118,8 @@ export class DebitManager {
|
|||
}
|
||||
|
||||
handleNip68Debit = async (pointerdata: NdebitData, event: NostrEvent) => {
|
||||
if (!this._nostrSend) {
|
||||
throw new Error("No nostrSend attached")
|
||||
if (!this.storage.NostrSender().IsReady()) {
|
||||
throw new Error("Nostr sender not ready")
|
||||
}
|
||||
this.logger("📥 [DEBIT REQUEST] Received debit request", {
|
||||
fromPub: event.pub,
|
||||
|
|
@ -144,10 +128,10 @@ export class DebitManager {
|
|||
pointerdata
|
||||
})
|
||||
const res = await this.payNdebitInvoice(event, pointerdata)
|
||||
this.logger("🔍 [DEBIT REQUEST] Sending ",res.status," response")
|
||||
this.logger("🔍 [DEBIT REQUEST] Sending ", res.status, " response")
|
||||
if (res.status === 'fail' || res.status === 'authOk') {
|
||||
const e = newNdebitResponse(JSON.stringify(res.debitRes), event)
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
return
|
||||
}
|
||||
const { appUser } = res
|
||||
|
|
@ -155,30 +139,27 @@ export class DebitManager {
|
|||
this.handleAuthRequired(pointerdata, event, res)
|
||||
return
|
||||
}
|
||||
const { op, debitRes } = res
|
||||
this.notifyPaymentSuccess(appUser, debitRes, op, event)
|
||||
const { debitRes } = res
|
||||
this.notifyPaymentSuccess(debitRes, event)
|
||||
}
|
||||
|
||||
handleAuthRequired = (data:NdebitData, event: NostrEvent, res: AuthRequiredRes) => {
|
||||
handleAuthRequired = (data: NdebitData, event: NostrEvent, res: AuthRequiredRes) => {
|
||||
if (!res.appUser.nostr_public_key) {
|
||||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: event.pub, id: event.id, appId: event.appId })
|
||||
return
|
||||
}
|
||||
const message: Types.LiveDebitRequest & { requestId: string, status: 'OK' } = { ...res.liveDebitReq, requestId: "GetLiveDebitRequests", status: 'OK' }
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: res.appUser.nostr_public_key })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: res.appUser.nostr_public_key })
|
||||
}
|
||||
|
||||
notifyPaymentSuccess = (appUser: ApplicationUser, debitRes: NdebitSuccess, op: Types.UserOperation, event: { pub: string, id: string, appId: string }) => {
|
||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
|
||||
if (appUser.nostr_public_key) { // TODO - fix before support for http streams
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
|
||||
}
|
||||
notifyPaymentSuccess = (debitRes: NdebitSuccess, event: { pub: string, id: string, appId: string }) => {
|
||||
this.sendDebitResponse(debitRes, event)
|
||||
}
|
||||
|
||||
sendDebitResponse = (debitRes: NdebitFailure | NdebitSuccess, event: { pub: string, id: string, appId: string }) => {
|
||||
const e = newNdebitResponse(JSON.stringify(debitRes), event)
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
|
||||
}
|
||||
|
||||
payNdebitInvoice = async (event: NostrEvent, pointerdata: NdebitData): Promise<HandleNdebitRes> => {
|
||||
|
|
@ -286,15 +267,14 @@ export class DebitManager {
|
|||
}
|
||||
await this.validateAccessRules(authorization, app, appUser)
|
||||
this.logger("🔍 [DEBIT REQUEST] Sending requested debit payment")
|
||||
const { op, payment } = await this.sendDebitPayment(appId, appUserId, requestorPub, bolt11)
|
||||
return { status: 'invoicePaid', op, app, appUser, debitRes: { res: 'ok', preimage: payment.preimage } }
|
||||
const { payment } = await this.sendDebitPayment(appId, appUserId, requestorPub, bolt11)
|
||||
return { status: 'invoicePaid', app, appUser, debitRes: { res: 'ok', preimage: payment.preimage } }
|
||||
}
|
||||
|
||||
sendDebitPayment = async (appId: string, appUserId: string, requestorPub: string, bolt11: string) => {
|
||||
const payment = await this.applicationManager.PayAppUserInvoice(appId, { amount: 0, invoice: bolt11, user_identifier: appUserId, debit_npub: requestorPub })
|
||||
await this.storage.debitStorage.IncrementDebitAccess(appUserId, requestorPub, payment.amount_paid + payment.service_fee + payment.network_fee)
|
||||
const op = this.newPaymentOperation(payment, bolt11)
|
||||
return { payment, op }
|
||||
await this.storage.debitStorage.IncrementDebitAccess(appUserId, requestorPub, payment.amount_paid + payment.service_fee)
|
||||
return { payment }
|
||||
}
|
||||
|
||||
validateAccessRules = async (access: DebitAccess, app: Application, appUser: ApplicationUser): Promise<boolean> => {
|
||||
|
|
@ -325,21 +305,5 @@ export class DebitManager {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
newPaymentOperation = (payment: Types.PayInvoiceResponse, bolt11: string) => {
|
||||
return {
|
||||
amount: payment.amount_paid,
|
||||
paidAtUnix: Math.floor(Date.now() / 1000),
|
||||
inbound: false,
|
||||
type: Types.UserOperationType.OUTGOING_INVOICE,
|
||||
identifier: bolt11,
|
||||
operationId: payment.operation_id,
|
||||
network_fee: payment.network_fee,
|
||||
service_fee: payment.service_fee,
|
||||
confirmed: true,
|
||||
tx_hash: "",
|
||||
internal: payment.network_fee === 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import * as Types from "../../../proto/autogenerated/ts/types.js";
|
||||
import { DebitAccessRules } from '../storage/entity/DebitAccess.js';
|
||||
import { DebitAccessRules } from '../storage/entity/DebitAccess.js';
|
||||
import { Application } from '../storage/entity/Application.js';
|
||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js';
|
||||
import { UnsignedEvent } from 'nostr-tools';
|
||||
import { NdebitFailure, NdebitSuccess, RecurringDebitTimeUnit } from "@shocknet/clink-sdk";
|
||||
import { NdebitFailure, NdebitSuccess, RecurringDebitTimeUnit } from "@shocknet/clink-sdk";
|
||||
|
||||
export const expirationRuleName = 'expiration'
|
||||
export const frequencyRuleName = 'frequency'
|
||||
|
|
@ -96,7 +96,7 @@ export const nofferErrors = {
|
|||
}
|
||||
export type AuthRequiredRes = { status: 'authRequired', liveDebitReq: Types.LiveDebitRequest, app: Application, appUser: ApplicationUser }
|
||||
export type HandleNdebitRes = { status: 'fail', debitRes: NdebitFailure }
|
||||
| { status: 'invoicePaid', op: Types.UserOperation, app: Application, appUser: ApplicationUser, debitRes: NdebitSuccess }
|
||||
| { status: 'invoicePaid', app: Application, appUser: ApplicationUser, debitRes: NdebitSuccess }
|
||||
| AuthRequiredRes
|
||||
| { status: 'authOk', debitRes: NdebitSuccess }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import AppUserManager from "./appUserManager.js"
|
|||
import { Application } from '../storage/entity/Application.js'
|
||||
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
|
||||
import { UnsignedEvent } from 'nostr-tools'
|
||||
import { NostrSend } from '../nostr/handler.js'
|
||||
import { NostrSend } from '../nostr/nostrPool.js'
|
||||
import MetricsManager from '../metrics/index.js'
|
||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||
import { LiquidityManager } from "./liquidityManager.js"
|
||||
|
|
@ -30,7 +30,7 @@ import { Agent } from "https"
|
|||
import { NotificationsManager } from "./notificationsManager.js"
|
||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
||||
import SettingsManager from './settingsManager.js'
|
||||
import { NostrSettings } from '../nostr/handler.js'
|
||||
import { NostrSettings, AppInfo } from '../nostr/nostrPool.js'
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
newIncomingInvoice: (operation: Types.UserOperation) => void
|
||||
|
|
@ -61,8 +61,6 @@ export default class {
|
|||
rugPullTracker: RugPullTracker
|
||||
unlocker: Unlocker
|
||||
notificationsManager: NotificationsManager
|
||||
//webRTC: webRTC
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
nostrProcessPing: (() => Promise<void>) | null = null
|
||||
nostrReset: (settings: NostrSettings) => void = () => { getLogger({})("nostr reset not initialized yet") }
|
||||
constructor(settings: SettingsManager, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) {
|
||||
|
|
@ -82,7 +80,7 @@ export default class {
|
|||
this.liquidityManager = new LiquidityManager(this.settings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||
|
||||
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.paymentManager = new PaymentManager(this.storage, this.metricsManager, this.lnd, adminManager.swaps, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
|
||||
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
|
||||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||
|
|
@ -102,19 +100,13 @@ export default class {
|
|||
}
|
||||
|
||||
StartBeacons() {
|
||||
this.applicationManager.StartAppsServiceBeacon(app => {
|
||||
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url })
|
||||
this.applicationManager.StartAppsServiceBeacon((app, fees) => {
|
||||
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, fees })
|
||||
})
|
||||
}
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
this.liquidityProvider.attachNostrSend(f)
|
||||
this.debitManager.attachNostrSend(f)
|
||||
this.offerManager.attachNostrSend(f)
|
||||
this.managementManager.attachNostrSend(f)
|
||||
this.utils.attachNostrSend(f)
|
||||
//this.webRTC.attachNostrSend(f)
|
||||
this.utils.nostrSender.AttachNostrSend(f)
|
||||
}
|
||||
|
||||
attachNostrProcessPing(f: () => Promise<void>) {
|
||||
|
|
@ -168,7 +160,8 @@ export default class {
|
|||
NewBlockHandler = async (height: number) => {
|
||||
let confirmed: (PendingTx & { confs: number; })[]
|
||||
let log = getLogger({})
|
||||
|
||||
this.storage.paymentStorage.DeleteExpiredTransactionSwaps(height)
|
||||
.catch(err => log(ERROR, "failed to delete expired transaction swaps", err.message || err))
|
||||
try {
|
||||
const balanceEvents = await this.paymentManager.GetLndBalance()
|
||||
await this.metricsManager.NewBlockCb(height, balanceEvents)
|
||||
|
|
@ -220,6 +213,10 @@ export default class {
|
|||
const { blockHeight } = await this.lnd.GetInfo()
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
||||
if (!userAddress) {
|
||||
const isChange = await this.lnd.IsChangeAddress(address)
|
||||
if (isChange) {
|
||||
return
|
||||
}
|
||||
await this.metricsManager.AddRootAddressPaid(address, txOutput, amount)
|
||||
return
|
||||
}
|
||||
|
|
@ -230,11 +227,8 @@ export default class {
|
|||
return
|
||||
}
|
||||
log = getLogger({ appName: userAddress.linkedApplication.name })
|
||||
const isAppUserPayment = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id
|
||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_TX, amount, isAppUserPayment)
|
||||
if (userAddress.linkedApplication && userAddress.linkedApplication.owner.user_id === userAddress.user.user_id) {
|
||||
fee = 0
|
||||
}
|
||||
const isManagedUser = userAddress.user.user_id !== userAddress.linkedApplication.owner.user_id
|
||||
const fee = this.paymentManager.getReceiveServiceFee(Types.UserOperationType.INCOMING_TX, amount, isManagedUser)
|
||||
try {
|
||||
// This call will fail if the transaction is already registered
|
||||
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, internal, blockHeight, tx)
|
||||
|
|
@ -274,11 +268,8 @@ export default class {
|
|||
return
|
||||
}
|
||||
log = getLogger({ appName: userInvoice.linkedApplication.name })
|
||||
const isAppUserPayment = userInvoice.user.user_id !== userInvoice.linkedApplication.owner.user_id
|
||||
let fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount, isAppUserPayment)
|
||||
if (userInvoice.linkedApplication && userInvoice.linkedApplication.owner.user_id === userInvoice.user.user_id) {
|
||||
fee = 0
|
||||
}
|
||||
const isManagedUser = userInvoice.user.user_id !== userInvoice.linkedApplication.owner.user_id
|
||||
const fee = this.paymentManager.getReceiveServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount, isManagedUser)
|
||||
try {
|
||||
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, internal, tx)
|
||||
this.storage.eventsLog.LogEvent({ type: 'invoice_paid', userId: userInvoice.user.user_id, appId: userInvoice.linkedApplication.app_id, appUserId: "", balance: userInvoice.user.balance_sats, data: paymentRequest, amount })
|
||||
|
|
@ -385,10 +376,11 @@ export default class {
|
|||
getLogger({ appName: app.name })("cannot notify user, not a nostr user")
|
||||
return
|
||||
}
|
||||
|
||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
|
||||
const balance = user.user.balance_sats
|
||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } =
|
||||
{ operation: op, requestId: "GetLiveUserOperations", status: 'OK', latest_balance: balance }
|
||||
const j = JSON.stringify(message)
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'content', content: j, pub: user.nostr_public_key })
|
||||
this.utils.nostrSender.Send({ type: 'app', appId: app.app_id }, { type: 'content', content: j, pub: user.nostr_public_key })
|
||||
this.SendEncryptedNotification(app, user, op)
|
||||
}
|
||||
|
||||
|
|
@ -408,7 +400,7 @@ export default class {
|
|||
})
|
||||
}
|
||||
|
||||
async UpdateBeacon(app: Application, content: { type: 'service', name: string, avatarUrl?: string, nextRelay?: string }) {
|
||||
async UpdateBeacon(app: Application, content: Types.BeaconData) {
|
||||
if (!app.nostr_public_key) {
|
||||
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
||||
return
|
||||
|
|
@ -421,7 +413,7 @@ export default class {
|
|||
pubkey: app.nostr_public_key,
|
||||
tags,
|
||||
}
|
||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'event', event })
|
||||
this.utils.nostrSender.Send({ type: 'app', appId: app.app_id }, { type: 'event', event })
|
||||
}
|
||||
|
||||
async createZapReceipt(log: PubLogger, invoice: UserReceivingInvoice) {
|
||||
|
|
@ -441,7 +433,7 @@ export default class {
|
|||
tags,
|
||||
}
|
||||
log({ unsigned: event })
|
||||
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
|
||||
this.utils.nostrSender.Send({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
|
||||
}
|
||||
|
||||
async createClinkReceipt(log: PubLogger, invoice: UserReceivingInvoice) {
|
||||
|
|
@ -465,7 +457,7 @@ export default class {
|
|||
["clink_version", "1"]
|
||||
],
|
||||
}
|
||||
this.nostrSend(
|
||||
this.utils.nostrSender.Send(
|
||||
{ type: 'app', appId: invoice.linkedApplication.app_id },
|
||||
{ type: 'event', event, encrypt: { toPub: invoice.clink_requester_pub } }
|
||||
)
|
||||
|
|
@ -474,28 +466,38 @@ export default class {
|
|||
async ResetNostr() {
|
||||
const apps = await this.storage.applicationStorage.GetApplications()
|
||||
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
||||
const fees = this.paymentManager.GetFees()
|
||||
for (const app of apps) {
|
||||
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay })
|
||||
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay, fees })
|
||||
}
|
||||
|
||||
const defaultNames = ['wallet', 'wallet-test', this.settings.getSettings().serviceSettings.defaultAppName]
|
||||
const liquidityProviderApp = apps.find(app => defaultNames.includes(app.name))
|
||||
if (!liquidityProviderApp) {
|
||||
throw new Error("wallet app not initialized correctly")
|
||||
}
|
||||
const liquidityProviderInfo = {
|
||||
privateKey: liquidityProviderApp.nostr_private_key || "",
|
||||
publicKey: liquidityProviderApp.nostr_public_key || "",
|
||||
name: "liquidity_provider", clientId: `client_${liquidityProviderApp.app_id}`
|
||||
const local = apps.find(app => defaultNames.includes(app.name))
|
||||
if (!local) {
|
||||
throw new Error("local app not initialized correctly")
|
||||
}
|
||||
this.liquidityProvider.setNostrInfo({ localId: `client_${local.app_id}`, localPubkey: local.nostr_public_key || "" })
|
||||
const relays = this.settings.getSettings().nostrRelaySettings.relays
|
||||
const appsInfo: AppInfo[] = apps.map(app => {
|
||||
return {
|
||||
appId: app.app_id,
|
||||
privateKey: app.nostr_private_key || "",
|
||||
publicKey: app.nostr_public_key || "",
|
||||
name: app.name,
|
||||
provider: app.nostr_public_key === local.nostr_public_key ? {
|
||||
clientId: `client_${local.app_id}`,
|
||||
pubkey: this.settings.getSettings().liquiditySettings.liquidityProviderPub,
|
||||
relayUrl: this.settings.getSettings().liquiditySettings.providerRelayUrl
|
||||
} : undefined
|
||||
}
|
||||
})
|
||||
const s: NostrSettings = {
|
||||
apps: apps.map(a => ({ appId: a.app_id, name: a.name, privateKey: a.nostr_private_key || "", publicKey: a.nostr_public_key || "" })),
|
||||
relays: this.settings.getSettings().nostrRelaySettings.relays,
|
||||
apps: appsInfo,
|
||||
relays,
|
||||
maxEventContentLength: this.settings.getSettings().nostrRelaySettings.maxEventContentLength,
|
||||
clients: [liquidityProviderInfo]
|
||||
/* clients: [local],
|
||||
providerDestinationPub: this.settings.getSettings().liquiditySettings.liquidityProviderPub */
|
||||
}
|
||||
this.nostrReset(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import { Wizard } from "../wizard/index.js"
|
|||
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"
|
||||
export type AppData = {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
|
|
@ -18,7 +20,8 @@ export type AppData = {
|
|||
}
|
||||
|
||||
export const initSettings = async (log: PubLogger, storageSettings: StorageSettings): Promise<SettingsManager> => {
|
||||
const utils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages })
|
||||
const nostrSender = new NostrSender()
|
||||
const utils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages }, nostrSender)
|
||||
const storageManager = new Storage(storageSettings, utils)
|
||||
await storageManager.Connect(log)
|
||||
const settingsManager = new SettingsManager(storageManager)
|
||||
|
|
@ -30,7 +33,8 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM
|
|||
const utils = storageManager.utils
|
||||
const unlocker = new Unlocker(settingsManager, storageManager)
|
||||
await unlocker.Unlock()
|
||||
const adminManager = new AdminManager(settingsManager, storageManager)
|
||||
const swaps = new Swaps(settingsManager, storageManager)
|
||||
const adminManager = new AdminManager(settingsManager, storageManager, swaps)
|
||||
let wizard: Wizard | null = null
|
||||
if (settingsManager.getSettings().serviceSettings.wizard) {
|
||||
wizard = new Wizard(settingsManager, storageManager, adminManager)
|
||||
|
|
@ -61,26 +65,22 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM
|
|||
return { privateKey: app.nostr_private_key, publicKey: app.nostr_public_key, appId: app.app_id, name: app.name }
|
||||
}
|
||||
}))
|
||||
const liquidityProviderApp = apps.find(app => defaultNames.includes(app.name))
|
||||
if (!liquidityProviderApp) {
|
||||
throw new Error("wallet app not initialized correctly")
|
||||
const localProviderClient = apps.find(app => defaultNames.includes(app.name))
|
||||
if (!localProviderClient) {
|
||||
throw new Error("local app not initialized correctly")
|
||||
}
|
||||
const liquidityProviderInfo = {
|
||||
privateKey: liquidityProviderApp.privateKey,
|
||||
publicKey: liquidityProviderApp.publicKey,
|
||||
name: "liquidity_provider", clientId: `client_${liquidityProviderApp.appId}`
|
||||
}
|
||||
mainHandler.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
mainHandler.liquidityProvider.setNostrInfo({ localId: `client_${localProviderClient.appId}`, localPubkey: localProviderClient.publicKey })
|
||||
const stop = await processArgs(mainHandler)
|
||||
if (stop) {
|
||||
return
|
||||
}
|
||||
await mainHandler.paymentManager.checkPendingPayments()
|
||||
await mainHandler.paymentManager.checkPaymentStatus()
|
||||
await mainHandler.paymentManager.checkMissedChainTxs()
|
||||
await mainHandler.paymentManager.CleanupOldUnpaidInvoices()
|
||||
await mainHandler.appUserManager.CleanupInactiveUsers()
|
||||
await mainHandler.appUserManager.CleanupNeverActiveUsers()
|
||||
await mainHandler.paymentManager.watchDog.Start()
|
||||
return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp, wizard, adminManager }
|
||||
return { mainHandler, apps, localProviderClient, wizard, adminManager }
|
||||
}
|
||||
|
||||
const processArgs = async (mainHandler: Main) => {
|
||||
|
|
|
|||
|
|
@ -50,8 +50,11 @@ export class LiquidityManager {
|
|||
}
|
||||
|
||||
beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
|
||||
const providerReady = this.liquidityProvider.IsReady()
|
||||
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||
if (!providerReady) {
|
||||
throw new Error("cannot use liquidity provider, it is not ready")
|
||||
}
|
||||
return 'provider'
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +66,7 @@ export class LiquidityManager {
|
|||
if (remote > amount) {
|
||||
return 'lnd'
|
||||
}
|
||||
const providerCanHandle = await this.liquidityProvider.CanProviderHandle({ action: 'receive', amount })
|
||||
const providerCanHandle = this.liquidityProvider.IsReady()
|
||||
if (!providerCanHandle) {
|
||||
return 'lnd'
|
||||
}
|
||||
|
|
@ -82,16 +85,24 @@ export class LiquidityManager {
|
|||
}
|
||||
}
|
||||
|
||||
beforeOutInvoicePayment = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
beforeOutInvoicePayment = async (amount: number, localServiceFee: number): Promise<'lnd' | 'provider'> => {
|
||||
const providerReady = this.liquidityProvider.IsReady()
|
||||
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||
if (!providerReady) {
|
||||
throw new Error("cannot use liquidity provider, it is not ready")
|
||||
}
|
||||
return 'provider'
|
||||
}
|
||||
const canHandle = await this.liquidityProvider.CanProviderHandle({ action: 'spend', amount })
|
||||
if (canHandle) {
|
||||
return 'provider'
|
||||
if (!providerReady) {
|
||||
return 'lnd'
|
||||
}
|
||||
return 'lnd'
|
||||
const canHandle = await this.liquidityProvider.CanProviderPay(amount, localServiceFee)
|
||||
if (!canHandle) {
|
||||
return 'lnd'
|
||||
}
|
||||
return 'provider'
|
||||
}
|
||||
|
||||
afterOutInvoicePaid = async () => { }
|
||||
|
||||
shouldDrainProvider = async () => {
|
||||
|
|
@ -99,7 +110,7 @@ export class LiquidityManager {
|
|||
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||
return
|
||||
}
|
||||
const maxW = await this.liquidityProvider.GetLatestMaxWithdrawable()
|
||||
const maxW = await this.liquidityProvider.GetMaxWithdrawable()
|
||||
const { remote } = await this.lnd.ChannelBalance()
|
||||
const drainable = Math.min(maxW, remote)
|
||||
if (drainable < 500) {
|
||||
|
|
@ -129,7 +140,7 @@ export class LiquidityManager {
|
|||
try {
|
||||
const invoice = await this.lnd.NewInvoice(amt, "liqudity provider drain", defaultInvoiceExpiry, { from: 'system', useProvider: false })
|
||||
const res = await this.liquidityProvider.PayInvoice(invoice.payRequest, amt, 'system')
|
||||
const fees = res.network_fee + res.service_fee
|
||||
const fees = res.service_fee
|
||||
this.feesPaid += fees
|
||||
this.updateLatestDrain(true, amt)
|
||||
} catch (err: any) {
|
||||
|
|
@ -172,7 +183,7 @@ export class LiquidityManager {
|
|||
if (pendingChannels.pendingOpenChannels.length > 0) {
|
||||
return { shouldOpen: false }
|
||||
}
|
||||
const maxW = await this.liquidityProvider.GetLatestMaxWithdrawable()
|
||||
const maxW = await this.liquidityProvider.GetMaxWithdrawable()
|
||||
if (maxW < threshold) {
|
||||
return { shouldOpen: false }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,23 @@
|
|||
import newNostrClient from '../../../proto/autogenerated/ts/nostr_client.js'
|
||||
import { NostrRequest } from '../../../proto/autogenerated/ts/nostr_transport.js'
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import { ERROR, getLogger } from '../helpers/logger.js'
|
||||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
||||
import { InvoicePaidCb } from '../lnd/settings.js'
|
||||
import Storage from '../storage/index.js'
|
||||
import SettingsManager from './settingsManager.js'
|
||||
import { LiquiditySettings } from './settings.js'
|
||||
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
||||
|
||||
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||
export class LiquidityProvider {
|
||||
getSettings: () => LiquiditySettings
|
||||
client: ReturnType<typeof newNostrClient>
|
||||
clientCbs: Record<string, nostrCallback<any>> = {}
|
||||
clientId: string = ""
|
||||
myPub: string = ""
|
||||
localId: string = ""
|
||||
localPubkey: string = ""
|
||||
log = getLogger({ component: 'liquidityProvider' })
|
||||
nostrSend: NostrSend | null = null
|
||||
// nostrSend: NostrSend | null = null
|
||||
configured = false
|
||||
pubDestination: string
|
||||
providerPubkey: string
|
||||
ready: boolean
|
||||
invoicePaidCb: InvoicePaidCb
|
||||
connecting = false
|
||||
|
|
@ -28,14 +25,18 @@ export class LiquidityProvider {
|
|||
queue: ((state: 'ready') => void)[] = []
|
||||
utils: Utils
|
||||
pendingPayments: Record<string, number> = {}
|
||||
feesCache: Types.CumulativeFees | null = null
|
||||
lastSeenBeacon = 0
|
||||
latestReceivedBalance = 0
|
||||
incrementProviderBalance: (balance: number) => Promise<void>
|
||||
pendingPaymentsAck: Record<string, boolean> = {}
|
||||
// make the sub process accept client
|
||||
constructor(getSettings: () => LiquiditySettings, utils: Utils, invoicePaidCb: InvoicePaidCb, incrementProviderBalance: (balance: number) => Promise<any>) {
|
||||
this.utils = utils
|
||||
this.getSettings = getSettings
|
||||
const pubDestination = getSettings().liquidityProviderPub
|
||||
const providerPubkey = getSettings().liquidityProviderPub
|
||||
const disableLiquidityProvider = getSettings().disableLiquidityProvider
|
||||
if (!pubDestination) {
|
||||
if (!providerPubkey) {
|
||||
this.log("No pub provider to liquidity provider, will not be initialized")
|
||||
return
|
||||
}
|
||||
|
|
@ -43,19 +44,29 @@ export class LiquidityProvider {
|
|||
this.log("Liquidity provider is disabled, will not be initialized")
|
||||
return
|
||||
}
|
||||
this.log("connecting to liquidity provider:", pubDestination)
|
||||
this.pubDestination = pubDestination
|
||||
this.log("connecting to liquidity provider:", providerPubkey)
|
||||
this.providerPubkey = providerPubkey
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
this.incrementProviderBalance = incrementProviderBalance
|
||||
this.client = newNostrClient({
|
||||
pubDestination: this.pubDestination,
|
||||
retrieveNostrUserAuth: async () => this.myPub,
|
||||
retrieveNostrAdminAuth: async () => this.myPub,
|
||||
retrieveNostrMetricsAuth: async () => this.myPub,
|
||||
retrieveNostrGuestWithPubAuth: async () => this.myPub
|
||||
pubDestination: this.providerPubkey,
|
||||
retrieveNostrUserAuth: async () => this.localPubkey,
|
||||
retrieveNostrAdminAuth: async () => this.localPubkey,
|
||||
retrieveNostrMetricsAuth: async () => this.localPubkey,
|
||||
retrieveNostrGuestWithPubAuth: async () => this.localPubkey
|
||||
}, this.clientSend, this.clientSub)
|
||||
|
||||
this.utils.nostrSender.OnReady(() => {
|
||||
this.setSetIfConfigured()
|
||||
if (this.configured) {
|
||||
clearInterval(this.configuredInterval)
|
||||
this.Connect()
|
||||
}
|
||||
})
|
||||
this.configuredInterval = setInterval(() => {
|
||||
if (!this.configured && this.utils.nostrSender.IsReady()) {
|
||||
this.setSetIfConfigured()
|
||||
}
|
||||
if (this.configured) {
|
||||
clearInterval(this.configuredInterval)
|
||||
this.Connect()
|
||||
|
|
@ -63,16 +74,17 @@ export class LiquidityProvider {
|
|||
}, 1000)
|
||||
}
|
||||
|
||||
GetProviderDestination() {
|
||||
return this.pubDestination
|
||||
GetProviderPubkey() {
|
||||
return this.providerPubkey
|
||||
}
|
||||
|
||||
IsReady = () => {
|
||||
return this.ready && !this.getSettings().disableLiquidityProvider
|
||||
const seenInPast2Minutes = Date.now() - this.lastSeenBeacon < 1000 * 60 * 2
|
||||
return this.ready && !this.getSettings().disableLiquidityProvider && seenInPast2Minutes
|
||||
}
|
||||
|
||||
AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => {
|
||||
if (!this.pubDestination || this.getSettings().disableLiquidityProvider) {
|
||||
if (!this.providerPubkey || this.getSettings().disableLiquidityProvider) {
|
||||
return 'inactive'
|
||||
}
|
||||
if (this.IsReady()) {
|
||||
|
|
@ -94,6 +106,7 @@ export class LiquidityProvider {
|
|||
return
|
||||
}
|
||||
this.log("provider ready with balance:", res.status === 'OK' ? res.balance : 0)
|
||||
this.lastSeenBeacon = Date.now()
|
||||
this.ready = true
|
||||
this.queue.forEach(q => q('ready'))
|
||||
this.log("subbing to user operations")
|
||||
|
|
@ -107,9 +120,12 @@ export class LiquidityProvider {
|
|||
try {
|
||||
await this.invoicePaidCb(res.operation.identifier, res.operation.amount, 'provider')
|
||||
this.incrementProviderBalance(res.operation.amount)
|
||||
this.latestReceivedBalance = res.latest_balance
|
||||
} catch (err: any) {
|
||||
this.log("error processing incoming invoice", err.message)
|
||||
}
|
||||
} else if (res.operation.type === Types.UserOperationType.OUTGOING_INVOICE) {
|
||||
delete this.pendingPaymentsAck[res.operation.identifier]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -122,62 +138,78 @@ export class LiquidityProvider {
|
|||
}
|
||||
return res
|
||||
}
|
||||
this.feesCache = {
|
||||
serviceFeeFloor: res.network_max_fee_fixed,
|
||||
serviceFeeBps: res.service_fee_bps
|
||||
}
|
||||
this.latestReceivedBalance = res.balance
|
||||
this.utils.stateBundler.AddBalancePoint('providerBalance', res.balance)
|
||||
this.utils.stateBundler.AddBalancePoint('providerMaxWithdrawable', res.max_withdrawable)
|
||||
return res
|
||||
}
|
||||
|
||||
GetLatestMaxWithdrawable = async () => {
|
||||
if (!this.IsReady()) {
|
||||
return 0
|
||||
GetFees = () => {
|
||||
if (!this.feesCache) {
|
||||
throw new Error("fees not cached")
|
||||
}
|
||||
const res = await this.GetUserState()
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user info", res.reason)
|
||||
return 0
|
||||
}
|
||||
return res.max_withdrawable
|
||||
return this.feesCache
|
||||
}
|
||||
|
||||
GetLatestBalance = async () => {
|
||||
GetMaxWithdrawable = () => {
|
||||
if (!this.IsReady() || !this.feesCache) {
|
||||
return 0
|
||||
}
|
||||
const balance = this.latestReceivedBalance
|
||||
const { serviceFeeFloor, serviceFeeBps } = this.feesCache
|
||||
const div = 1 + (serviceFeeBps / 10000)
|
||||
const maxWithoutFixed = Math.floor(balance / div)
|
||||
const fee = balance - maxWithoutFixed
|
||||
const m = balance - Math.max(fee, serviceFeeFloor)
|
||||
return Math.max(m, 0)
|
||||
}
|
||||
|
||||
GetLatestBalance = () => {
|
||||
if (!this.IsReady()) {
|
||||
return 0
|
||||
}
|
||||
const res = await this.GetUserState()
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user info", res.reason)
|
||||
return 0
|
||||
}
|
||||
return res.balance
|
||||
return this.latestReceivedBalance
|
||||
}
|
||||
|
||||
GetPendingBalance = async () => {
|
||||
return Object.values(this.pendingPayments).reduce((a, b) => a + b, 0)
|
||||
}
|
||||
|
||||
CalculateExpectedFeeLimit = (amount: number, info: Types.UserInfo) => {
|
||||
const serviceFeeRate = info.service_fee_bps / 10000
|
||||
GetServiceFee = (amount: number, f?: Types.CumulativeFees) => {
|
||||
const fees = f ? f : this.GetFees()
|
||||
const serviceFeeRate = fees.serviceFeeBps / 10000
|
||||
const serviceFee = Math.ceil(serviceFeeRate * amount)
|
||||
const networkMaxFeeRate = info.network_max_fee_bps / 10000
|
||||
const networkFeeLimit = Math.ceil(amount * networkMaxFeeRate + info.network_max_fee_fixed)
|
||||
return serviceFee + networkFeeLimit
|
||||
return Math.max(serviceFee, fees.serviceFeeFloor)
|
||||
}
|
||||
|
||||
CanProviderHandle = async (req: LiquidityRequest) => {
|
||||
CanProviderPay = async (amount: number, localServiceFee: number): Promise<boolean> => {
|
||||
if (!this.IsReady()) {
|
||||
this.log("provider is not ready")
|
||||
return false
|
||||
}
|
||||
const maxW = await this.GetLatestMaxWithdrawable()
|
||||
if (req.action === 'spend') {
|
||||
return maxW > req.amount
|
||||
const maxW = this.GetMaxWithdrawable()
|
||||
if (maxW < amount) {
|
||||
this.log("provider does not have enough funds to pay the invoice")
|
||||
return false
|
||||
}
|
||||
|
||||
const providerServiceFee = this.GetServiceFee(amount)
|
||||
if (localServiceFee < providerServiceFee) {
|
||||
this.log(`local service fee ${localServiceFee} is less than the provider's service fee ${providerServiceFee}`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
AddInvoice = async (amount: number, memo: string, from: 'user' | 'system', expiry: number) => {
|
||||
try {
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
throw new Error("liquidity provider is not ready yet, disabled or unreachable")
|
||||
}
|
||||
const res = await this.client.NewInvoice({ amountSats: amount, memo, expiry })
|
||||
if (res.status === 'ERROR') {
|
||||
|
|
@ -193,23 +225,37 @@ export class LiquidityProvider {
|
|||
|
||||
}
|
||||
|
||||
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system') => {
|
||||
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system', feeLimit?: number) => {
|
||||
try {
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
throw new Error("liquidity provider is not ready yet, disabled or unreachable")
|
||||
}
|
||||
const userInfo = await this.GetUserState()
|
||||
if (userInfo.status === 'ERROR') {
|
||||
throw new Error(userInfo.reason)
|
||||
const fees = this.GetFees()
|
||||
const providerServiceFee = this.GetServiceFee(decodedAmount, fees)
|
||||
if (feeLimit && providerServiceFee > feeLimit) {
|
||||
this.log("fees", fees)
|
||||
this.log("provider service fee is greater than the fee limit", providerServiceFee, feeLimit)
|
||||
throw new Error("provider service fee is greater than the fee limit")
|
||||
}
|
||||
this.pendingPayments[invoice] = decodedAmount + this.CalculateExpectedFeeLimit(decodedAmount, userInfo)
|
||||
const res = await this.client.PayInvoice({ invoice, amount: 0 })
|
||||
this.pendingPayments[invoice] = decodedAmount + providerServiceFee
|
||||
const timeout = setTimeout(() => {
|
||||
if (!this.pendingPaymentsAck[invoice]) {
|
||||
return
|
||||
}
|
||||
this.log("10 seconds passed without a payment ack, locking provider until the next beacon")
|
||||
this.lastSeenBeacon = 0
|
||||
}, 1000 * 10)
|
||||
this.pendingPaymentsAck[invoice] = true
|
||||
const res = await this.client.PayInvoice({ invoice, amount: 0, expected_fees: fees })
|
||||
delete this.pendingPaymentsAck[invoice]
|
||||
clearTimeout(timeout)
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error paying invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
const totalPaid = res.amount_paid + res.network_fee + res.service_fee
|
||||
const totalPaid = res.amount_paid + res.service_fee
|
||||
this.incrementProviderBalance(-totalPaid).then(() => { delete this.pendingPayments[invoice] })
|
||||
this.latestReceivedBalance = res.latest_balance
|
||||
this.utils.stateBundler.AddTxPoint('paidAnInvoice', decodedAmount, { used: 'provider', from, timeDiscount: true })
|
||||
return res
|
||||
} catch (err) {
|
||||
|
|
@ -221,7 +267,7 @@ export class LiquidityProvider {
|
|||
|
||||
GetPaymentState = async (invoice: string) => {
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
throw new Error("liquidity provider is not ready yet, disabled or unreachable")
|
||||
}
|
||||
const res = await this.client.GetPaymentState({ invoice })
|
||||
if (res.status === 'ERROR') {
|
||||
|
|
@ -233,7 +279,7 @@ export class LiquidityProvider {
|
|||
|
||||
GetOperations = async () => {
|
||||
if (!this.IsReady()) {
|
||||
throw new Error("liquidity provider is not ready yet or disabled")
|
||||
throw new Error("liquidity provider is not ready yet, disabled or unreachable")
|
||||
}
|
||||
const res = await this.client.GetUserOperations({
|
||||
latestIncomingInvoice: { ts: 0, id: 0 }, latestOutgoingInvoice: { ts: 0, id: 0 },
|
||||
|
|
@ -247,31 +293,48 @@ export class LiquidityProvider {
|
|||
return res
|
||||
}
|
||||
|
||||
setNostrInfo = ({ clientId, myPub }: { myPub: string, clientId: string }) => {
|
||||
this.log("setting nostr info")
|
||||
this.clientId = clientId
|
||||
this.myPub = myPub
|
||||
setNostrInfo = ({ localId, localPubkey }: { localPubkey: string, localId: string }) => {
|
||||
this.localId = localId
|
||||
this.localPubkey = localPubkey
|
||||
this.setSetIfConfigured()
|
||||
// If nostrSender becomes ready after setNostrInfo, ensure we check again
|
||||
if (!this.configured && this.utils.nostrSender.IsReady()) {
|
||||
this.setSetIfConfigured()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.log("attaching nostrSend action")
|
||||
this.nostrSend = f
|
||||
this.setSetIfConfigured()
|
||||
}
|
||||
|
||||
setSetIfConfigured = () => {
|
||||
if (this.nostrSend && !!this.pubDestination && !!this.clientId && !!this.myPub) {
|
||||
this.configured = true
|
||||
this.log("configured to send to ", this.pubDestination)
|
||||
if (this.utils.nostrSender.IsReady() && !!this.providerPubkey && !!this.localId && !!this.localPubkey) {
|
||||
if (!this.configured) {
|
||||
this.configured = true
|
||||
}
|
||||
}
|
||||
}
|
||||
onBeaconEvent = async (beaconData: { content: string, pub: string }) => {
|
||||
if (beaconData.pub !== this.providerPubkey) {
|
||||
this.log(ERROR, "got beacon from invalid pub", beaconData.pub, this.providerPubkey)
|
||||
return
|
||||
}
|
||||
const beacon = JSON.parse(beaconData.content) as Types.BeaconData
|
||||
const err = Types.BeaconDataValidate(beacon)
|
||||
if (err) {
|
||||
this.log(ERROR, "error validating beacon data", err.message)
|
||||
return
|
||||
}
|
||||
if (beacon.type !== 'service') {
|
||||
this.log(ERROR, "got beacon from invalid type", beacon.type)
|
||||
return
|
||||
}
|
||||
this.lastSeenBeacon = Date.now()
|
||||
if (beacon.fees) {
|
||||
this.feesCache = beacon.fees
|
||||
}
|
||||
}
|
||||
|
||||
onEvent = async (res: { requestId: string }, fromPub: string) => {
|
||||
if (fromPub !== this.pubDestination) {
|
||||
this.log("got event from invalid pub", fromPub, this.pubDestination)
|
||||
if (fromPub !== this.providerPubkey) {
|
||||
this.log("got event from invalid pub", fromPub, this.providerPubkey)
|
||||
return false
|
||||
}
|
||||
if (this.clientCbs[res.requestId]) {
|
||||
|
|
@ -288,9 +351,6 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
clientSend = (to: string, message: NostrRequest): Promise<any> => {
|
||||
if (!this.configured || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
message.requestId = makeId(16)
|
||||
}
|
||||
|
|
@ -298,7 +358,7 @@ export class LiquidityProvider {
|
|||
if (this.clientCbs[reqId]) {
|
||||
throw new Error("request was already sent")
|
||||
}
|
||||
this.nostrSend({ type: 'client', clientId: this.clientId }, {
|
||||
this.utils.nostrSender.Send({ type: 'client', clientId: this.localId }, {
|
||||
type: 'content',
|
||||
pub: to,
|
||||
content: JSON.stringify(message)
|
||||
|
|
@ -317,9 +377,6 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
clientSub = (to: string, message: NostrRequest, cb: (res: any) => void): void => {
|
||||
if (!this.configured || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
message.requestId = message.rpcName
|
||||
}
|
||||
|
|
@ -336,7 +393,7 @@ export class LiquidityProvider {
|
|||
this.log("sub for", reqId, "was already registered, overriding")
|
||||
return
|
||||
}
|
||||
this.nostrSend({ type: 'client', clientId: this.clientId }, {
|
||||
this.utils.nostrSender.Send({ type: 'client', clientId: this.localId }, {
|
||||
type: 'content',
|
||||
pub: to,
|
||||
content: JSON.stringify(message)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { getRepository } from "typeorm";
|
|||
import { User } from "../storage/entity/User.js";
|
||||
import { UserOffer } from "../storage/entity/UserOffer.js";
|
||||
import { ManagementGrant } from "../storage/entity/ManagementGrant.js";
|
||||
import { NostrEvent, NostrSend } from "../nostr/handler.js";
|
||||
import { NostrEvent } from "../nostr/nostrPool.js";
|
||||
import Storage from "../storage/index.js";
|
||||
import { OfferManager } from "./offerManager.js";
|
||||
import * as Types from "../../../proto/autogenerated/ts/types.js";
|
||||
|
|
@ -13,7 +13,6 @@ import SettingsManager from "./settingsManager.js";
|
|||
type Result<T> = { state: 'success', result: T } | { state: 'error', err: NmanageFailure } | { state: 'authRequired' }
|
||||
|
||||
export class ManagementManager {
|
||||
private nostrSend: NostrSend;
|
||||
private storage: Storage;
|
||||
private settings: SettingsManager;
|
||||
private awaitingRequests: Record<string, { request: NmanageRequest, event: NostrEvent }> = {}
|
||||
|
|
@ -24,10 +23,6 @@ export class ManagementManager {
|
|||
this.logger = getLogger({ component: 'ManagementManager' })
|
||||
}
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
}
|
||||
|
||||
ResetManage = async (ctx: Types.UserContext, req: Types.ManageOperation): Promise<void> => {
|
||||
await this.storage.managementStorage.removeGrant(ctx.app_user_id, req.npub)
|
||||
}
|
||||
|
|
@ -62,12 +57,12 @@ export class ManagementManager {
|
|||
private sendManageAuthorizationRequest = (appId: string, userPub: string, { requestId, npub }: { requestId: string, npub: string }) => {
|
||||
const message: Types.LiveManageRequest & { requestId: string, status: 'OK' } = { requestId: "GetLiveManageRequests", status: 'OK', npub: npub, request_id: requestId }
|
||||
this.logger("Sending manage authorization request to", npub, "for app", appId)
|
||||
this.nostrSend({ type: 'app', appId: appId }, { type: 'content', content: JSON.stringify(message), pub: userPub })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: appId }, { type: 'content', content: JSON.stringify(message), pub: userPub })
|
||||
}
|
||||
|
||||
private sendError(event: NostrEvent, err: NmanageFailure) {
|
||||
const e = newNmanageResponse(JSON.stringify(err), event)
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
}
|
||||
|
||||
private async handleAuthRequired(nmanageReq: NmanageRequest, event: NostrEvent) {
|
||||
|
|
@ -107,7 +102,7 @@ export class ManagementManager {
|
|||
return
|
||||
}
|
||||
const e = newNmanageResponse(JSON.stringify(r.result), event)
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
} catch (err: any) {
|
||||
this.logger(ERROR, err.message || err)
|
||||
this.sendError(event, { res: 'GFY', code: 2, error: 'Temporary Failure' })
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import ProductManager from "./productManager.js";
|
|||
import Storage from '../storage/index.js'
|
||||
import LND from "../lnd/lnd.js"
|
||||
import { ERROR, getLogger } from "../helpers/logger.js";
|
||||
import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js';
|
||||
import { NostrEvent } from '../nostr/nostrPool.js';
|
||||
import { UnsignedEvent } from 'nostr-tools';
|
||||
import { UserOffer } from '../storage/entity/UserOffer.js';
|
||||
import { LiquidityManager } from "./liquidityManager.js"
|
||||
|
|
@ -31,9 +31,6 @@ const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }
|
|||
}
|
||||
}
|
||||
export class OfferManager {
|
||||
|
||||
|
||||
_nostrSend: NostrSend | null = null
|
||||
settings: SettingsManager
|
||||
applicationManager: ApplicationManager
|
||||
productManager: ProductManager
|
||||
|
|
@ -50,16 +47,6 @@ export class OfferManager {
|
|||
this.liquidityManager = liquidityManager
|
||||
}
|
||||
|
||||
attachNostrSend = (nostrSend: NostrSend) => {
|
||||
this._nostrSend = nostrSend
|
||||
}
|
||||
nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
|
||||
if (!this._nostrSend) {
|
||||
throw new Error("No nostrSend attached")
|
||||
}
|
||||
this._nostrSend(initiator, data, relays)
|
||||
}
|
||||
|
||||
async AddUserOffer(ctx: Types.UserContext, req: Types.OfferConfig): Promise<Types.OfferId> {
|
||||
const newOffer = await this.storage.offerStorage.AddUserOffer(ctx.app_user_id, {
|
||||
payer_data: req.payer_data,
|
||||
|
|
@ -182,7 +169,7 @@ export class OfferManager {
|
|||
max: offerInvoice.max
|
||||
})
|
||||
const e = newNofferResponse(JSON.stringify({ code, error: codeToMessage(code), range: { min: 10, max: offerInvoice.max } }), event)
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +181,7 @@ export class OfferManager {
|
|||
})
|
||||
|
||||
const e = newNofferResponse(JSON.stringify({ bolt11: offerInvoice.invoice }), event)
|
||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||
|
||||
this.logger("📤 [OFFER RESPONSE] Sent offer response", {
|
||||
toPub: event.pub,
|
||||
|
|
|
|||
|
|
@ -12,11 +12,16 @@ import { Payment_PaymentStatus } from '../../../proto/lnd/lightning.js'
|
|||
import { Event, verifiedSymbol, verifyEvent } from 'nostr-tools'
|
||||
import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingTransaction.js'
|
||||
import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js'
|
||||
import { UserReceivingAddress } from '../storage/entity/UserReceivingAddress.js'
|
||||
import { Watchdog } from './watchdog.js'
|
||||
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 { Transaction, OutputDetail } from '../../../proto/lnd/lightning.js'
|
||||
import { LndAddress } from '../lnd/lnd.js'
|
||||
import Metrics from '../metrics/index.js'
|
||||
interface UserOperationInfo {
|
||||
serial_id: number
|
||||
paid_amount: number
|
||||
|
|
@ -36,6 +41,8 @@ interface UserOperationInfo {
|
|||
};
|
||||
internal?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export type PendingTx = { type: 'incoming', tx: AddressReceivingTransaction } | { type: 'outgoing', tx: UserTransactionPayment }
|
||||
const defaultLnurlPayMetadata = (text: string) => `[["text/plain", "${text}"]]`
|
||||
const defaultLnAddressMetadata = (text: string, id: string) => `[["text/plain", "${text}"],["text/identifier", "${id}"]]`
|
||||
|
|
@ -51,25 +58,34 @@ export default class {
|
|||
watchDog: Watchdog
|
||||
liquidityManager: LiquidityManager
|
||||
utils: Utils
|
||||
constructor(storage: Storage, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||
swaps: Swaps
|
||||
invoiceLock: InvoiceLock
|
||||
metrics: Metrics
|
||||
constructor(storage: Storage, metrics: Metrics, lnd: LND, swaps: Swaps, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||
this.storage = storage
|
||||
this.metrics = metrics
|
||||
this.settings = settings
|
||||
this.lnd = lnd
|
||||
this.liquidityManager = liquidityManager
|
||||
this.utils = utils
|
||||
this.watchDog = new Watchdog(settings, this.liquidityManager, this.lnd, this.storage, this.utils, this.liquidityManager.rugPullTracker)
|
||||
this.swaps = swaps
|
||||
this.addressPaidCb = addressPaidCb
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
}
|
||||
Stop() {
|
||||
this.watchDog.Stop()
|
||||
this.invoiceLock = new InvoiceLock()
|
||||
}
|
||||
|
||||
checkPendingPayments = async () => {
|
||||
const log = getLogger({ component: 'pendingPaymentsOnStart' })
|
||||
|
||||
Stop() {
|
||||
this.watchDog.Stop()
|
||||
this.swaps.Stop()
|
||||
}
|
||||
|
||||
checkPaymentStatus = async () => {
|
||||
const log = getLogger({ component: 'checkPaymentStatus' })
|
||||
const pendingPayments = await this.storage.paymentStorage.GetPendingPayments()
|
||||
for (const p of pendingPayments) {
|
||||
log("checking state of payment: ", p.invoice)
|
||||
log("checking status of payment: ", p.invoice)
|
||||
if (p.internal) {
|
||||
log("found pending internal payment", p.serial_id)
|
||||
} else if (p.liquidityProvider) {
|
||||
|
|
@ -85,7 +101,7 @@ export default class {
|
|||
checkPendingProviderPayment = async (log: PubLogger, p: UserInvoicePayment) => {
|
||||
const state = await this.lnd.liquidProvider.GetPaymentState(p.invoice)
|
||||
if (state.paid_at_unix < 0) {
|
||||
const fullAmount = p.paid_amount + p.service_fees + p.routing_fees
|
||||
const fullAmount = p.paid_amount + p.service_fees
|
||||
log("found a failed provider payment, refunding", fullAmount, "sats to user", p.user.user_id)
|
||||
await this.storage.StartTransaction(async tx => {
|
||||
await this.storage.userStorage.IncrementUserBalance(p.user.user_id, fullAmount, "payment_refund:" + p.invoice, tx)
|
||||
|
|
@ -94,18 +110,16 @@ export default class {
|
|||
return
|
||||
} else if (state.paid_at_unix > 0) {
|
||||
log("provider payment succeeded", p.serial_id, "updating payment info")
|
||||
const routingFeeLimit = p.routing_fees
|
||||
const serviceFee = p.service_fees
|
||||
const actualFee = state.network_fee + state.service_fee
|
||||
await this.storage.StartTransaction(async tx => {
|
||||
if (routingFeeLimit - actualFee > 0) {
|
||||
this.log("refund pending provider payment routing fee", routingFeeLimit, actualFee, "sats")
|
||||
await this.storage.userStorage.IncrementUserBalance(p.user.user_id, routingFeeLimit - actualFee, "routing_fee_refund:" + p.invoice, tx)
|
||||
}
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(p.serial_id, actualFee, p.service_fees, true, undefined, tx)
|
||||
}, "pending provider payment success after restart")
|
||||
if (p.linkedApplication && p.user.user_id !== p.linkedApplication.owner.user_id && serviceFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(p.linkedApplication.owner.user_id, serviceFee, "fees")
|
||||
const networkFee = state.service_fee
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(p.serial_id, networkFee, serviceFee, true)
|
||||
const remainingFee = serviceFee - networkFee
|
||||
if (remainingFee < 0) {
|
||||
this.log("WARNING: provider fee was higher than expected,", remainingFee, "were lost")
|
||||
}
|
||||
|
||||
if (p.linkedApplication && p.user.user_id !== p.linkedApplication.owner.user_id && remainingFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(p.linkedApplication.owner.user_id, remainingFee, "fees")
|
||||
}
|
||||
const user = await this.storage.userStorage.GetUser(p.user.user_id)
|
||||
this.storage.eventsLog.LogEvent({ type: 'invoice_payment', userId: p.user.user_id, appId: p.linkedApplication?.app_id || "", appUserId: "", balance: user.balance_sats, data: p.invoice, amount: p.paid_amount })
|
||||
|
|
@ -126,7 +140,6 @@ export default class {
|
|||
log(ERROR, "lnd payment not found for pending payment hash ", decoded.paymentHash)
|
||||
return
|
||||
}
|
||||
|
||||
switch (payment.status) {
|
||||
case Payment_PaymentStatus.UNKNOWN:
|
||||
log("pending payment in unknown state", p.serial_id, "no action will be performed")
|
||||
|
|
@ -136,24 +149,22 @@ export default class {
|
|||
return
|
||||
case Payment_PaymentStatus.SUCCEEDED:
|
||||
log("pending payment succeeded", p.serial_id, "updating payment info")
|
||||
const routingFeeLimit = p.routing_fees
|
||||
const serviceFee = p.service_fees
|
||||
const actualFee = Number(payment.feeSat)
|
||||
await this.storage.StartTransaction(async tx => {
|
||||
if (routingFeeLimit - actualFee > 0) {
|
||||
this.log("refund pending payment routing fee", routingFeeLimit, actualFee, "sats")
|
||||
await this.storage.userStorage.IncrementUserBalance(p.user.user_id, routingFeeLimit - actualFee, "routing_fee_refund:" + p.invoice, tx)
|
||||
}
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(p.serial_id, actualFee, p.service_fees, true, undefined, tx)
|
||||
}, "pending payment success after restart")
|
||||
if (p.linkedApplication && p.user.user_id !== p.linkedApplication.owner.user_id && serviceFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(p.linkedApplication.owner.user_id, serviceFee, "fees")
|
||||
const networkFee = Number(payment.feeSat)
|
||||
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(p.serial_id, networkFee, p.service_fees, true, undefined)
|
||||
const remainingFee = serviceFee - networkFee
|
||||
if (remainingFee < 0) { // should not be possible beacuse of the fee limit
|
||||
this.log("WARNING: lnd fee was higher than expected,", remainingFee, "were lost")
|
||||
}
|
||||
if (p.linkedApplication && p.user.user_id !== p.linkedApplication.owner.user_id && remainingFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(p.linkedApplication.owner.user_id, remainingFee, "fees")
|
||||
}
|
||||
const user = await this.storage.userStorage.GetUser(p.user.user_id)
|
||||
this.storage.eventsLog.LogEvent({ type: 'invoice_payment', userId: p.user.user_id, appId: p.linkedApplication?.app_id || "", appUserId: "", balance: user.balance_sats, data: p.invoice, amount: p.paid_amount })
|
||||
return
|
||||
case Payment_PaymentStatus.FAILED:
|
||||
const fullAmount = p.paid_amount + p.service_fees + p.routing_fees
|
||||
const fullAmount = p.paid_amount + p.service_fees
|
||||
log("found a failed pending payment, refunding", fullAmount, "sats to user", p.user.user_id)
|
||||
await this.storage.StartTransaction(async tx => {
|
||||
await this.storage.userStorage.IncrementUserBalance(p.user.user_id, fullAmount, "payment_refund:" + p.invoice, tx)
|
||||
|
|
@ -165,32 +176,171 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
getServiceFee(action: Types.UserOperationType, amount: number, appUser: boolean): number {
|
||||
checkMissedChainTxs = async () => {
|
||||
const log = getLogger({ component: 'checkMissedChainTxs' })
|
||||
|
||||
if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||
log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping chain tx check")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { txs, currentHeight, lndPubkey } = await this.getLatestTransactions(log)
|
||||
|
||||
const recoveredCount = await this.processMissedChainTransactions(txs, log)
|
||||
|
||||
// Update latest checked height to current block height
|
||||
await this.storage.liquidityStorage.UpdateLatestCheckedHeight('lnd', lndPubkey, currentHeight)
|
||||
|
||||
if (recoveredCount > 0) {
|
||||
log(`processed ${recoveredCount} missed chain tx(s)`)
|
||||
} else {
|
||||
log("no missed chain transactions found")
|
||||
}
|
||||
} catch (err: any) {
|
||||
log(ERROR, "failed to check for missed chain transactions:", err.message || err)
|
||||
}
|
||||
}
|
||||
|
||||
private async getLatestTransactions(log: PubLogger): Promise<{ txs: Transaction[], currentHeight: number, lndPubkey: string }> {
|
||||
const lndInfo = await this.lnd.GetInfo()
|
||||
const lndPubkey = lndInfo.identityPubkey
|
||||
|
||||
const startHeight = await this.storage.liquidityStorage.GetLatestCheckedHeight('lnd', lndPubkey)
|
||||
log(`checking for missed confirmed chain transactions from height ${startHeight}...`)
|
||||
|
||||
const { transactions } = await this.lnd.GetTransactions(startHeight)
|
||||
log(`retrieved ${transactions.length} transactions from LND`)
|
||||
|
||||
return { txs: transactions, currentHeight: lndInfo.blockHeight, lndPubkey }
|
||||
}
|
||||
|
||||
private async processMissedChainTransactions(transactions: Transaction[], log: PubLogger): Promise<number> {
|
||||
let recoveredCount = 0
|
||||
const addresses = await this.lnd.ListAddresses()
|
||||
for (const tx of transactions) {
|
||||
if (!tx.outputDetails || tx.outputDetails.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const outputsWithAddresses = await this.collectOutputsWithAddresses(tx)
|
||||
|
||||
for (const { output, userAddress } of outputsWithAddresses) {
|
||||
if (!userAddress) {
|
||||
await this.processRootAddressOutput(output, tx, addresses, log)
|
||||
continue
|
||||
}
|
||||
|
||||
const processed = await this.processUserAddressOutput(output, tx, log)
|
||||
if (processed) {
|
||||
recoveredCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recoveredCount
|
||||
}
|
||||
|
||||
private async collectOutputsWithAddresses(tx: Transaction) {
|
||||
const outputs: { output: OutputDetail, userAddress: UserReceivingAddress | null, tx: Transaction }[] = []
|
||||
for (const output of tx.outputDetails) {
|
||||
if (!output.address || !output.isOurAddress) {
|
||||
continue
|
||||
}
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(output.address)
|
||||
outputs.push({ output, userAddress, tx })
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
|
||||
private async processRootAddressOutput(output: OutputDetail, tx: Transaction, addresses: LndAddress[], log: PubLogger): Promise<boolean> {
|
||||
const addr = addresses.find(a => a.address === output.address)
|
||||
if (!addr) {
|
||||
throw new Error(`address ${output.address} not found in list of addresses`)
|
||||
}
|
||||
if (addr.change) {
|
||||
log(`ignoring change address ${output.address}`)
|
||||
return false
|
||||
}
|
||||
const outputIndex = Number(output.outputIndex)
|
||||
const existingRootOp = await this.metrics.GetRootAddressTransaction(output.address, tx.txHash, outputIndex)
|
||||
if (existingRootOp) {
|
||||
return false
|
||||
}
|
||||
this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, Number(output.amount), 'lnd')
|
||||
.catch(err => log(ERROR, "failed to process root address output:", err.message || err))
|
||||
return true
|
||||
}
|
||||
|
||||
private async processUserAddressOutput(output: OutputDetail, tx: Transaction, log: PubLogger) {
|
||||
const existingTx = await this.storage.paymentStorage.GetAddressReceivingTransactionOwner(
|
||||
output.address,
|
||||
tx.txHash
|
||||
)
|
||||
|
||||
if (existingTx) {
|
||||
return false
|
||||
}
|
||||
|
||||
const amount = Number(output.amount)
|
||||
const outputIndex = Number(output.outputIndex)
|
||||
log(`processing missed chain tx: address=${output.address}, txHash=${tx.txHash}, amount=${amount}, outputIndex=${outputIndex}`)
|
||||
this.addressPaidCb({ hash: tx.txHash, index: outputIndex }, output.address, amount, 'lnd')
|
||||
.catch(err => log(ERROR, "failed to process user address output:", err.message || err))
|
||||
return true
|
||||
}
|
||||
|
||||
getReceiveServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => {
|
||||
switch (action) {
|
||||
case Types.UserOperationType.INCOMING_TX:
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingTxFee * amount)
|
||||
case Types.UserOperationType.OUTGOING_TX:
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingTxFee * amount)
|
||||
return 0
|
||||
case Types.UserOperationType.INCOMING_INVOICE:
|
||||
if (appUser) {
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppUserInvoiceFee * amount)
|
||||
}
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.incomingAppInvoiceFee * amount)
|
||||
case Types.UserOperationType.OUTGOING_INVOICE:
|
||||
if (appUser) {
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee * amount)
|
||||
}
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee * amount)
|
||||
case Types.UserOperationType.OUTGOING_USER_TO_USER || Types.UserOperationType.INCOMING_USER_TO_USER:
|
||||
if (appUser) {
|
||||
// Incoming invoice fees are always 0 (not configurable)
|
||||
return 0
|
||||
case Types.UserOperationType.INCOMING_USER_TO_USER:
|
||||
if (managedUser) {
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||
}
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.appToUserFee * amount)
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.rootToUserFee * amount)
|
||||
default:
|
||||
throw new Error("Unknown receive action type")
|
||||
}
|
||||
}
|
||||
|
||||
getInvoicePaymentServiceFee = (amount: number, managedUser: boolean): number => {
|
||||
if (!managedUser) {
|
||||
return 0 // Root doesn't pay service fee to themselves
|
||||
}
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.serviceFee * amount)
|
||||
}
|
||||
|
||||
getSendServiceFee = (action: Types.UserOperationType, amount: number, managedUser: boolean): number => {
|
||||
switch (action) {
|
||||
case Types.UserOperationType.OUTGOING_TX:
|
||||
throw new Error("OUTGOING_TX is not a valid send service fee action")
|
||||
case Types.UserOperationType.OUTGOING_INVOICE:
|
||||
const fee = this.getInvoicePaymentServiceFee(amount, managedUser)
|
||||
// Only managed users pay the service fee floor
|
||||
if (!managedUser) {
|
||||
return 0
|
||||
}
|
||||
return Math.max(fee, this.settings.getSettings().serviceFeeSettings.serviceFeeFloor)
|
||||
case Types.UserOperationType.OUTGOING_USER_TO_USER:
|
||||
if (managedUser) {
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.userToUserFee * amount)
|
||||
}
|
||||
return Math.ceil(this.settings.getSettings().serviceFeeSettings.rootToUserFee * amount)
|
||||
default:
|
||||
throw new Error("Unknown service action type")
|
||||
}
|
||||
}
|
||||
|
||||
getRoutingFeeLimit = (amount: number): number => {
|
||||
const { routingFeeLimitBps, routingFeeFloor } = this.settings.getSettings().lndSettings
|
||||
const limit = Math.floor(amount * routingFeeLimitBps / 10000)
|
||||
return Math.max(limit, routingFeeFloor)
|
||||
}
|
||||
|
||||
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
|
||||
if (!this.settings.getSettings().lndSettings.mockLnd) {
|
||||
throw new Error("mock disabled, cannot set invoice as paid")
|
||||
|
|
@ -229,7 +379,7 @@ export default class {
|
|||
}
|
||||
const use = await this.liquidityManager.beforeInvoiceCreation(req.amountSats)
|
||||
const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry, { useProvider: use === 'provider', from: 'user' }, req.blind)
|
||||
const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options, res.providerDst)
|
||||
const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options, res.providerPubkey)
|
||||
const appId = options.linkedApplication ? options.linkedApplication.app_id : ""
|
||||
this.storage.eventsLog.LogEvent({ type: 'new_invoice', userId: user.user_id, appUserId: "", appId, balance: user.balance_sats, data: userInvoice.invoice, amount: req.amountSats })
|
||||
return {
|
||||
|
|
@ -237,14 +387,19 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
GetMaxPayableInvoice(balance: number, appUser: boolean): number {
|
||||
let maxWithinServiceFee = 0
|
||||
if (appUser) {
|
||||
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee)))
|
||||
} else {
|
||||
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee)))
|
||||
}
|
||||
return this.lnd.GetMaxWithinLimit(maxWithinServiceFee)
|
||||
GetFees = (): Types.CumulativeFees => {
|
||||
const { serviceFeeBps, serviceFeeFloor } = this.settings.getSettings().serviceFeeSettings
|
||||
return { serviceFeeFloor, serviceFeeBps }
|
||||
}
|
||||
|
||||
GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } {
|
||||
const { serviceFeeFloor, serviceFeeBps } = this.GetFees()
|
||||
const div = 1 + (serviceFeeBps / 10000)
|
||||
const maxWithoutFixed = Math.floor(balance / div)
|
||||
const fee = balance - maxWithoutFixed
|
||||
const m = balance - Math.max(fee, serviceFeeFloor)
|
||||
const max = Math.max(m, 0)
|
||||
return { max, serviceFeeFloor, serviceFeeBps }
|
||||
}
|
||||
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
|
||||
const decoded = await this.lnd.DecodeInvoice(req.invoice)
|
||||
|
|
@ -253,12 +408,20 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application): Promise<Types.PayInvoiceResponse> {
|
||||
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application, optionals: { swapOperationId?: string, ack?: (op: Types.UserOperation) => void } = {}): Promise<Types.PayInvoiceResponse & { operation: Types.UserOperation }> {
|
||||
await this.watchDog.PaymentRequested()
|
||||
const maybeBanned = await this.storage.userStorage.GetUser(userId)
|
||||
if (maybeBanned.locked) {
|
||||
throw new Error("user is banned, cannot send payment")
|
||||
}
|
||||
if (req.expected_fees) {
|
||||
const { serviceFeeFloor, serviceFeeBps } = req.expected_fees
|
||||
const serviceFixed = this.settings.getSettings().serviceFeeSettings.serviceFeeFloor
|
||||
const serviceBps = this.settings.getSettings().serviceFeeSettings.serviceFeeBps
|
||||
if (serviceFixed !== serviceFeeFloor || serviceBps !== serviceFeeBps) {
|
||||
throw new Error("fees do not match the expected fees")
|
||||
}
|
||||
}
|
||||
const decoded = await this.lnd.DecodeInvoice(req.invoice)
|
||||
if (decoded.numSatoshis !== 0 && req.amount !== 0) {
|
||||
throw new Error("invoice has value, do not provide amount the the request")
|
||||
|
|
@ -267,8 +430,8 @@ export default class {
|
|||
throw new Error("invoice has no value, an amount must be provided in the request")
|
||||
}
|
||||
const payAmount = req.amount !== 0 ? req.amount : Number(decoded.numSatoshis)
|
||||
const isAppUserPayment = userId !== linkedApplication.owner.user_id
|
||||
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount, isAppUserPayment)
|
||||
const isManagedUser = userId !== linkedApplication.owner.user_id
|
||||
const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount, isManagedUser)
|
||||
const internalInvoice = await this.storage.paymentStorage.GetInvoiceOwner(req.invoice)
|
||||
if (internalInvoice && internalInvoice.paid_at_unix > 0) {
|
||||
throw new Error("this invoice was already paid")
|
||||
|
|
@ -278,26 +441,42 @@ export default class {
|
|||
throw new Error("this invoice was already paid")
|
||||
}
|
||||
let paymentInfo = { preimage: "", amtPaid: 0, networkFee: 0, serialId: 0 }
|
||||
if (internalInvoice) {
|
||||
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication, req.debit_npub)
|
||||
} else {
|
||||
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount }, linkedApplication, req.debit_npub)
|
||||
if (this.invoiceLock.isLocked(req.invoice)) {
|
||||
throw new Error("this invoice is already being paid")
|
||||
}
|
||||
if (isAppUserPayment && serviceFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee, "fees")
|
||||
this.invoiceLock.lock(req.invoice)
|
||||
try {
|
||||
if (internalInvoice) {
|
||||
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication, req.debit_npub)
|
||||
} else {
|
||||
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount }, linkedApplication, { ...optionals, debitNpub: req.debit_npub })
|
||||
}
|
||||
this.invoiceLock.unlock(req.invoice)
|
||||
} catch (err) {
|
||||
this.invoiceLock.unlock(req.invoice)
|
||||
throw err
|
||||
}
|
||||
const feeDiff = serviceFee - paymentInfo.networkFee
|
||||
if (isManagedUser && feeDiff > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, feeDiff, "fees")
|
||||
}
|
||||
const user = await this.storage.userStorage.GetUser(userId)
|
||||
this.storage.eventsLog.LogEvent({ type: 'invoice_payment', userId, appId: linkedApplication.app_id, appUserId: "", balance: user.balance_sats, data: req.invoice, amount: payAmount })
|
||||
const opId = `${Types.UserOperationType.OUTGOING_INVOICE}-${paymentInfo.serialId}`
|
||||
const operation = this.newInvoicePaymentOperation({ invoice: req.invoice, opId, amount: paymentInfo.amtPaid, networkFee: paymentInfo.networkFee, serviceFee: serviceFee, confirmed: true, paidAtUnix: Math.floor(Date.now() / 1000) })
|
||||
return {
|
||||
preimage: paymentInfo.preimage,
|
||||
amount_paid: paymentInfo.amtPaid,
|
||||
operation_id: `${Types.UserOperationType.OUTGOING_INVOICE}-${paymentInfo.serialId}`,
|
||||
network_fee: paymentInfo.networkFee,
|
||||
operation_id: opId,
|
||||
network_fee: 0,
|
||||
service_fee: serviceFee,
|
||||
latest_balance: user.balance_sats,
|
||||
operation
|
||||
}
|
||||
}
|
||||
|
||||
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number }, linkedApplication: Application, debitNpub?: string) {
|
||||
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number }, linkedApplication: Application, optionals: { debitNpub?: string, swapOperationId?: string, ack?: (op: Types.UserOperation) => void } = {}) {
|
||||
|
||||
if (this.settings.getSettings().serviceSettings.disableExternalPayments) {
|
||||
throw new Error("something went wrong sending payment, please try again later")
|
||||
}
|
||||
|
|
@ -310,30 +489,33 @@ export default class {
|
|||
}
|
||||
throw new Error("payment already in progress")
|
||||
}
|
||||
|
||||
const { amountForLnd, payAmount, serviceFee } = amounts
|
||||
const totalAmountToDecrement = payAmount + serviceFee
|
||||
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
||||
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount)
|
||||
const provider = use === 'provider' ? this.lnd.liquidProvider.GetProviderDestination() : undefined
|
||||
const routingFeeLimit = this.getRoutingFeeLimit(payAmount)
|
||||
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount, serviceFee)
|
||||
const provider = use === 'provider' ? this.lnd.liquidProvider.GetProviderPubkey() : undefined
|
||||
const pendingPayment = await this.storage.StartTransaction(async tx => {
|
||||
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice, tx)
|
||||
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: routingFeeLimit }, linkedApplication, provider, tx, debitNpub)
|
||||
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement, invoice, tx)
|
||||
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: 0 }, linkedApplication, provider, tx, optionals)
|
||||
}, "payment started")
|
||||
this.log("ready to pay")
|
||||
const opId = `${Types.UserOperationType.OUTGOING_INVOICE}-${pendingPayment.serial_id}`
|
||||
const op = this.newInvoicePaymentOperation({ invoice, opId, amount: payAmount, networkFee: 0, serviceFee: serviceFee, confirmed: false, paidAtUnix: 0 })
|
||||
optionals.ack?.(op)
|
||||
try {
|
||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, { routingFeeLimit, serviceFee }, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
||||
this.storage.paymentStorage.SetExternalPaymentIndex(pendingPayment.serial_id, index)
|
||||
})
|
||||
if (routingFeeLimit - payment.feeSat > 0) {
|
||||
this.log("refund routing fee", routingFeeLimit, payment.feeSat, "sats")
|
||||
await this.storage.userStorage.IncrementUserBalance(userId, routingFeeLimit - payment.feeSat, "routing_fee_refund:" + invoice)
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, payment.feeSat, serviceFee, true, payment.providerPubkey)
|
||||
const feeDiff = serviceFee - payment.feeSat
|
||||
if (feeDiff < 0) { // should not happen to lnd beacuse of the fee limit, culd happen to provider if the fee used to calculate the provider fee are out of date
|
||||
this.log("WARNING: network fee was higher than expected,", feeDiff, "were lost by", use === 'provider' ? "provider" : "lnd")
|
||||
}
|
||||
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, payment.feeSat, serviceFee, true, payment.providerDst)
|
||||
return { preimage: payment.paymentPreimage, amtPaid: payment.valueSat, networkFee: payment.feeSat, serialId: pendingPayment.serial_id }
|
||||
|
||||
} catch (err) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, "payment_refund:" + invoice)
|
||||
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement, "payment_refund:" + invoice)
|
||||
await this.storage.paymentStorage.UpdateExternalPayment(pendingPayment.serial_id, 0, 0, false)
|
||||
throw err
|
||||
}
|
||||
|
|
@ -354,60 +536,79 @@ export default class {
|
|||
} catch (err) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement, "internal_payment_refund:" + internalInvoice.invoice)
|
||||
this.utils.stateBundler.AddTxPointFailed('paidAnInvoice', payAmount, { used: 'internal', from: 'user' }, linkedApplication.app_id)
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async GetTransactionSwapQuotes(ctx: Types.UserContext, req: Types.TransactionSwapRequest): Promise<Types.TransactionSwapQuoteList> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const quotes = await this.swaps.GetTxSwapQuotes(ctx.app_user_id, req.transaction_amount_sats, decodedAmt => {
|
||||
const isManagedUser = ctx.user_id !== app.owner.user_id
|
||||
return this.getSendServiceFee(Types.UserOperationType.OUTGOING_INVOICE, decodedAmt, isManagedUser)
|
||||
})
|
||||
return { quotes }
|
||||
}
|
||||
|
||||
async PayAddress(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
|
||||
throw new Error("address payment currently disabled, use Lightning instead")
|
||||
await this.watchDog.PaymentRequested()
|
||||
this.log("paying address", req.address, "for user", ctx.user_id, "with amount", req.amoutSats)
|
||||
this.log("paying address", req.address, "for user", ctx.user_id, "with amount", req.amountSats)
|
||||
const maybeBanned = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||
if (maybeBanned.locked) {
|
||||
throw new Error("user is banned, cannot send chain tx")
|
||||
}
|
||||
const internalAddress = await this.storage.paymentStorage.GetAddressOwner(req.address)
|
||||
if (internalAddress) {
|
||||
return this.PayInternalAddress(ctx, req)
|
||||
}
|
||||
return this.PayAddressWithSwap(ctx, req)
|
||||
}
|
||||
|
||||
async PayAddressWithSwap(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
|
||||
this.log("paying external address")
|
||||
if (!req.swap_operation_id) {
|
||||
throw new Error("request a swap quote before paying an external address")
|
||||
}
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
let payment: Types.PayInvoiceResponse
|
||||
const swap = await this.swaps.PayAddrWithSwap(ctx.app_user_id, req.swap_operation_id, req.address, async (invoice) => {
|
||||
payment = await this.PayInvoice(ctx.user_id, {
|
||||
amount: 0,
|
||||
invoice: invoice
|
||||
}, app, { swapOperationId: req.swap_operation_id })
|
||||
})
|
||||
return {
|
||||
txId: swap.txId,
|
||||
network_fee: swap.network_fee,
|
||||
service_fee: payment!.service_fee,
|
||||
operation_id: payment!.operation_id,
|
||||
}
|
||||
}
|
||||
|
||||
async PayInternalAddress(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
|
||||
this.log("paying internal address")
|
||||
if (req.swap_operation_id) {
|
||||
await this.storage.paymentStorage.DeleteTransactionSwap(req.swap_operation_id)
|
||||
}
|
||||
const { blockHeight } = await this.lnd.GetInfo()
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_TX, req.amoutSats, false)
|
||||
const isAppUserPayment = ctx.user_id !== app.owner.user_id
|
||||
const internalAddress = await this.storage.paymentStorage.GetAddressOwner(req.address)
|
||||
let txId = ""
|
||||
let chainFees = 0
|
||||
if (!internalAddress) {
|
||||
this.log("paying external address")
|
||||
const estimate = await this.lnd.EstimateChainFees(req.address, req.amoutSats, 1)
|
||||
const vBytes = Math.ceil(Number(estimate.feeSat / estimate.satPerVbyte))
|
||||
chainFees = vBytes * req.satsPerVByte
|
||||
const total = req.amoutSats + chainFees
|
||||
// WARNING, before re-enabling this, make sure to add the tx_hash to the DecrementUserBalance "reason"!!
|
||||
this.storage.userStorage.DecrementUserBalance(ctx.user_id, total + serviceFee, req.address)
|
||||
try {
|
||||
const payment = await this.lnd.PayAddress(req.address, req.amoutSats, req.satsPerVByte, "", { useProvider: false, from: 'user' })
|
||||
txId = payment.txid
|
||||
} catch (err) {
|
||||
// WARNING, before re-enabling this, make sure to add the tx_hash to the IncrementUserBalance "reason"!!
|
||||
await this.storage.userStorage.IncrementUserBalance(ctx.user_id, total + serviceFee, req.address)
|
||||
throw err
|
||||
}
|
||||
} else {
|
||||
this.log("paying internal address")
|
||||
txId = crypto.randomBytes(32).toString("hex")
|
||||
const addressData = `${req.address}:${txId}`
|
||||
await this.storage.userStorage.DecrementUserBalance(ctx.user_id, req.amoutSats + serviceFee, addressData)
|
||||
this.addressPaidCb({ hash: txId, index: 0 }, req.address, req.amoutSats, 'internal')
|
||||
}
|
||||
const isManagedUser = ctx.user_id !== app.owner.user_id
|
||||
const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, req.amountSats, isManagedUser)
|
||||
|
||||
if (isAppUserPayment && serviceFee > 0) {
|
||||
const txId = crypto.randomBytes(32).toString("hex")
|
||||
const addressData = `${req.address}:${txId}`
|
||||
await this.storage.userStorage.DecrementUserBalance(ctx.user_id, req.amountSats + serviceFee, addressData)
|
||||
this.addressPaidCb({ hash: txId, index: 0 }, req.address, req.amountSats, 'internal')
|
||||
if (isManagedUser && serviceFee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(app.owner.user_id, serviceFee, 'fees')
|
||||
}
|
||||
|
||||
const newTx = await this.storage.paymentStorage.AddUserTransactionPayment(ctx.user_id, req.address, txId, 0, req.amoutSats, chainFees, serviceFee, !!internalAddress, blockHeight, app)
|
||||
const chainFees = 0
|
||||
const internalAddress = true
|
||||
const newTx = await this.storage.paymentStorage.AddUserTransactionPayment(ctx.user_id, req.address, txId, 0, req.amountSats, chainFees, serviceFee, internalAddress, blockHeight, app)
|
||||
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||
const txData = `${newTx.address}:${newTx.tx_hash}`
|
||||
this.storage.eventsLog.LogEvent({ type: 'address_payment', userId: ctx.user_id, appId: app.app_id, appUserId: "", balance: user.balance_sats, data: txData, amount: req.amoutSats })
|
||||
this.storage.eventsLog.LogEvent({ type: 'address_payment', userId: ctx.user_id, appId: app.app_id, appUserId: "", balance: user.balance_sats, data: txData, amount: req.amountSats })
|
||||
return {
|
||||
txId: txId,
|
||||
operation_id: `${Types.UserOperationType.OUTGOING_TX}-${newTx.serial_id}`,
|
||||
|
|
@ -416,6 +617,16 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
async ListSwaps(ctx: Types.UserContext): Promise<Types.SwapsList> {
|
||||
const payments = await this.storage.paymentStorage.ListSwapPayments(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 => {
|
||||
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))
|
||||
}
|
||||
|
||||
balanceCheckUrl(k1: string): string {
|
||||
return `${this.settings.getSettings().serviceSettings.serviceUrl}/api/guest/lnurl_withdraw/info?k1=${k1}`
|
||||
}
|
||||
|
|
@ -445,21 +656,6 @@ export default class {
|
|||
|
||||
async GetLnurlWithdrawInfo(balanceCheckK1: string): Promise<Types.LnurlWithdrawInfoResponse> {
|
||||
throw new Error("LNURL withdraw currenlty not supported for non application users")
|
||||
/*const key = await this.storage.paymentStorage.UseUserEphemeralKey(balanceCheckK1, 'balanceCheck')
|
||||
const maxWithdrawable = this.GetMaxPayableInvoice(key.user.balance_sats)
|
||||
const callbackK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'withdraw')
|
||||
const newBalanceCheckK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'balanceCheck')
|
||||
const payInfoK1 = await this.storage.paymentStorage.AddUserEphemeralKey(key.user.user_id, 'pay')
|
||||
return {
|
||||
tag: "withdrawRequest",
|
||||
callback: `${this.settings.serviceUrl}/api/guest/lnurl_withdraw/handle`,
|
||||
defaultDescription: "lnurl withdraw from lightning.pub",
|
||||
k1: callbackK1.key,
|
||||
maxWithdrawable: maxWithdrawable * 1000,
|
||||
minWithdrawable: 10000,
|
||||
balanceCheck: this.balanceCheckUrl(newBalanceCheckK1.key),
|
||||
payLink: `${this.settings.serviceUrl}/api/guest/lnurl_pay/info?k1=${payInfoK1.key}`,
|
||||
}*/
|
||||
}
|
||||
|
||||
async HandleLnurlWithdraw(k1: string, invoice: string): Promise<void> {
|
||||
|
|
@ -672,6 +868,23 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
newInvoicePaymentOperation = (opInfo: { invoice: string, opId: string, amount: number, networkFee: number, serviceFee: number, confirmed: boolean, paidAtUnix: number }): Types.UserOperation => {
|
||||
const { invoice, opId, amount, networkFee, serviceFee, confirmed, paidAtUnix } = opInfo
|
||||
return {
|
||||
amount: amount,
|
||||
paidAtUnix: paidAtUnix,
|
||||
inbound: false,
|
||||
type: Types.UserOperationType.OUTGOING_INVOICE,
|
||||
identifier: invoice,
|
||||
operationId: opId,
|
||||
network_fee: networkFee,
|
||||
service_fee: serviceFee,
|
||||
confirmed,
|
||||
tx_hash: "",
|
||||
internal: networkFee === 0
|
||||
}
|
||||
}
|
||||
|
||||
async GetPaymentState(userId: string, req: Types.GetPaymentStateRequest): Promise<Types.PaymentState> {
|
||||
const user = await this.storage.userStorage.GetUser(userId)
|
||||
if (user.locked) {
|
||||
|
|
@ -684,8 +897,10 @@ export default class {
|
|||
return {
|
||||
paid_at_unix: invoice.paid_at_unix,
|
||||
amount: invoice.paid_amount,
|
||||
network_fee: invoice.routing_fees,
|
||||
network_fee: 0,
|
||||
service_fee: invoice.service_fees,
|
||||
internal: invoice.internal,
|
||||
operation_id: `${Types.UserOperationType.OUTGOING_INVOICE}-${invoice.serial_id}`,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -722,14 +937,14 @@ export default class {
|
|||
if (fromUser.balance_sats < amount) {
|
||||
throw new Error("not enough balance to send payment")
|
||||
}
|
||||
const isAppUserPayment = fromUser.user_id !== linkedApplication.owner.user_id
|
||||
let fee = this.getServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isAppUserPayment)
|
||||
const isManagedUser = fromUser.user_id !== linkedApplication.owner.user_id
|
||||
let fee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_USER_TO_USER, amount, isManagedUser)
|
||||
const toDecrement = amount + fee
|
||||
const paymentEntry = await this.storage.paymentStorage.AddPendingUserToUserPayment(fromUserId, toUserId, amount, fee, linkedApplication, tx)
|
||||
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, toDecrement, `${toUserId}:${paymentEntry.serial_id}`, tx)
|
||||
await this.storage.userStorage.IncrementUserBalance(toUser.user_id, amount, `${fromUserId}:${paymentEntry.serial_id}`, tx)
|
||||
await this.storage.paymentStorage.SetPendingUserToUserPaymentAsPaid(paymentEntry.serial_id, tx)
|
||||
if (isAppUserPayment && fee > 0) {
|
||||
if (isManagedUser && fee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||
}
|
||||
return paymentEntry
|
||||
|
|
@ -785,3 +1000,16 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class InvoiceLock {
|
||||
locked: Record<string, boolean> = {}
|
||||
lock(invoice: string) {
|
||||
this.locked[invoice] = true
|
||||
}
|
||||
unlock(invoice: string) {
|
||||
delete this.locked[invoice]
|
||||
}
|
||||
isLocked(invoice: string) {
|
||||
return this.locked[invoice]
|
||||
}
|
||||
}
|
||||
|
|
@ -20,18 +20,18 @@ export class RugPullTracker {
|
|||
}
|
||||
|
||||
CheckProviderBalance = async (): Promise<{ balance: number, prevBalance?: number }> => {
|
||||
const pubDst = this.liquidProvider.GetProviderDestination()
|
||||
const pubDst = this.liquidProvider.GetProviderPubkey()
|
||||
if (!pubDst) {
|
||||
return { balance: 0 }
|
||||
}
|
||||
const providerTracker = await this.storage.liquidityStorage.GetTrackedProvider('lnPub', pubDst)
|
||||
const ready = this.liquidProvider.IsReady()
|
||||
if (ready) {
|
||||
const balance = await this.liquidProvider.GetLatestBalance()
|
||||
const balance = this.liquidProvider.GetLatestBalance()
|
||||
const pendingBalance = await this.liquidProvider.GetPendingBalance()
|
||||
const trackedBalance = balance + pendingBalance
|
||||
if (!providerTracker) {
|
||||
this.log("starting to track provider", this.liquidProvider.GetProviderDestination())
|
||||
this.log("starting to track provider", this.liquidProvider.GetProviderPubkey())
|
||||
await this.storage.liquidityStorage.CreateTrackedProvider('lnPub', pubDst, trackedBalance)
|
||||
return { balance: trackedBalance }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,27 @@
|
|||
import { EnvCacher, EnvMustBeNonEmptyString, EnvMustBeInteger, chooseEnv, chooseEnvBool, chooseEnvInt } from '../helpers/envParser.js'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { nip19 } from '@shocknet/clink-sdk'
|
||||
|
||||
export type ServiceFeeSettings = {
|
||||
incomingTxFee: number
|
||||
outgoingTxFee: number
|
||||
incomingAppInvoiceFee: number
|
||||
incomingAppUserInvoiceFee: number
|
||||
outgoingAppInvoiceFee: number
|
||||
outgoingAppUserInvoiceFee: number
|
||||
outgoingAppUserInvoiceFeeBps: number
|
||||
serviceFee: number
|
||||
serviceFeeBps: number
|
||||
serviceFeeFloor: number
|
||||
userToUserFee: number
|
||||
appToUserFee: number
|
||||
rootToUserFee: number
|
||||
}
|
||||
|
||||
export const LoadServiceFeeSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): ServiceFeeSettings => {
|
||||
const outgoingAppUserInvoiceFeeBps = chooseEnvInt("OUTGOING_INVOICE_FEE_USER_BPS", dbEnv, 0, addToDb)
|
||||
const oldServiceFeeBps = chooseEnvInt("OUTGOING_INVOICE_FEE_USER_BPS", dbEnv, 60, addToDb)
|
||||
const serviceFeeBps = chooseEnvInt("SERVICE_FEE_BPS", dbEnv, oldServiceFeeBps, addToDb)
|
||||
const oldRoutingFeeFloor = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 10, addToDb)
|
||||
const serviceFeeFloor = chooseEnvInt("SERVICE_FEE_FLOOR_SATS", dbEnv, oldRoutingFeeFloor, addToDb)
|
||||
return {
|
||||
incomingTxFee: chooseEnvInt("INCOMING_CHAIN_FEE_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
outgoingTxFee: chooseEnvInt("OUTGOING_CHAIN_FEE_ROOT_BPS", dbEnv, 60, addToDb) / 10000,
|
||||
incomingAppInvoiceFee: chooseEnvInt("INCOMING_INVOICE_FEE_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
outgoingAppInvoiceFee: chooseEnvInt("OUTGOING_INVOICE_FEE_ROOT_BPS", dbEnv, 60, addToDb) / 10000,
|
||||
incomingAppUserInvoiceFee: chooseEnvInt("INCOMING_INVOICE_FEE_USER_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
outgoingAppUserInvoiceFeeBps,
|
||||
outgoingAppUserInvoiceFee: outgoingAppUserInvoiceFeeBps / 10000,
|
||||
serviceFeeBps,
|
||||
serviceFee: serviceFeeBps / 10000,
|
||||
serviceFeeFloor,
|
||||
userToUserFee: chooseEnvInt("TX_FEE_INTERNAL_USER_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
appToUserFee: chooseEnvInt("TX_FEE_INTERNAL_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
rootToUserFee: chooseEnvInt("TX_FEE_INTERNAL_ROOT_BPS", dbEnv, 0, addToDb) / 10000,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,13 +72,14 @@ export type LndNodeSettings = {
|
|||
lndCertPath: string // cold setting
|
||||
lndMacaroonPath: string // cold setting
|
||||
}
|
||||
const networks = ['mainnet', 'testnet', 'regtest'] as const
|
||||
export type BTCNetwork = (typeof networks)[number]
|
||||
export type LndSettings = {
|
||||
lndLogDir: string
|
||||
feeRateLimit: number
|
||||
feeFixedLimit: number
|
||||
feeRateBps: number
|
||||
routingFeeLimitBps: number
|
||||
routingFeeFloor: number
|
||||
mockLnd: boolean
|
||||
|
||||
network: BTCNetwork
|
||||
}
|
||||
|
||||
const resolveHome = (filepath: string) => {
|
||||
|
|
@ -111,13 +108,16 @@ export const LoadLndNodeSettingsFromEnv = (dbEnv: Record<string, string | undefi
|
|||
}
|
||||
|
||||
export const LoadLndSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LndSettings => {
|
||||
const feeRateBps: number = chooseEnvInt('OUTBOUND_MAX_FEE_BPS', dbEnv, 60, addToDb)
|
||||
const network = chooseEnv('BTC_NETWORK', dbEnv, 'mainnet', addToDb) as BTCNetwork
|
||||
const oldRoutingFeeFloor = chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 5, addToDb)
|
||||
const routingFeeFloor = chooseEnvInt('ROUTING_FEE_FLOOR_SATS', dbEnv, oldRoutingFeeFloor, addToDb)
|
||||
const routingFeeLimitBps = chooseEnvInt('ROUTING_FEE_LIMIT_BPS', dbEnv, 50, addToDb)
|
||||
return {
|
||||
lndLogDir: chooseEnv('LND_LOG_DIR', dbEnv, path.join(lndDir(), "logs", "bitcoin", "mainnet", "lnd.log"), addToDb),
|
||||
feeRateBps: feeRateBps,
|
||||
feeRateLimit: feeRateBps / 10000,
|
||||
feeFixedLimit: chooseEnvInt('OUTBOUND_MAX_FEE_EXTRA_SATS', dbEnv, 100, addToDb),
|
||||
mockLnd: false
|
||||
lndLogDir: chooseEnv('LND_LOG_DIR', dbEnv, resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log"), addToDb),
|
||||
routingFeeLimitBps,
|
||||
routingFeeFloor,
|
||||
mockLnd: false,
|
||||
network: networks.includes(network) ? network : 'mainnet'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,13 +167,48 @@ export type LiquiditySettings = {
|
|||
liquidityProviderPub: string // cold setting
|
||||
useOnlyLiquidityProvider: boolean // hot setting
|
||||
disableLiquidityProvider: boolean // hot setting
|
||||
providerRelayUrl: string
|
||||
}
|
||||
export const LoadLiquiditySettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): LiquiditySettings => {
|
||||
//const liquidityProviderPub = process.env.LIQUIDITY_PROVIDER_PUB === "null" ? "" : (process.env.LIQUIDITY_PROVIDER_PUB || "76ed45f00cea7bac59d8d0b7d204848f5319d7b96c140ffb6fcbaaab0a13d44e")
|
||||
const liquidityProviderPub = chooseEnv("LIQUIDITY_PROVIDER_PUB", dbEnv, "76ed45f00cea7bac59d8d0b7d204848f5319d7b96c140ffb6fcbaaab0a13d44e", addToDb)
|
||||
const providerNprofile = chooseEnv("PROVIDER_NPROFILE", dbEnv, "nprofile1qyd8wumn8ghj7um5wfn8y7fwwd5x7cmt9ehx2arhdaexkqpqwmk5tuqvafa6ckwc6zmaypyy3af3n4aeds2ql7m0ew42kzsn638q9s9z8p", addToDb)
|
||||
const { liquidityProviderPub, providerRelayUrl } = decodeNprofile(providerNprofile)
|
||||
|
||||
const disableLiquidityProvider = chooseEnvBool("DISABLE_LIQUIDITY_PROVIDER", dbEnv, false, addToDb) || liquidityProviderPub === "null"
|
||||
const useOnlyLiquidityProvider = chooseEnvBool("USE_ONLY_LIQUIDITY_PROVIDER", dbEnv, false, addToDb)
|
||||
return { liquidityProviderPub, useOnlyLiquidityProvider, disableLiquidityProvider }
|
||||
|
||||
return { liquidityProviderPub, useOnlyLiquidityProvider, disableLiquidityProvider, providerRelayUrl }
|
||||
}
|
||||
|
||||
const decodeNprofile = (nprofile: string) => {
|
||||
const decoded = nip19.decode(nprofile)
|
||||
if (decoded.type !== 'nprofile') {
|
||||
throw new Error("PROVIDER_NPROFILE must be a valid nprofile")
|
||||
}
|
||||
if (!decoded.data.pubkey) {
|
||||
throw new Error("PROVIDER_NPROFILE must contain a pubkey")
|
||||
}
|
||||
if (!decoded.data.relays || decoded.data.relays.length === 0) {
|
||||
throw new Error("PROVIDER_NPROFILE must contain at least one relay")
|
||||
}
|
||||
return { liquidityProviderPub: decoded.data.pubkey, providerRelayUrl: decoded.data.relays[0] }
|
||||
}
|
||||
|
||||
export type SwapsSettings = {
|
||||
boltzHttpUrl: string
|
||||
boltzWebSocketUrl: string
|
||||
boltsHttpUrlAlt: string
|
||||
boltsWebSocketUrlAlt: string
|
||||
enableSwaps: boolean
|
||||
}
|
||||
|
||||
export const LoadSwapsSettingsFromEnv = (dbEnv: Record<string, string | undefined>, addToDb?: EnvCacher): SwapsSettings => {
|
||||
return {
|
||||
boltzHttpUrl: chooseEnv("BOLTZ_HTTP_URL", dbEnv, "https://swaps.zeuslsp.com/api", addToDb),
|
||||
boltzWebSocketUrl: chooseEnv("BOLTZ_WEBSOCKET_URL", dbEnv, "wss://swaps.zeuslsp.com/api", addToDb),
|
||||
boltsHttpUrlAlt: chooseEnv("BOLTZ_HTTP_URL_ALT", dbEnv, "https://api.boltz.exchange/", addToDb),
|
||||
boltsWebSocketUrlAlt: chooseEnv("BOLTZ_WEBSOCKET_URL_ALT", dbEnv, "wss://api.boltz.exchange/", addToDb),
|
||||
enableSwaps: chooseEnvBool("ENABLE_SWAPS", dbEnv, false, addToDb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import {
|
|||
LiquiditySettings, LndNodeSettings, LndSettings, LoadLiquiditySettingsFromEnv,
|
||||
LoadLSPSettingsFromEnv, LSPSettings, ServiceFeeSettings, ServiceSettings, LoadServiceFeeSettingsFromEnv,
|
||||
LoadNostrRelaySettingsFromEnv, LoadServiceSettingsFromEnv, LoadWatchdogSettingsFromEnv,
|
||||
LoadLndNodeSettingsFromEnv, LoadLndSettingsFromEnv, NostrRelaySettings, WatchdogSettings
|
||||
LoadLndNodeSettingsFromEnv, LoadLndSettingsFromEnv, NostrRelaySettings, WatchdogSettings, SwapsSettings, LoadSwapsSettingsFromEnv
|
||||
|
||||
} from "./settings.js"
|
||||
export default class SettingsManager {
|
||||
storage: Storage
|
||||
|
|
@ -27,6 +28,7 @@ export default class SettingsManager {
|
|||
serviceFeeSettings: LoadServiceFeeSettingsFromEnv(dbEnv, addToDb),
|
||||
serviceSettings: LoadServiceSettingsFromEnv(dbEnv, addToDb),
|
||||
watchDogSettings: LoadWatchdogSettingsFromEnv(dbEnv, addToDb),
|
||||
swapsSettings: LoadSwapsSettingsFromEnv(dbEnv, addToDb),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,9 +50,26 @@ export default class SettingsManager {
|
|||
for (const key in toAdd) {
|
||||
await this.storage.settingsStorage.setDbEnvIFNeeded(key, toAdd[key])
|
||||
}
|
||||
// Validate fee configuration: routing fee limit must be <= service fee
|
||||
this.validateFeeSettings(this.settings)
|
||||
return this.settings
|
||||
}
|
||||
|
||||
private validateFeeSettings(settings: FullSettings): void {
|
||||
const { serviceFeeSettings, lndSettings } = settings
|
||||
const serviceFeeBps = serviceFeeSettings.serviceFeeBps
|
||||
const routingFeeLimitBps = lndSettings.routingFeeLimitBps
|
||||
const serviceFeeFloor = serviceFeeSettings.serviceFeeFloor
|
||||
const routingFeeFloor = lndSettings.routingFeeFloor
|
||||
|
||||
if (routingFeeLimitBps > serviceFeeBps) {
|
||||
throw new Error(`ROUTING_FEE_LIMIT_BPS (${routingFeeLimitBps}) must be <= SERVICE_FEE_BPS (${serviceFeeBps}) to ensure Pub keeps a spread`)
|
||||
}
|
||||
if (routingFeeFloor > serviceFeeFloor) {
|
||||
throw new Error(`ROUTING_FEE_FLOOR_SATS (${routingFeeFloor}) must be <= SERVICE_FEE_FLOOR_SATS (${serviceFeeFloor}) to ensure Pub keeps a spread`)
|
||||
}
|
||||
}
|
||||
|
||||
getStorageSettings(): StorageSettings {
|
||||
return this.storage.getStorageSettings()
|
||||
}
|
||||
|
|
@ -148,5 +167,6 @@ type FullSettings = {
|
|||
nostrRelaySettings: NostrRelaySettings,
|
||||
serviceFeeSettings: ServiceFeeSettings,
|
||||
serviceSettings: ServiceSettings,
|
||||
lspSettings: LSPSettings
|
||||
lspSettings: LSPSettings,
|
||||
swapsSettings: SwapsSettings
|
||||
}
|
||||
|
|
@ -241,12 +241,12 @@ export default class Handler {
|
|||
ops.outgoingInvoices.forEach(i => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.OUTGOING_INVOICE, amount: i.paid_amount, inbound: false, paidAtUnix: i.paid_at_unix, confirmed: true, service_fee: i.service_fees, network_fee: i.routing_fees, identifier: "", operationId: "", tx_hash: "", internal: i.internal })
|
||||
totalSpent += i.paid_amount
|
||||
feesInRange += i.service_fees
|
||||
feesInRange += (i.service_fees - i.routing_fees)
|
||||
})
|
||||
ops.outgoingTransactions.forEach(tx => {
|
||||
if (req.include_operations) operations.push({ type: Types.UserOperationType.OUTGOING_TX, amount: tx.paid_amount, inbound: false, paidAtUnix: tx.paid_at_unix, confirmed: tx.confs > 1, service_fee: tx.service_fees, network_fee: tx.chain_fees, identifier: "", operationId: "", tx_hash: tx.tx_hash, internal: tx.internal })
|
||||
totalSpent += tx.paid_amount
|
||||
feesInRange += tx.service_fees
|
||||
feesInRange += (tx.service_fees - tx.chain_fees)
|
||||
})
|
||||
|
||||
ops.userToUser.forEach(op => {
|
||||
|
|
@ -414,6 +414,10 @@ export default class Handler {
|
|||
await this.storage.metricsStorage.AddRootOperation("chain", `${address}:${txOutput.hash}:${txOutput.index}`, amount)
|
||||
}
|
||||
|
||||
async GetRootAddressTransaction(address: string, txHash: string, index: number) {
|
||||
return this.storage.metricsStorage.GetRootOperation("chain", `${address}:${txHash}:${index}`)
|
||||
}
|
||||
|
||||
async AddRootInvoicePaid(paymentRequest: string, amount: number) {
|
||||
await this.storage.metricsStorage.AddRootOperation("invoice", paymentRequest, amount)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,37 @@
|
|||
//import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, signEvent } from 'nostr-tools'
|
||||
import WebSocket from 'ws'
|
||||
Object.assign(global, { WebSocket: WebSocket });
|
||||
import crypto from 'crypto'
|
||||
import { SimplePool, Event, UnsignedEvent, finalizeEvent, Relay, nip44 } from 'nostr-tools'
|
||||
/* import WebSocket from 'ws'
|
||||
Object.assign(global, { WebSocket: WebSocket }); */
|
||||
/* import crypto from 'crypto'
|
||||
import { SimplePool, Event, UnsignedEvent, finalizeEvent, Relay, nip44, Filter } from 'nostr-tools' */
|
||||
import { ERROR, getLogger } from '../helpers/logger.js'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
||||
/* import { nip19 } from 'nostr-tools'
|
||||
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js' */
|
||||
import { ProcessMetrics, ProcessMetricsCollector } from '../storage/tlv/processMetricsCollector.js'
|
||||
import { Subscription } from 'nostr-tools/lib/types/abstract-relay.js';
|
||||
/* import { Subscription } from 'nostr-tools/lib/types/abstract-relay.js';
|
||||
const { nprofileEncode } = nip19
|
||||
const { v2 } = nip44
|
||||
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
|
||||
const { getConversationKey: getConversationKeyV2 } = utils
|
||||
const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
const { getConversationKey: getConversationKeyV2 } = utils */
|
||||
/* const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string }
|
||||
type ClientInfo = { clientId: string, publicKey: string, privateKey: string, name: string }
|
||||
type SendDataContent = { type: "content", content: string, pub: string }
|
||||
type SendDataEvent = { type: "event", event: UnsignedEvent, encrypt?: { toPub: string } }
|
||||
export type SendData = SendDataContent | SendDataEvent
|
||||
export type SendInitiator = { type: 'app', appId: string } | { type: 'client', clientId: string }
|
||||
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void
|
||||
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void */
|
||||
import { NostrPool } from './nostrPool.js'
|
||||
import { NostrSettings, SendInitiator, SendData, NostrEvent, NostrSend } from './nostrPool.js'
|
||||
|
||||
export type NostrSettings = {
|
||||
/* export type NostrSettings = {
|
||||
apps: AppInfo[]
|
||||
relays: string[]
|
||||
clients: ClientInfo[]
|
||||
maxEventContentLength: number
|
||||
}
|
||||
providerDestinationPub: string
|
||||
} */
|
||||
|
||||
export type NostrEvent = {
|
||||
/* export type NostrEvent = {
|
||||
id: string
|
||||
pub: string
|
||||
content: string
|
||||
|
|
@ -36,7 +39,8 @@ export type NostrEvent = {
|
|||
startAtNano: string
|
||||
startAtMs: number
|
||||
kind: number
|
||||
}
|
||||
relayConstraint?: 'service' | 'provider'
|
||||
} */
|
||||
|
||||
type SettingsRequest = {
|
||||
type: 'settings'
|
||||
|
|
@ -69,9 +73,14 @@ type ProcessMetricsResponse = {
|
|||
type: 'processMetrics'
|
||||
metrics: ProcessMetrics
|
||||
}
|
||||
type BeaconResponse = {
|
||||
type: 'beacon'
|
||||
content: string
|
||||
pub: string
|
||||
}
|
||||
|
||||
export type ChildProcessRequest = SettingsRequest | SendRequest | PingRequest
|
||||
export type ChildProcessResponse = ReadyResponse | EventResponse | ProcessMetricsResponse | PingResponse
|
||||
export type ChildProcessResponse = ReadyResponse | EventResponse | ProcessMetricsResponse | PingResponse | BeaconResponse
|
||||
const send = (message: ChildProcessResponse) => {
|
||||
if (process.send) {
|
||||
process.send(message, undefined, undefined, err => {
|
||||
|
|
@ -82,7 +91,7 @@ const send = (message: ChildProcessResponse) => {
|
|||
})
|
||||
}
|
||||
}
|
||||
let subProcessHandler: Handler | undefined
|
||||
let subProcessHandler: NostrPool | undefined
|
||||
process.on("message", (message: ChildProcessRequest) => {
|
||||
switch (message.type) {
|
||||
case 'settings':
|
||||
|
|
@ -102,11 +111,15 @@ process.on("message", (message: ChildProcessRequest) => {
|
|||
const handleNostrSettings = (settings: NostrSettings) => {
|
||||
if (subProcessHandler) {
|
||||
getLogger({ component: "nostrMiddleware" })("got new nostr setting, resetting nostr handler")
|
||||
subProcessHandler.Stop()
|
||||
initNostrHandler(settings)
|
||||
subProcessHandler.UpdateSettings(settings)
|
||||
// initNostrHandler(settings)
|
||||
return
|
||||
}
|
||||
initNostrHandler(settings)
|
||||
subProcessHandler = new NostrPool(event => {
|
||||
send(event)
|
||||
})
|
||||
subProcessHandler.UpdateSettings(settings)
|
||||
// initNostrHandler(settings)
|
||||
new ProcessMetricsCollector((metrics) => {
|
||||
send({
|
||||
type: 'processMetrics',
|
||||
|
|
@ -114,14 +127,11 @@ const handleNostrSettings = (settings: NostrSettings) => {
|
|||
})
|
||||
})
|
||||
}
|
||||
const initNostrHandler = (settings: NostrSettings) => {
|
||||
subProcessHandler = new Handler(settings, event => {
|
||||
send({
|
||||
type: 'event',
|
||||
event: event
|
||||
})
|
||||
/* const initNostrHandler = (settings: NostrSettings) => {
|
||||
subProcessHandler = new NostrPool(event => {
|
||||
send(event)
|
||||
})
|
||||
}
|
||||
} */
|
||||
const sendToNostr: NostrSend = (initiator, data, relays) => {
|
||||
if (!subProcessHandler) {
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized")
|
||||
|
|
@ -131,7 +141,7 @@ const sendToNostr: NostrSend = (initiator, data, relays) => {
|
|||
}
|
||||
|
||||
send({ type: 'ready' })
|
||||
const supportedKinds = [21000, 21001, 21002, 21003]
|
||||
/* const supportedKinds = [21000, 21001, 21002, 21003]
|
||||
export default class Handler {
|
||||
pool = new SimplePool()
|
||||
settings: NostrSettings
|
||||
|
|
@ -218,18 +228,28 @@ export default class Handler {
|
|||
appIds: appIds,
|
||||
listeningForPubkeys: appIds
|
||||
})
|
||||
|
||||
return relay.subscribe([
|
||||
const subs: Filter[] = [
|
||||
{
|
||||
since: Math.ceil(Date.now() / 1000),
|
||||
kinds: supportedKinds,
|
||||
'#p': appIds,
|
||||
}
|
||||
], {
|
||||
]
|
||||
if (this.settings.providerDestinationPub) {
|
||||
subs.push({
|
||||
kinds: [30078], '#d': ['Lightning.Pub'],
|
||||
authors: [this.settings.providerDestinationPub]
|
||||
})
|
||||
}
|
||||
return relay.subscribe(subs, {
|
||||
oneose: () => {
|
||||
this.log("up to date with nostr events")
|
||||
},
|
||||
onevent: async (e) => {
|
||||
if (e.kind === 30078 && e.pubkey === this.settings.providerDestinationPub) {
|
||||
send({ type: 'beacon', content: e.content, pub: e.pubkey })
|
||||
return
|
||||
}
|
||||
if (!supportedKinds.includes(e.kind) || !e.pubkey) {
|
||||
return
|
||||
}
|
||||
|
|
@ -366,4 +386,4 @@ const splitContent = (content: string, maxLength: number) => {
|
|||
parts.push(content.slice(i, i + maxLength))
|
||||
}
|
||||
return parts
|
||||
}
|
||||
} */
|
||||
|
|
@ -1,19 +1,17 @@
|
|||
import { ChildProcess, fork } from 'child_process'
|
||||
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, SendData, SendInitiator } from "./handler.js"
|
||||
import { NostrSettings, NostrEvent, SendData, SendInitiator } from "./nostrPool.js"
|
||||
import { ChildProcessRequest, ChildProcessResponse } from "./handler.js"
|
||||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
import { getLogger, ERROR } from '../helpers/logger.js'
|
||||
type EventCallback = (event: NostrEvent) => void
|
||||
|
||||
|
||||
|
||||
|
||||
type BeaconCallback = (beacon: { content: string, pub: string }) => void
|
||||
|
||||
export default class NostrSubprocess {
|
||||
childProcess: ChildProcess
|
||||
utils: Utils
|
||||
awaitingPongs: (() => void)[] = []
|
||||
log = getLogger({})
|
||||
constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback) {
|
||||
constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback, beaconCallback: BeaconCallback) {
|
||||
this.utils = utils
|
||||
this.childProcess = fork("./build/src/services/nostr/handler")
|
||||
this.childProcess.on("error", (error) => {
|
||||
|
|
@ -43,6 +41,9 @@ export default class NostrSubprocess {
|
|||
this.awaitingPongs.forEach(resolve => resolve())
|
||||
this.awaitingPongs = []
|
||||
break
|
||||
case 'beacon':
|
||||
beaconCallback({ content: message.content, pub: message.pub })
|
||||
break
|
||||
default:
|
||||
console.error("unknown nostr event response", message)
|
||||
break;
|
||||
|
|
|
|||
325
src/services/nostr/nostrPool.ts
Normal file
325
src/services/nostr/nostrPool.ts
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
import WebSocket from 'ws'
|
||||
Object.assign(global, { WebSocket: WebSocket });
|
||||
import crypto from 'crypto'
|
||||
import { SimplePool, Event, UnsignedEvent, finalizeEvent, Relay, nip44, Filter } from 'nostr-tools'
|
||||
import { ERROR, getLogger, PubLogger } from '../helpers/logger.js'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
||||
import { Subscription } from 'nostr-tools/lib/types/abstract-relay.js';
|
||||
import { RelayConnection, RelaySettings } from './nostrRelayConnection.js'
|
||||
const { nprofileEncode } = nip19
|
||||
const { v2 } = nip44
|
||||
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
|
||||
const { getConversationKey: getConversationKeyV2 } = utils
|
||||
// const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
export type SendDataContent = { type: "content", content: string, pub: string }
|
||||
export type SendDataEvent = { type: "event", event: UnsignedEvent, encrypt?: { toPub: string } }
|
||||
export type SendData = SendDataContent | SendDataEvent
|
||||
export type SendInitiator = { type: 'app', appId: string } | { type: 'client', clientId: string }
|
||||
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void
|
||||
|
||||
export type LinkedProviderInfo = { pubkey: string, clientId: string, relayUrl: string }
|
||||
export type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string, provider?: LinkedProviderInfo }
|
||||
// export type ClientInfo = { clientId: string, publicKey: string, privateKey: string, name: string }
|
||||
export type NostrSettings = {
|
||||
apps: AppInfo[]
|
||||
relays: string[]
|
||||
// clients: ClientInfo[]
|
||||
maxEventContentLength: number
|
||||
// providerDestinationPub: string
|
||||
}
|
||||
|
||||
export type NostrEvent = {
|
||||
id: string
|
||||
pub: string
|
||||
content: string
|
||||
appId: string
|
||||
startAtNano: string
|
||||
startAtMs: number
|
||||
kind: number
|
||||
relayConstraint?: 'service' | 'provider'
|
||||
}
|
||||
type RelayEvent = { type: 'event', event: NostrEvent } | { type: 'beacon', content: string, pub: string }
|
||||
type RelayEventCallback = (event: RelayEvent) => void
|
||||
const splitContent = (content: string, maxLength: number) => {
|
||||
const parts = []
|
||||
for (let i = 0; i < content.length; i += maxLength) {
|
||||
parts.push(content.slice(i, i + maxLength))
|
||||
}
|
||||
return parts
|
||||
}
|
||||
const actionKinds = [21000, 21001, 21002, 21003]
|
||||
const beaconKind = 30078
|
||||
const appTag = "Lightning.Pub"
|
||||
export class NostrPool {
|
||||
relays: Record<string, RelayConnection> = {}
|
||||
apps: Record<string /* app pubKey */, AppInfo> = {}
|
||||
maxEventContentLength: number
|
||||
// providerDestinationPub: string | undefined
|
||||
eventCallback: RelayEventCallback
|
||||
log = getLogger({ component: "nostrMiddleware" })
|
||||
handledEvents: Map<string, { handledAt: number }> = new Map() // add expiration handler
|
||||
providerInfo: (LinkedProviderInfo & { appPub: string }) | undefined = undefined
|
||||
cleanupInterval: NodeJS.Timeout | undefined = undefined
|
||||
constructor(eventCallback: RelayEventCallback) {
|
||||
this.eventCallback = eventCallback
|
||||
}
|
||||
|
||||
StartCleanupInterval() {
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.handledEvents.forEach((value, key) => {
|
||||
if (Date.now() - value.handledAt > 1000 * 60 * 60 * 2) {
|
||||
this.handledEvents.delete(key)
|
||||
}
|
||||
})
|
||||
}, 1000 * 60 * 60 * 1)
|
||||
}
|
||||
|
||||
UpdateSettings(settings: NostrSettings) {
|
||||
Object.values(this.relays).forEach(relay => relay.Stop())
|
||||
settings.apps.forEach(app => {
|
||||
this.log("appId:", app.appId, "pubkey:", app.publicKey, "nprofile:", nprofileEncode({ pubkey: app.publicKey, relays: settings.relays }))
|
||||
})
|
||||
this.maxEventContentLength = settings.maxEventContentLength
|
||||
const { apps, rSettings, providerInfo } = processApps(settings)
|
||||
this.providerInfo = providerInfo
|
||||
this.apps = apps
|
||||
this.relays = {}
|
||||
for (const r of rSettings) {
|
||||
this.relays[r.relayUrl] = new RelayConnection(r, (e, r) => this.onEvent(e, r))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private onEvent = (e: Event, relay: RelayConnection) => {
|
||||
const validated = this.validateEvent(e, relay)
|
||||
if (!validated || this.handledEvents.has(e.id)) {
|
||||
return
|
||||
}
|
||||
this.handledEvents.set(e.id, { handledAt: Date.now() })
|
||||
if (validated.type === 'beacon') {
|
||||
this.eventCallback({ type: 'beacon', content: e.content, pub: validated.pub })
|
||||
return
|
||||
}
|
||||
const { app } = validated
|
||||
|
||||
const startAtMs = Date.now()
|
||||
const startAtNano = process.hrtime.bigint().toString()
|
||||
let content = ""
|
||||
try {
|
||||
if (e.kind === 21000) {
|
||||
content = decryptV1(e.content, getConversationKeyV1(app.privateKey, e.pubkey))
|
||||
} else {
|
||||
content = decryptV2(e.content, getConversationKeyV2(Buffer.from(app.privateKey, 'hex'), e.pubkey))
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.log(ERROR, "failed to decrypt event", e.message, e.content)
|
||||
return
|
||||
}
|
||||
const relayConstraint = relay.getConstraint()
|
||||
const nostrEvent: NostrEvent = { id: e.id, content, pub: e.pubkey, appId: app.appId, startAtNano, startAtMs, kind: e.kind, relayConstraint }
|
||||
this.eventCallback({ type: 'event', event: nostrEvent })
|
||||
}
|
||||
|
||||
private validateEvent(e: Event, relay: RelayConnection): { type: 'event', pub: string, app: AppInfo } | { type: 'beacon', content: string, pub: string } | null {
|
||||
if (e.kind === 30078 && this.providerInfo && e.pubkey === this.providerInfo.pubkey) {
|
||||
// Accept beacons from provider relay (which may also be a service relay)
|
||||
if (relay.isProviderRelay()) {
|
||||
return { type: 'beacon', content: e.content, pub: e.pubkey }
|
||||
}
|
||||
}
|
||||
if (!actionKinds.includes(e.kind) || !e.pubkey) {
|
||||
return null
|
||||
}
|
||||
const pubTags = e.tags.find(tags => tags && tags.length > 1 && tags[0] === 'p')
|
||||
if (!pubTags) {
|
||||
return null
|
||||
}
|
||||
const app = this.apps[pubTags[1]]
|
||||
if (!app) {
|
||||
return null
|
||||
}
|
||||
return { type: 'event', pub: e.pubkey, app }
|
||||
}
|
||||
|
||||
async Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
|
||||
try {
|
||||
const keys = this.getSendKeys(initiator)
|
||||
const r = this.getRelays(initiator, relays)
|
||||
const privateKey = Buffer.from(keys.privateKey, 'hex')
|
||||
const toSign = await this.handleSend(data, keys)
|
||||
await Promise.all(toSign.map(ue => this.sendEvent(ue, keys, r)))
|
||||
} catch (e: any) {
|
||||
this.log(ERROR, "failed to send event", e.message || e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private async handleSend(data: SendData, keys: { name: string, privateKey: string, publicKey: string }): Promise<UnsignedEvent[]> {
|
||||
if (data.type === 'content') {
|
||||
const parts = splitContent(data.content, this.maxEventContentLength)
|
||||
if (parts.length > 1) {
|
||||
const shardsId = crypto.randomBytes(16).toString('hex')
|
||||
const totalShards = parts.length
|
||||
const ues = await Promise.all(parts.map((part, index) => this.handleSendDataContent({ ...data, content: JSON.stringify({ part, index, totalShards, shardsId }) }, keys)))
|
||||
return ues
|
||||
}
|
||||
return [await this.handleSendDataContent(data, keys)]
|
||||
}
|
||||
const ue = await this.handleSendDataEvent(data, keys)
|
||||
return [ue]
|
||||
}
|
||||
|
||||
private async handleSendDataContent(data: SendDataContent, keys: { name: string, privateKey: string, publicKey: string }): Promise<UnsignedEvent> {
|
||||
let content = encryptV1(data.content, getConversationKeyV1(keys.privateKey, data.pub))
|
||||
return {
|
||||
content,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 21000,
|
||||
pubkey: keys.publicKey,
|
||||
tags: [['p', data.pub]],
|
||||
}
|
||||
}
|
||||
|
||||
private async handleSendDataEvent(data: SendDataEvent, keys: { name: string, privateKey: string, publicKey: string }): Promise<UnsignedEvent> {
|
||||
const toSign = data.event
|
||||
if (data.encrypt) {
|
||||
toSign.content = encryptV2(data.event.content, getConversationKeyV2(Buffer.from(keys.privateKey, 'hex'), data.encrypt.toPub))
|
||||
}
|
||||
if (!toSign.pubkey) {
|
||||
toSign.pubkey = keys.publicKey
|
||||
}
|
||||
return toSign
|
||||
}
|
||||
private getServiceRelays() {
|
||||
return Object.values(this.relays).filter(r => r.isServiceRelay()).map(r => r.GetUrl())
|
||||
}
|
||||
|
||||
private getProviderRelays() {
|
||||
return Object.values(this.relays).filter(r => r.isProviderRelay()).map(r => r.GetUrl())
|
||||
}
|
||||
|
||||
private async sendEvent(event: UnsignedEvent, keys: { name: string, privateKey: string }, relays: string[]) {
|
||||
const signed = finalizeEvent(event, Buffer.from(keys.privateKey, 'hex'))
|
||||
let sent = false
|
||||
const log = getLogger({ appName: keys.name })
|
||||
// const r = relays ? relays : this.getServiceRelays()
|
||||
const pool = new SimplePool()
|
||||
await Promise.all(pool.publish(relays, signed).map(async p => {
|
||||
try {
|
||||
await p
|
||||
sent = true
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
log(e)
|
||||
}
|
||||
}))
|
||||
if (!sent) {
|
||||
log("failed to send event")
|
||||
} else {
|
||||
//log("sent event")
|
||||
}
|
||||
}
|
||||
|
||||
private getRelays(initiator: SendInitiator, requestRelays?: string[]) {
|
||||
if (requestRelays) {
|
||||
return requestRelays
|
||||
}
|
||||
if (initiator.type === 'app') {
|
||||
return this.getServiceRelays()
|
||||
} else if (initiator.type === 'client') {
|
||||
return this.getProviderRelays()
|
||||
}
|
||||
throw new Error("unkown initiator type")
|
||||
}
|
||||
|
||||
private getSendKeys(initiator: SendInitiator) {
|
||||
if (initiator.type === 'app') {
|
||||
const { appId } = initiator
|
||||
const found = Object.values(this.apps).find((info: AppInfo) => info.appId === appId)
|
||||
if (!found) {
|
||||
throw new Error("unkown app")
|
||||
}
|
||||
return { name: found.name, publicKey: found.publicKey, privateKey: found.privateKey }
|
||||
} else if (initiator.type === 'client') {
|
||||
const { clientId } = initiator
|
||||
const providerApp = this.apps[this.providerInfo?.appPub || ""]
|
||||
if (this.providerInfo && this.providerInfo.clientId === clientId && providerApp) {
|
||||
return { name: providerApp.name, publicKey: providerApp.publicKey, privateKey: providerApp.privateKey }
|
||||
}
|
||||
throw new Error("unkown client")
|
||||
}
|
||||
throw new Error("unkown initiator type")
|
||||
}
|
||||
}
|
||||
|
||||
const processApps = (settings: NostrSettings) => {
|
||||
const apps: Record<string, AppInfo> = {}
|
||||
let providerInfo: (LinkedProviderInfo & { appPub: string }) | undefined = undefined
|
||||
|
||||
for (const app of settings.apps) {
|
||||
apps[app.publicKey] = app
|
||||
// add provider info if the app has a provider
|
||||
if (app.provider) {
|
||||
// make sure only one provider is configured
|
||||
if (providerInfo) {
|
||||
throw new Error("found more than one provider")
|
||||
}
|
||||
providerInfo = { ...app.provider, appPub: app.publicKey }
|
||||
}
|
||||
}
|
||||
let providerAssigned = false
|
||||
const rSettings: RelaySettings[] = []
|
||||
new Set(settings.relays).forEach(r => {
|
||||
const filters = [getServiceFilter(apps)]
|
||||
// check if this service relay is also a provider relay, and add the beacon filter if so
|
||||
if (providerInfo && providerInfo.relayUrl === r) {
|
||||
providerAssigned = true
|
||||
filters.push(getBeaconFilter(providerInfo.pubkey))
|
||||
}
|
||||
// add the relay settings to the list
|
||||
rSettings.push({
|
||||
relayUrl: r,
|
||||
serviceRelay: true,
|
||||
providerRelay: r === providerInfo?.relayUrl,
|
||||
filters: filters,
|
||||
})
|
||||
})
|
||||
// if no provider was assigned to a service relay, add the provider relay settings with a provider filter
|
||||
if (!providerAssigned && providerInfo) {
|
||||
rSettings.push({
|
||||
relayUrl: providerInfo.relayUrl,
|
||||
providerRelay: true,
|
||||
serviceRelay: false,
|
||||
filters: [
|
||||
getProviderFilter(providerInfo.appPub, providerInfo.pubkey),
|
||||
getBeaconFilter(providerInfo.pubkey),
|
||||
],
|
||||
})
|
||||
}
|
||||
return { apps, rSettings, providerInfo }
|
||||
}
|
||||
|
||||
const getServiceFilter = (apps: Record<string, AppInfo>): Filter => {
|
||||
return {
|
||||
since: Math.ceil(Date.now() / 1000),
|
||||
kinds: actionKinds,
|
||||
'#p': Object.keys(apps),
|
||||
}
|
||||
}
|
||||
|
||||
const getProviderFilter = (appPub: string, providerPub: string): Filter => {
|
||||
return {
|
||||
since: Math.ceil(Date.now() / 1000),
|
||||
kinds: actionKinds,
|
||||
'#p': [appPub],
|
||||
authors: [providerPub]
|
||||
}
|
||||
}
|
||||
|
||||
const getBeaconFilter = (providerPub: string): Filter => {
|
||||
return {
|
||||
kinds: [beaconKind], '#d': [appTag],
|
||||
authors: [providerPub]
|
||||
}
|
||||
}
|
||||
152
src/services/nostr/nostrRelayConnection.ts
Normal file
152
src/services/nostr/nostrRelayConnection.ts
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import WebSocket from 'ws'
|
||||
Object.assign(global, { WebSocket: WebSocket });
|
||||
import { Event, UnsignedEvent, Relay, Filter } from 'nostr-tools'
|
||||
import { ERROR, getLogger, PubLogger } from '../helpers/logger.js'
|
||||
import { Subscription } from 'nostr-tools/lib/types/abstract-relay.js';
|
||||
// const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
/* export type SendDataContent = { type: "content", content: string, pub: string }
|
||||
export type SendDataEvent = { type: "event", event: UnsignedEvent, encrypt?: { toPub: string } }
|
||||
export type SendData = SendDataContent | SendDataEvent
|
||||
export type SendInitiator = { type: 'app', appId: string } | { type: 'client', clientId: string }
|
||||
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void */
|
||||
|
||||
/* export type LinkedProviderInfo = { pubDestination: string, clientId: string, relayUrl: string }
|
||||
export type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string, provider?: LinkedProviderInfo } */
|
||||
// export type ClientInfo = { clientId: string, publicKey: string, privateKey: string, name: string }
|
||||
/* export type NostrSettings = {
|
||||
apps: AppInfo[]
|
||||
relays: string[]
|
||||
// clients: ClientInfo[]
|
||||
maxEventContentLength: number
|
||||
// providerDestinationPub: string
|
||||
}
|
||||
|
||||
export type NostrEvent = {
|
||||
id: string
|
||||
pub: string
|
||||
content: string
|
||||
appId: string
|
||||
startAtNano: string
|
||||
startAtMs: number
|
||||
kind: number
|
||||
relayConstraint?: 'service' | 'provider'
|
||||
} */
|
||||
|
||||
type RelayCallback = (event: Event, relay: RelayConnection) => void
|
||||
export type RelaySettings = { relayUrl: string, filters: Filter[], serviceRelay: boolean, providerRelay: boolean }
|
||||
|
||||
|
||||
export class RelayConnection {
|
||||
eventCallback: RelayCallback
|
||||
log: PubLogger
|
||||
relay: Relay | null = null
|
||||
sub: Subscription | null = null
|
||||
// relayUrl: string
|
||||
stopped = false
|
||||
// filters: Filter[]
|
||||
settings: RelaySettings
|
||||
constructor(settings: RelaySettings, eventCallback: RelayCallback, autoconnect = true) {
|
||||
this.log = getLogger({ component: "relay:" + settings.relayUrl })
|
||||
// this.relayUrl = relayUrl
|
||||
// this.filters = filters
|
||||
this.settings = settings
|
||||
this.eventCallback = eventCallback
|
||||
if (autoconnect) {
|
||||
this.ConnectLoop()
|
||||
}
|
||||
}
|
||||
|
||||
GetUrl() {
|
||||
return this.settings.relayUrl
|
||||
}
|
||||
|
||||
Stop() {
|
||||
this.stopped = true
|
||||
this.sub?.close()
|
||||
this.relay?.close()
|
||||
this.relay = null
|
||||
this.sub = null
|
||||
}
|
||||
|
||||
isServiceRelay() {
|
||||
return this.settings.serviceRelay
|
||||
}
|
||||
isProviderRelay() {
|
||||
return this.settings.providerRelay
|
||||
}
|
||||
|
||||
getConstraint(): 'service' | 'provider' | undefined {
|
||||
if (this.isProviderRelay() && !this.isServiceRelay()) {
|
||||
return 'provider'
|
||||
}
|
||||
if (this.isServiceRelay() && !this.isProviderRelay()) {
|
||||
return 'service'
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
async ConnectLoop() {
|
||||
let failures = 0
|
||||
while (!this.stopped) {
|
||||
await this.ConnectPromise()
|
||||
const pow = Math.pow(2, failures)
|
||||
const delay = Math.min(pow, 900)
|
||||
this.log("connection failed, will try again in", delay, "seconds (failures:", failures, ")")
|
||||
await new Promise(resolve => setTimeout(resolve, delay * 1000))
|
||||
failures++
|
||||
}
|
||||
this.log("nostr handler stopped")
|
||||
}
|
||||
|
||||
async ConnectPromise() {
|
||||
return new Promise<void>(async (res) => {
|
||||
this.relay = await this.GetRelay()
|
||||
if (!this.relay) {
|
||||
res()
|
||||
return
|
||||
}
|
||||
this.sub = this.Subscribe(this.relay)
|
||||
this.relay.onclose = (() => {
|
||||
this.log("disconnected")
|
||||
this.sub?.close()
|
||||
if (this.relay) {
|
||||
this.relay.onclose = null
|
||||
this.relay.close()
|
||||
this.relay = null
|
||||
}
|
||||
this.sub = null
|
||||
res()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async GetRelay(): Promise<Relay | null> {
|
||||
try {
|
||||
const relay = await Relay.connect(this.settings.relayUrl)
|
||||
if (!relay.connected) {
|
||||
throw new Error("failed to connect to relay")
|
||||
}
|
||||
return relay
|
||||
} catch (err: any) {
|
||||
this.log("failed to connect to relay", err.message || err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Subscribe(relay: Relay) {
|
||||
this.log("🔍 subscribing...")
|
||||
return relay.subscribe(this.settings.filters, {
|
||||
oneose: () => this.log("is ready"),
|
||||
onevent: (e) => this.eventCallback(e, this)
|
||||
})
|
||||
}
|
||||
|
||||
Send(e: Event) {
|
||||
if (!this.relay) {
|
||||
throw new Error("relay not connected")
|
||||
}
|
||||
return this.relay.publish(e)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
36
src/services/nostr/sender.ts
Normal file
36
src/services/nostr/sender.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { NostrSend, SendData, SendInitiator } from "./nostrPool.js"
|
||||
import { getLogger } from "../helpers/logger.js"
|
||||
export class NostrSender {
|
||||
private _nostrSend: NostrSend = () => { throw new Error('nostr send not initialized yet') }
|
||||
private isReady: boolean = false
|
||||
private onReadyCallbacks: (() => void)[] = []
|
||||
private pendingSends: { initiator: SendInitiator, data: SendData, relays?: string[] | undefined }[] = []
|
||||
private log = getLogger({ component: "nostrSender" })
|
||||
|
||||
AttachNostrSend(nostrSend: NostrSend) {
|
||||
this._nostrSend = nostrSend
|
||||
this.isReady = true
|
||||
this.onReadyCallbacks.forEach(cb => cb())
|
||||
this.onReadyCallbacks = []
|
||||
this.pendingSends.forEach(send => this._nostrSend(send.initiator, send.data, send.relays))
|
||||
this.pendingSends = []
|
||||
}
|
||||
OnReady(callback: () => void) {
|
||||
if (this.isReady) {
|
||||
callback()
|
||||
} else {
|
||||
this.onReadyCallbacks.push(callback)
|
||||
}
|
||||
}
|
||||
Send(initiator: SendInitiator, data: SendData, relays?: string[] | undefined) {
|
||||
if (!this.isReady) {
|
||||
this.log("tried to send before nostr was ready, caching request")
|
||||
this.pendingSends.push({ initiator, data, relays })
|
||||
return
|
||||
}
|
||||
this._nostrSend(initiator, data, relays)
|
||||
}
|
||||
IsReady() {
|
||||
return this.isReady
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +91,21 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.CloseChannel(req)
|
||||
},
|
||||
GetAdminTransactionSwapQuotes: async ({ ctx, req }) => {
|
||||
const err = Types.TransactionSwapRequestValidate(req, {
|
||||
transaction_amount_sats_CustomCheck: amt => amt > 0
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.GetAdminTransactionSwapQuotes(req)
|
||||
},
|
||||
PayAdminTransactionSwap: async ({ ctx, req }) => {
|
||||
const err = Types.PayAdminTransactionSwapRequestValidate(req, {
|
||||
address_CustomCheck: addr => addr !== '',
|
||||
swap_operation_id_CustomCheck: id => id !== '',
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.adminManager.PayAdminTransactionSwap(req)
|
||||
},
|
||||
GetProvidersDisruption: async () => {
|
||||
return mainHandler.metricsManager.GetProvidersDisruption()
|
||||
},
|
||||
|
|
@ -130,6 +145,9 @@ 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 !== ""
|
||||
|
|
@ -141,12 +159,18 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
PayAddress: async ({ ctx, req }) => {
|
||||
const err = Types.PayAddressRequestValidate(req, {
|
||||
address_CustomCheck: addr => addr !== '',
|
||||
amoutSats_CustomCheck: amt => amt > 0,
|
||||
satsPerVByte_CustomCheck: spb => spb > 0
|
||||
amountSats_CustomCheck: amt => amt > 0,
|
||||
// satsPerVByte_CustomCheck: spb => spb > 0
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.paymentManager.PayAddress(ctx, req)
|
||||
},
|
||||
ListSwaps: async ({ ctx }) => {
|
||||
return mainHandler.paymentManager.ListSwaps(ctx)
|
||||
},
|
||||
GetTransactionSwapQuotes: async ({ ctx, req }) => {
|
||||
return mainHandler.paymentManager.GetTransactionSwapQuotes(ctx, req)
|
||||
},
|
||||
NewInvoice: ({ ctx, req }) => mainHandler.appUserManager.NewInvoice(ctx, req),
|
||||
DecodeInvoice: async ({ ctx, req }) => {
|
||||
return mainHandler.paymentManager.DecodeInvoice(req)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { AppUserDevice } from "../entity/AppUserDevice.js"
|
|||
import * as fs from 'fs'
|
||||
import { UserAccess } from "../entity/UserAccess.js"
|
||||
import { AdminSettings } from "../entity/AdminSettings.js"
|
||||
import { TransactionSwap } from "../entity/TransactionSwap.js"
|
||||
|
||||
|
||||
export type DbSettings = {
|
||||
|
|
@ -74,7 +75,8 @@ export const MainDbEntities = {
|
|||
'ManagementGrant': ManagementGrant,
|
||||
'AppUserDevice': AppUserDevice,
|
||||
'UserAccess': UserAccess,
|
||||
'AdminSettings': AdminSettings
|
||||
'AdminSettings': AdminSettings,
|
||||
'TransactionSwap': TransactionSwap
|
||||
}
|
||||
export type MainDbNames = keyof typeof MainDbEntities
|
||||
export const MainDbEntitiesNames = Object.keys(MainDbEntities)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ export class TrackedProvider {
|
|||
@Column({ default: 0 })
|
||||
latest_distruption_at_unix: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
latest_checked_height: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
|
|
|
|||
74
src/services/storage/entity/TransactionSwap.ts
Normal file
74
src/services/storage/entity/TransactionSwap.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn, UpdateDateColumn } from "typeorm";
|
||||
import { User } from "./User";
|
||||
|
||||
@Entity()
|
||||
export class TransactionSwap {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
swap_operation_id: string
|
||||
|
||||
@Column()
|
||||
app_user_id: string
|
||||
|
||||
@Column()
|
||||
swap_quote_id: string
|
||||
|
||||
@Column()
|
||||
swap_tree: 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()
|
||||
preimage: string
|
||||
|
||||
@Column()
|
||||
ephemeral_public_key: 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: "" })
|
||||
failure_reason: string
|
||||
|
||||
@Column({ default: "" })
|
||||
tx_id: string
|
||||
|
||||
@Column({ default: "" })
|
||||
address_paid: string
|
||||
|
||||
@Column({ default: "" })
|
||||
service_url: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
@ -47,6 +47,9 @@ export class UserInvoicePayment {
|
|||
@Column({ nullable: true })
|
||||
debit_to_pub: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
swap_operation_id: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ export default class {
|
|||
} */
|
||||
}
|
||||
|
||||
NostrSender() {
|
||||
return this.utils.nostrSender
|
||||
}
|
||||
|
||||
getStorageSettings(): StorageSettings {
|
||||
return this.settings
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,4 +64,13 @@ export class LiquidityStorage {
|
|||
async UpdateTrackedProviderDisruption(providerType: 'lnd' | 'lnPub', pub: string, latestDisruptionAtUnix: number) {
|
||||
return this.dbs.Update<TrackedProvider>('TrackedProvider', { provider_pubkey: pub, provider_type: providerType }, { latest_distruption_at_unix: latestDisruptionAtUnix })
|
||||
}
|
||||
|
||||
async GetLatestCheckedHeight(providerType: 'lnd' | 'lnPub', pub: string): Promise<number> {
|
||||
const provider = await this.GetTrackedProvider(providerType, pub)
|
||||
return provider?.latest_checked_height || 0
|
||||
}
|
||||
|
||||
async UpdateLatestCheckedHeight(providerType: 'lnd' | 'lnPub', pub: string, height: number) {
|
||||
return this.dbs.Update<TrackedProvider>('TrackedProvider', { provider_pubkey: pub, provider_type: providerType }, { latest_checked_height: height })
|
||||
}
|
||||
}
|
||||
|
|
@ -149,6 +149,10 @@ export default class {
|
|||
return this.dbs.CreateAndSave<RootOperation>('RootOperation', { operation_type: opType, operation_amount: amount, operation_identifier: id, at_unix: Math.floor(Date.now() / 1000) }, txId)
|
||||
}
|
||||
|
||||
async GetRootOperation(opType: string, id: string, txId?: string) {
|
||||
return this.dbs.FindOne<RootOperation>('RootOperation', { where: { operation_type: opType, operation_identifier: id } }, txId)
|
||||
}
|
||||
|
||||
async GetRootOperations({ from, to }: { from?: number, to?: number }, txId?: string) {
|
||||
const q = getTimeQuery({ from, to })
|
||||
return this.dbs.Find<RootOperation>('RootOperation', q, txId)
|
||||
|
|
|
|||
26
src/services/storage/migrations/1762890527098-tx_swap.ts
Normal file
26
src/services/storage/migrations/1762890527098-tx_swap.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TxSwap1762890527098 implements MigrationInterface {
|
||||
name = 'TxSwap1762890527098'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "transaction_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "lockup_address" varchar NOT NULL, "refund_public_key" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "preimage" varchar NOT NULL, "ephemeral_public_key" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "paymentIndex" integer NOT NULL DEFAULT (-1), "debit_to_pub" varchar, "swap_operation_id" varchar, CONSTRAINT "FK_ef2aa6761ab681bbbd5f94e0fcb" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_6bcac90887eea1dc61d37db2994" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex", "debit_to_pub") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex", "debit_to_pub" FROM "user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "user_invoice_payment"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_invoice_payment" RENAME TO "user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_invoice_payment" RENAME TO "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "paymentIndex" integer NOT NULL DEFAULT (-1), "debit_to_pub" varchar, CONSTRAINT "FK_ef2aa6761ab681bbbd5f94e0fcb" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_6bcac90887eea1dc61d37db2994" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex", "debit_to_pub") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex", "debit_to_pub" FROM "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_invoice_payment"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
|
||||
await queryRunner.query(`DROP TABLE "transaction_swap"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TxSwapAddress1764779178945 implements MigrationInterface {
|
||||
name = 'TxSwapAddress1764779178945'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "temporary_transaction_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "lockup_address" varchar NOT NULL, "refund_public_key" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "preimage" varchar NOT NULL, "ephemeral_public_key" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "address_paid" varchar NOT NULL DEFAULT (''))`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_transaction_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at" FROM "transaction_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "transaction_swap"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_transaction_swap" RENAME TO "transaction_swap"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "transaction_swap" RENAME TO "temporary_transaction_swap"`);
|
||||
await queryRunner.query(`CREATE TABLE "transaction_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "lockup_address" varchar NOT NULL, "refund_public_key" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "preimage" varchar NOT NULL, "ephemeral_public_key" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`INSERT INTO "transaction_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at" FROM "temporary_transaction_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_transaction_swap"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TrackedProviderHeight1766504040000 implements MigrationInterface {
|
||||
name = 'TrackedProviderHeight1766504040000'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tracked_provider" ADD "latest_checked_height" integer NOT NULL DEFAULT (0)`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tracked_provider" DROP COLUMN "latest_checked_height"`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class SwapsServiceUrl1768413055036 implements MigrationInterface {
|
||||
name = 'SwapsServiceUrl1768413055036'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "temporary_transaction_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "lockup_address" varchar NOT NULL, "refund_public_key" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "preimage" varchar NOT NULL, "ephemeral_public_key" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "address_paid" varchar NOT NULL DEFAULT (''), "service_url" varchar NOT NULL DEFAULT (''))`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_transaction_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at", "address_paid") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at", "address_paid" FROM "transaction_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "transaction_swap"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_transaction_swap" RENAME TO "transaction_swap"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "transaction_swap" RENAME TO "temporary_transaction_swap"`);
|
||||
await queryRunner.query(`CREATE TABLE "transaction_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "lockup_address" varchar NOT NULL, "refund_public_key" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "preimage" varchar NOT NULL, "ephemeral_public_key" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "address_paid" varchar NOT NULL DEFAULT (''))`);
|
||||
await queryRunner.query(`INSERT INTO "transaction_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at", "address_paid") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at", "address_paid" FROM "temporary_transaction_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_transaction_swap"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,14 +27,19 @@ import { UserAccess1759426050669 } from './1759426050669-user_access.js'
|
|||
import { AddBlindToUserOffer1760000000000 } from './1760000000000-add_blind_to_user_offer.js'
|
||||
import { ApplicationAvatarUrl1761000001000 } from './1761000001000-application_avatar_url.js'
|
||||
import { AdminSettings1761683639419 } from './1761683639419-admin_settings.js'
|
||||
import { TxSwap1762890527098 } from './1762890527098-tx_swap.js'
|
||||
import { TxSwapAddress1764779178945 } from './1764779178945-tx_swap_address.js'
|
||||
import { ClinkRequester1765497600000 } from './1765497600000-clink_requester.js'
|
||||
import { TrackedProviderHeight1766504040000 } from './1766504040000-tracked_provider_height.js'
|
||||
import { SwapsServiceUrl1768413055036 } from './1768413055036-swaps_service_url.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, ClinkRequester1765497600000]
|
||||
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098,
|
||||
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036]
|
||||
|
||||
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
|
||||
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import crypto from 'crypto';
|
||||
import { And, Between, Equal, FindOperator, IsNull, LessThan, LessThanOrEqual, MoreThan, MoreThanOrEqual } from "typeorm"
|
||||
import { And, Between, Equal, FindOperator, IsNull, LessThan, LessThanOrEqual, MoreThan, MoreThanOrEqual, Not } from "typeorm"
|
||||
import { User } from './entity/User.js';
|
||||
import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
|
||||
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
|
||||
|
|
@ -14,6 +14,7 @@ import { Application } from './entity/Application.js';
|
|||
import TransactionsQueue from "./db/transactionsQueue.js";
|
||||
import { LoggedEvent } from './eventsLog.js';
|
||||
import { StorageInterface } from './db/storageInterface.js';
|
||||
import { TransactionSwap } from './entity/TransactionSwap.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 {
|
||||
|
|
@ -160,7 +161,8 @@ export default class {
|
|||
return this.dbs.FindOne<UserToUserPayment>('UserToUserPayment', { where: { serial_id: serialId } }, txId)
|
||||
}
|
||||
|
||||
async AddPendingExternalPayment(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, networkFee: number }, linkedApplication: Application, liquidityProvider: string | undefined, txId: string, debitNpub?: string): Promise<UserInvoicePayment> {
|
||||
async AddPendingExternalPayment(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, networkFee: number }, linkedApplication: Application, liquidityProvider: string | undefined, txId: string, optionals: { debitNpub?: string, swapOperationId?: string } = {}): Promise<UserInvoicePayment> {
|
||||
const { debitNpub, swapOperationId } = optionals
|
||||
const user = await this.userStorage.GetUser(userId, txId)
|
||||
return this.dbs.CreateAndSave<UserInvoicePayment>('UserInvoicePayment', {
|
||||
user,
|
||||
|
|
@ -172,7 +174,8 @@ export default class {
|
|||
internal: false,
|
||||
linkedApplication,
|
||||
liquidityProvider,
|
||||
debit_to_pub: debitNpub
|
||||
debit_to_pub: debitNpub,
|
||||
swap_operation_id: swapOperationId
|
||||
}, txId)
|
||||
}
|
||||
|
||||
|
|
@ -460,6 +463,58 @@ export default class {
|
|||
}
|
||||
return this.dbs.Find<UserReceivingInvoice>('UserReceivingInvoice', { where })
|
||||
}
|
||||
|
||||
async AddTransactionSwap(swap: Partial<TransactionSwap>) {
|
||||
return this.dbs.CreateAndSave<TransactionSwap>('TransactionSwap', swap)
|
||||
}
|
||||
|
||||
async GetTransactionSwap(swapOperationId: string, appUserId: string, txId?: string) {
|
||||
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) {
|
||||
return this.dbs.Update<TransactionSwap>('TransactionSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
tx_id: txId,
|
||||
address_paid: address,
|
||||
})
|
||||
}
|
||||
|
||||
async FailTransactionSwap(swapOperationId: string, address: string, failureReason: string) {
|
||||
return this.dbs.Update<TransactionSwap>('TransactionSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
failure_reason: failureReason,
|
||||
address_paid: address,
|
||||
})
|
||||
}
|
||||
|
||||
async DeleteTransactionSwap(swapOperationId: string, txId?: string) {
|
||||
return this.dbs.Delete<TransactionSwap>('TransactionSwap', { swap_operation_id: swapOperationId }, txId)
|
||||
}
|
||||
|
||||
async DeleteExpiredTransactionSwaps(currentHeight: number, txId?: string) {
|
||||
return this.dbs.Delete<TransactionSwap>('TransactionSwap', { timeout_block_height: LessThan(currentHeight) }, txId)
|
||||
}
|
||||
|
||||
async ListPendingTransactionSwaps(appUserId: string, txId?: string) {
|
||||
return this.dbs.Find<TransactionSwap>('TransactionSwap', { where: { used: false, app_user_id: appUserId } }, txId)
|
||||
}
|
||||
|
||||
async ListSwapPayments(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) {
|
||||
const completed = await this.dbs.Find<TransactionSwap>('TransactionSwap', { where: { used: true, app_user_id: appUserId } }, txId)
|
||||
// const payments = await this.dbs.Find<UserInvoicePayment>('UserInvoicePayment', { where: { swap_operation_id: Not(IsNull()), } }, txId)
|
||||
const paymentsMap = new Map<string, UserInvoicePayment>()
|
||||
payments.forEach(p => {
|
||||
paymentsMap.set(p.swap_operation_id, p)
|
||||
})
|
||||
return completed.map(c => ({
|
||||
swap: c, payment: paymentsMap.get(c.swap_operation_id)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const orFail = async <T>(resultPromise: Promise<T | null>) => {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import { ChildProcess, fork } from 'child_process';
|
|||
import { EventEmitter } from 'events';
|
||||
import { AddTlvOperation, ITlvStorageOperation, SuccessTlvOperationResponse, LoadLatestTlvOperation, LoadTlvFileOperation, NewTlvStorageOperation, SerializableLatestData, SerializableTlvFile, TlvOperationResponse, TlvStorageSettings, WebRtcMessageOperation, ProcessMetricsTlvOperation, ZipStoragesOperation, ResetTlvStorageOperation, PingTlvOperation } from './tlvFilesStorageProcessor';
|
||||
import { LatestData, TlvFile } from './tlvFilesStorage';
|
||||
import { NostrSend, SendData, SendInitiator } from '../../nostr/handler';
|
||||
import { SendData, SendInitiator } from '../../nostr/nostrPool.js';
|
||||
import { WebRtcUserInfo } from '../../webRTC';
|
||||
import * as Types from '../../../../proto/autogenerated/ts/types.js'
|
||||
import { ProcessMetrics } from './processMetricsCollector';
|
||||
import { getLogger, ERROR } from '../../helpers/logger.js';
|
||||
import { NostrSender } from '../../nostr/sender';
|
||||
export type TlvStorageInterface = {
|
||||
AddTlv: (appId: string, dataName: string, tlv: Uint8Array) => Promise<number>
|
||||
LoadLatest: (limit?: number) => Promise<LatestData>
|
||||
|
|
@ -17,12 +18,14 @@ export class TlvStorageFactory extends EventEmitter {
|
|||
private process: ChildProcess;
|
||||
private isConnected: boolean = false;
|
||||
private debug: boolean = false;
|
||||
private _nostrSend: NostrSend = () => { throw new Error('nostr send not initialized yet') }
|
||||
// private _nostrSend: NostrSend = () => { throw new Error('nostr send not initialized yet') }
|
||||
private nostrSender: NostrSender
|
||||
private allowResetMetricsStorages: boolean
|
||||
log = getLogger({component: 'TlvStorageFactory'})
|
||||
constructor(allowResetMetricsStorages: boolean) {
|
||||
log = getLogger({ component: 'TlvStorageFactory' })
|
||||
constructor(allowResetMetricsStorages: boolean, nostrSender: NostrSender) {
|
||||
super();
|
||||
this.allowResetMetricsStorages = allowResetMetricsStorages
|
||||
this.nostrSender = nostrSender
|
||||
this.initializeSubprocess();
|
||||
}
|
||||
|
||||
|
|
@ -30,15 +33,8 @@ export class TlvStorageFactory extends EventEmitter {
|
|||
this.debug = debug;
|
||||
}
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this._nostrSend = f
|
||||
}
|
||||
|
||||
private nostrSend = (opResponse: SuccessTlvOperationResponse<{ initiator: SendInitiator, data: SendData, relays?: string[] }>) => {
|
||||
if (!this._nostrSend) {
|
||||
throw new Error("No nostrSend attached")
|
||||
}
|
||||
this._nostrSend(opResponse.data.initiator, opResponse.data.data, opResponse.data.relays)
|
||||
this.nostrSender.Send(opResponse.data.initiator, opResponse.data.data, opResponse.data.relays)
|
||||
}
|
||||
|
||||
private initializeSubprocess() {
|
||||
|
|
@ -134,10 +130,15 @@ export class TlvStorageFactory extends EventEmitter {
|
|||
return this.handleOp<Types.WebRtcAnswer>(op)
|
||||
}
|
||||
|
||||
ProcessMetrics(metrics: ProcessMetrics, processName: string): Promise<void> {
|
||||
async ProcessMetrics(metrics: ProcessMetrics, processName: string): Promise<void> {
|
||||
const opId = Math.random().toString()
|
||||
const op: ProcessMetricsTlvOperation = { type: 'processMetrics', opId, metrics, processName }
|
||||
return this.handleOp<void>(op)
|
||||
try {
|
||||
return this.handleOp<void>(op)
|
||||
} catch (error: any) {
|
||||
this.log(ERROR, 'Error processing metrics', error.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { PubLogger, getLogger } from '../../helpers/logger.js';
|
|||
import webRTC, { WebRtcUserInfo } from '../../webRTC/index.js';
|
||||
import { TlvFilesStorage } from './tlvFilesStorage.js';
|
||||
import * as Types from '../../../../proto/autogenerated/ts/types.js'
|
||||
import { SendData } from '../../nostr/handler.js';
|
||||
import { SendInitiator } from '../../nostr/handler.js';
|
||||
import { SendData } from '../../nostr/nostrPool.js';
|
||||
import { SendInitiator } from '../../nostr/nostrPool.js';
|
||||
import { ProcessMetrics, ProcessMetricsCollector } from './processMetricsCollector.js';
|
||||
import { integerToUint8Array } from '../../helpers/tlv.js';
|
||||
import { zip } from 'zip-a-folder'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import wrtc from 'wrtc'
|
|||
import Storage from '../storage/index.js'
|
||||
import { ERROR, getLogger } from "../helpers/logger.js"
|
||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { NostrSend, SendData, SendInitiator } from "../nostr/handler.js"
|
||||
import { NostrSend, SendData, SendInitiator } from "../nostr/nostrPool.js"
|
||||
import { encodeTLbV, encodeTLV, encodeTLVDataPacket } from '../helpers/tlv.js'
|
||||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
import { TlvFilesStorage } from '../storage/tlv/tlvFilesStorage.js'
|
||||
|
|
@ -111,7 +111,7 @@ export default class webRTC {
|
|||
const packet = packets[i]
|
||||
const tlv = encodeTLVDataPacket({ dataId: id, packetNum: i + 1, totalPackets: packets.length, data: packet })
|
||||
const bytes = encodeTLbV(tlv)
|
||||
channel.send(bytes)
|
||||
channel.send(new Uint8Array(bytes))
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.log(ERROR, 'ondatachannel', e.message || e)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ DATABASE_FILE=db.sqlite
|
|||
JWT_SECRET=bigsecrethere
|
||||
ALLOW_BALANCE_MIGRATION=true
|
||||
OUTBOUND_MAX_FEE_BPS=60
|
||||
OUTBOUND_MAX_FEE_EXTRA_SATS=100
|
||||
OUTBOUND_MAX_FEE_EXTRA_SATS=10
|
||||
INCOMING_CHAIN_FEE_ROOT_BPS=0
|
||||
OUTGOING_CHAIN_FEE_ROOT_BPS=60 #this is applied only to withdrawls from application wallets
|
||||
INCOMING_INVOICE_FEE_ROOT_BPS=0
|
||||
|
|
|
|||
|
|
@ -24,11 +24,10 @@ const testSuccessfulExternalPayment = async (T: TestBase) => {
|
|||
T.d("paid 500 sats invoice from user1")
|
||||
const u1 = await T.main.storage.userStorage.GetUser(T.user1.userId)
|
||||
const owner = await T.main.storage.userStorage.GetUser(application.owner.user_id)
|
||||
expect(u1.balance_sats).to.be.equal(1496)
|
||||
T.d("user1 balance is now 1496 (2000 - (500 + 3 fee + 1 routing))")
|
||||
expect(owner.balance_sats).to.be.equal(3)
|
||||
T.d("app balance is 3 sats")
|
||||
|
||||
expect(u1.balance_sats).to.be.equal(1490)
|
||||
T.d("user1 balance is now 1490 (2000 - (500 + 10fee))")
|
||||
expect(owner.balance_sats).to.be.equal(9)
|
||||
T.d("app balance is 9 sats")
|
||||
}
|
||||
|
||||
const testFailedExternalPayment = async (T: TestBase) => {
|
||||
|
|
@ -41,11 +40,11 @@ const testFailedExternalPayment = async (T: TestBase) => {
|
|||
await expectThrowsAsync(T.main.paymentManager.PayInvoice(T.user1.userId, { invoice: invoice.payRequest, amount: 0 }, application), "not enough balance to decrement")
|
||||
T.d("payment failed as expected, with the expected error message")
|
||||
const u1 = await T.main.storage.userStorage.GetUser(T.user1.userId)
|
||||
expect(u1.balance_sats).to.be.equal(1496)
|
||||
T.d("user1 balance is still 1496")
|
||||
expect(u1.balance_sats).to.be.equal(1490)
|
||||
T.d("user1 balance is still 1490")
|
||||
const owner = await T.main.storage.userStorage.GetUser(application.owner.user_id)
|
||||
expect(owner.balance_sats).to.be.equal(3)
|
||||
T.d("app balance is still 3 sats")
|
||||
expect(owner.balance_sats).to.be.equal(9)
|
||||
T.d("app balance is still 9 sats")
|
||||
}
|
||||
|
||||
const testSuccesfulReceivedExternalChainPayment = async (T: TestBase) => {
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ const testSuccessfulInternalPayment = async (T: TestBase) => {
|
|||
const owner = await T.main.storage.userStorage.GetUser(application.owner.user_id)
|
||||
expect(u2.balance_sats).to.be.equal(1000)
|
||||
T.d("user2 balance is 1000")
|
||||
expect(u1.balance_sats).to.be.equal(994)
|
||||
T.d("user1 balance is 994 cuz he paid 6 sats fee")
|
||||
expect(owner.balance_sats).to.be.equal(6)
|
||||
T.d("app balance is 6 sats")
|
||||
expect(u1.balance_sats).to.be.equal(990)
|
||||
T.d("user1 balance is 990 cuz he paid 10 sats fee")
|
||||
expect(owner.balance_sats).to.be.equal(10)
|
||||
T.d("app balance is 10 sats")
|
||||
}
|
||||
|
||||
const testFailedInternalPayment = async (T: TestBase) => {
|
||||
|
|
|
|||
|
|
@ -21,30 +21,30 @@ export default async (T: TestBase) => {
|
|||
|
||||
const testInboundPaymentFromProvider = async (T: TestBase, bootstrapped: Main, bUser: TestUserData) => {
|
||||
T.d("starting testInboundPaymentFromProvider")
|
||||
const invoiceRes = await bootstrapped.appUserManager.NewInvoice({ app_id: bUser.appId, user_id: bUser.userId, app_user_id: bUser.appUserIdentifier }, { amountSats: 2000, memo: "liquidityTest" })
|
||||
const invoiceRes = await bootstrapped.appUserManager.NewInvoice({ app_id: bUser.appId, user_id: bUser.userId, app_user_id: bUser.appUserIdentifier }, { amountSats: 3000, memo: "liquidityTest" })
|
||||
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoiceRes.invoice, 0, 100, 2000, { from: 'system', useProvider: false })
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoiceRes.invoice, 0, { routingFeeLimit: 100, serviceFee: 100 }, 3000, { from: 'system', useProvider: false })
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
const userBalance = await bootstrapped.appUserManager.GetUserInfo({ app_id: bUser.appId, user_id: bUser.userId, app_user_id: bUser.appUserIdentifier })
|
||||
T.expect(userBalance.balance).to.equal(2000)
|
||||
T.d("user balance is 2000")
|
||||
T.expect(userBalance.balance).to.equal(3000)
|
||||
T.d("user balance is 3000")
|
||||
const providerBalance = await bootstrapped.liquidityProvider.GetLatestBalance()
|
||||
T.expect(providerBalance).to.equal(2000)
|
||||
T.d("provider balance is 2000")
|
||||
T.expect(providerBalance).to.equal(3000)
|
||||
T.d("provider balance is 3000")
|
||||
T.d("testInboundPaymentFromProvider done")
|
||||
}
|
||||
|
||||
const testOutboundPaymentFromProvider = async (T: TestBase, bootstrapped: Main, bootstrappedUser: TestUserData) => {
|
||||
T.d("starting testOutboundPaymentFromProvider")
|
||||
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(1000, "", 60 * 60, { from: 'system', useProvider: false })
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(2000, "", 60 * 60, { from: 'system', useProvider: false })
|
||||
const ctx = { app_id: bootstrappedUser.appId, user_id: bootstrappedUser.userId, app_user_id: bootstrappedUser.appUserIdentifier }
|
||||
const res = await bootstrapped.appUserManager.PayInvoice(ctx, { invoice: invoice.payRequest, amount: 0 })
|
||||
|
||||
const userBalance = await bootstrapped.appUserManager.GetUserInfo(ctx)
|
||||
T.expect(userBalance.balance).to.equal(986) // 2000 - (1000 + 6(x2) + 2)
|
||||
T.expect(userBalance.balance).to.equal(988) // 3000 - (2000 + 12)
|
||||
|
||||
const providerBalance = await bootstrapped.liquidityProvider.GetLatestBalance()
|
||||
T.expect(providerBalance).to.equal(992) // 2000 - (1000 + 6 +2)
|
||||
T.expect(providerBalance).to.equal(988) // 3000 - (2000 + 12)
|
||||
T.d("testOutboundPaymentFromProvider done")
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import LND from '../services/lnd/lnd.js'
|
|||
import { LiquidityProvider } from "../services/main/liquidityProvider.js"
|
||||
import { Utils } from "../services/helpers/utilsWrapper.js"
|
||||
import { LoadStorageSettingsFromEnv } from "../services/storage/index.js"
|
||||
import { NostrSender } from "../services/nostr/sender.js"
|
||||
|
||||
export type ChainTools = {
|
||||
mine: (amount: number) => Promise<void>
|
||||
|
|
@ -15,7 +16,8 @@ export type ChainTools = {
|
|||
|
||||
export const setupNetwork = async (): Promise<ChainTools> => {
|
||||
const storageSettings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||
const setupUtils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages })
|
||||
const nostrSender = new NostrSender()
|
||||
const setupUtils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages }, nostrSender)
|
||||
//const settingsManager = new SettingsManager(storageSettings)
|
||||
const core = new BitcoinCoreWrapper(LoadBitcoinCoreSettingsFromEnv())
|
||||
await core.InitAddress()
|
||||
|
|
@ -23,7 +25,7 @@ export const setupNetwork = async (): Promise<ChainTools> => {
|
|||
const lndSettings = LoadLndSettingsFromEnv({})
|
||||
const lndNodeSettings = LoadLndNodeSettingsFromEnv({})
|
||||
const secondLndNodeSettings = LoadSecondLndSettingsFromEnv()
|
||||
const liquiditySettings: LiquiditySettings = { disableLiquidityProvider: true, liquidityProviderPub: "", useOnlyLiquidityProvider: false }
|
||||
const liquiditySettings: LiquiditySettings = { disableLiquidityProvider: true, liquidityProviderPub: "", useOnlyLiquidityProvider: false, providerRelayUrl: "" }
|
||||
const alice = new LND(() => ({ lndSettings, lndNodeSettings }), new LiquidityProvider(() => liquiditySettings, setupUtils, async () => { }, async () => { }), async () => { }, setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||
const bob = new LND(() => ({ lndSettings, lndNodeSettings: secondLndNodeSettings }), new LiquidityProvider(() => liquiditySettings, setupUtils, async () => { }, async () => { }), async () => { }, setupUtils, async () => { }, async () => { }, () => { }, () => { }, () => { })
|
||||
await tryUntil<void>(async i => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { getLogger } from '../services/helpers/logger.js'
|
||||
import { initMainHandler, initSettings } from '../services/main/init.js'
|
||||
import { SendData } from '../services/nostr/handler.js'
|
||||
import { SendData } from '../services/nostr/nostrPool.js'
|
||||
import { TestBase, TestUserData } from './testBase.js'
|
||||
import * as Types from '../../proto/autogenerated/ts/types.js'
|
||||
import { GetTestStorageSettings, LoadStorageSettingsFromEnv } from '../services/storage/index.js'
|
||||
|
|
@ -20,19 +20,19 @@ export const initBootstrappedInstance = async (T: TestBase) => {
|
|||
if (!initialized) {
|
||||
throw new Error("failed to initialize bootstrapped main handler")
|
||||
}
|
||||
const { mainHandler: bootstrapped, liquidityProviderInfo, liquidityProviderApp } = initialized
|
||||
const { mainHandler: bootstrapped, localProviderClient } = initialized
|
||||
T.main.attachNostrSend(async (_, data, r) => {
|
||||
if (data.type === 'event') {
|
||||
throw new Error("unsupported event type")
|
||||
}
|
||||
if (data.pub !== liquidityProviderInfo.publicKey) {
|
||||
throw new Error("invalid pub " + data.pub + " expected " + liquidityProviderInfo.publicKey)
|
||||
if (data.pub !== localProviderClient.publicKey) {
|
||||
throw new Error("invalid pub " + data.pub + " expected " + localProviderClient.publicKey)
|
||||
}
|
||||
const j = JSON.parse(data.content) as { requestId: string }
|
||||
console.log("sending new operation to provider")
|
||||
bootstrapped.liquidityProvider.onEvent(j, T.app.publicKey)
|
||||
})
|
||||
bootstrapped.liquidityProvider.attachNostrSend(async (_, data, r) => {
|
||||
bootstrapped.attachNostrSend(async (_, data, r) => {
|
||||
const res = await handleSend(T, data)
|
||||
if (data.type === 'event') {
|
||||
throw new Error("unsupported event type")
|
||||
|
|
@ -42,10 +42,10 @@ export const initBootstrappedInstance = async (T: TestBase) => {
|
|||
}
|
||||
bootstrapped.liquidityProvider.onEvent(res, data.pub)
|
||||
})
|
||||
bootstrapped.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
bootstrapped.liquidityProvider.setNostrInfo({ localId: `client_${localProviderClient.appId}`, localPubkey: localProviderClient.publicKey })
|
||||
await new Promise<void>(res => {
|
||||
const interval = setInterval(async () => {
|
||||
const canHandle = await bootstrapped.liquidityProvider.CanProviderHandle({ action: 'receive', amount: 2000 })
|
||||
const canHandle = bootstrapped.liquidityProvider.IsReady()
|
||||
if (canHandle) {
|
||||
clearInterval(interval)
|
||||
res()
|
||||
|
|
@ -54,10 +54,10 @@ export const initBootstrappedInstance = async (T: TestBase) => {
|
|||
}
|
||||
}, 500)
|
||||
})
|
||||
const bUser = await bootstrapped.applicationManager.AddAppUser(liquidityProviderApp.appId, { identifier: "user1_bootstrapped", balance: 0, fail_if_exists: true })
|
||||
const bootstrappedUser: TestUserData = { userId: bUser.info.userId, appUserIdentifier: bUser.identifier, appId: liquidityProviderApp.appId }
|
||||
const bUser = await bootstrapped.applicationManager.AddAppUser(localProviderClient.appId, { identifier: "user1_bootstrapped", balance: 0, fail_if_exists: true })
|
||||
const bootstrappedUser: TestUserData = { userId: bUser.info.userId, appUserIdentifier: bUser.identifier, appId: localProviderClient.appId }
|
||||
return {
|
||||
bootstrapped, liquidityProviderInfo, liquidityProviderApp, bootstrappedUser, stop: () => {
|
||||
bootstrapped, localProviderClient, bootstrappedUser, stop: () => {
|
||||
bootstrapped.Stop()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,16 +29,15 @@ const testSpamExternalPayment = async (T: TestBase) => {
|
|||
const failedPayments = res.filter(r => !r.success)
|
||||
console.log(failedPayments)
|
||||
failedPayments.forEach(f => expect(f.err).to.be.equal("not enough balance to decrement"))
|
||||
successfulPayments.forEach(s => expect(s.result).to.contain({ amount_paid: 500, network_fee: 1, service_fee: 3 }))
|
||||
successfulPayments.forEach(s => expect(s.result).to.contain({ amount_paid: 500, network_fee: 0, service_fee: 10 }))
|
||||
expect(successfulPayments.length).to.be.equal(3)
|
||||
expect(failedPayments.length).to.be.equal(7)
|
||||
T.d("3 payments succeeded, 7 failed as expected")
|
||||
const u = await T.main.storage.userStorage.GetUser(T.user1.userId)
|
||||
const owner = await T.main.storage.userStorage.GetUser(application.owner.user_id)
|
||||
expect(u.balance_sats).to.be.equal(488)
|
||||
T.d("user1 balance is now 488 (2000 - (500 + 3 fee + 1 routing) * 3)")
|
||||
expect(owner.balance_sats).to.be.equal(9)
|
||||
T.d("app balance is 9 sats")
|
||||
|
||||
expect(u.balance_sats).to.be.equal(470)
|
||||
T.d("user1 balance is now 470 (2000 - (500 + 10 fee) * 3)")
|
||||
expect(owner.balance_sats).to.be.equal(27)
|
||||
T.d("app balance is 27 sats")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { AdminManager } from '../services/main/adminManager.js'
|
|||
import { TlvStorageFactory } from '../services/storage/tlv/tlvFilesStorageFactory.js'
|
||||
import { ChainTools } from './networkSetup.js'
|
||||
import { LiquiditySettings, LoadLndSettingsFromEnv, LoadSecondLndSettingsFromEnv, LoadThirdLndSettingsFromEnv } from '../services/main/settings.js'
|
||||
import { NostrSender } from '../services/nostr/sender.js'
|
||||
chai.use(chaiString)
|
||||
export const expect = chai.expect
|
||||
export type Describe = (message: string, failure?: boolean) => void
|
||||
|
|
@ -46,7 +47,8 @@ export type StorageTestBase = {
|
|||
|
||||
export const setupStorageTest = async (d: Describe): Promise<StorageTestBase> => {
|
||||
const settings = GetTestStorageSettings(LoadStorageSettingsFromEnv())
|
||||
const utils = new Utils({ dataDir: settings.dataDir, allowResetMetricsStorages: true })
|
||||
const nostrSender = new NostrSender()
|
||||
const utils = new Utils({ dataDir: settings.dataDir, allowResetMetricsStorages: true }, nostrSender)
|
||||
const storageManager = new Storage(settings, utils)
|
||||
await storageManager.Connect(console.log)
|
||||
return {
|
||||
|
|
@ -79,11 +81,11 @@ export const SetupTest = async (d: Describe, chainTools: ChainTools): Promise<Te
|
|||
const u2 = await main.applicationManager.AddAppUser(app.appId, { identifier: "user2", balance: 0, fail_if_exists: true })
|
||||
const user1 = { userId: u1.info.userId, appUserIdentifier: u1.identifier, appId: app.appId }
|
||||
const user2 = { userId: u2.info.userId, appUserIdentifier: u2.identifier, appId: app.appId }
|
||||
|
||||
const extermnalUtils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages })
|
||||
const nostrSender = new NostrSender()
|
||||
const extermnalUtils = new Utils({ dataDir: storageSettings.dataDir, allowResetMetricsStorages: storageSettings.allowResetMetricsStorages }, nostrSender)
|
||||
/* const externalAccessToMainLnd = new LND(settings.lndSettings, new LiquidityProvider("", extermnalUtils, async () => { }, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
||||
await externalAccessToMainLnd.Warmup() */
|
||||
const liquiditySettings: LiquiditySettings = { disableLiquidityProvider: true, liquidityProviderPub: "", useOnlyLiquidityProvider: false }
|
||||
const liquiditySettings: LiquiditySettings = { disableLiquidityProvider: true, liquidityProviderPub: "", useOnlyLiquidityProvider: false, providerRelayUrl: "" }
|
||||
const lndSettings = LoadLndSettingsFromEnv({})
|
||||
const secondLndNodeSettings = LoadSecondLndSettingsFromEnv()
|
||||
const otherLndSetting = () => ({ lndSettings, lndNodeSettings: secondLndNodeSettings })
|
||||
|
|
@ -119,7 +121,7 @@ export const teardown = async (T: TestBase) => {
|
|||
export const safelySetUserBalance = async (T: TestBase, user: TestUserData, amount: number) => {
|
||||
const app = await T.main.storage.applicationStorage.GetApplication(user.appId)
|
||||
const invoice = await T.main.paymentManager.NewInvoice(user.userId, { amountSats: amount, memo: "test" }, { linkedApplication: app, expiry: defaultInvoiceExpiry })
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoice.invoice, 0, 100, amount, { from: 'system', useProvider: false })
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoice.invoice, 0, { routingFeeLimit: 100, serviceFee: 100 }, amount, { from: 'system', useProvider: false })
|
||||
const u = await T.main.storage.userStorage.GetUser(user.userId)
|
||||
expect(u.balance_sats).to.be.equal(amount)
|
||||
T.d(`user ${user.appUserIdentifier} balance is now ${amount}`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue