Merge branch 'master' into swaps-test
This commit is contained in:
commit
4374d3f443
20 changed files with 283 additions and 34 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# Lightning.Pub
|
# Lightning.Pub
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
[](http://makeapullrequest.com)
|
[](http://makeapullrequest.com)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
#LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
|
#LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
|
||||||
#LND_LOG_DIR=~/.lnd/logs/bitcoin/mainnet/lnd.log
|
#LND_LOG_DIR=~/.lnd/logs/bitcoin/mainnet/lnd.log
|
||||||
#BTC_NETWORK=mainnet
|
#BTC_NETWORK=mainnet
|
||||||
|
# Bypass LND entirely and daisychain off the bootstrap provider (testing only)
|
||||||
|
#USE_ONLY_LIQUIDITY_PROVIDER=false
|
||||||
|
|
||||||
#BOOTSTRAP_PEER
|
#BOOTSTRAP_PEER
|
||||||
# A trusted peer that will hold a node-level account until channel automation becomes affordable
|
# A trusted peer that will hold a node-level account until channel automation becomes affordable
|
||||||
|
|
@ -17,8 +19,7 @@
|
||||||
# To disable this feature entirely overwrite the env with "null"
|
# To disable this feature entirely overwrite the env with "null"
|
||||||
# LIQUIDITY_PROVIDER_PUB=null
|
# LIQUIDITY_PROVIDER_PUB=null
|
||||||
# DISABLE_LIQUIDITY_PROVIDER=false
|
# DISABLE_LIQUIDITY_PROVIDER=false
|
||||||
# USE_ONLY_LIQUIDITY_PROVIDER=false
|
# PROVIDER_RELAY_URL=
|
||||||
PROVIDER_RELAY_URL=
|
|
||||||
|
|
||||||
#SWAPS
|
#SWAPS
|
||||||
# BOLTZ_HTTP_URL=
|
# BOLTZ_HTTP_URL=
|
||||||
|
|
|
||||||
24
package-lock.json
generated
24
package-lock.json
generated
|
|
@ -37,6 +37,7 @@
|
||||||
"globby": "^13.1.2",
|
"globby": "^13.1.2",
|
||||||
"grpc-tools": "^1.12.4",
|
"grpc-tools": "^1.12.4",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"light-bolt11-decoder": "^3.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"nostr-tools": "^2.13.0",
|
"nostr-tools": "^2.13.0",
|
||||||
"pg": "^8.4.0",
|
"pg": "^8.4.0",
|
||||||
|
|
@ -4682,6 +4683,27 @@
|
||||||
"safe-buffer": "^5.1.1"
|
"safe-buffer": "^5.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/light-bolt11-decoder": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@scure/base": "1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/light-bolt11-decoder/node_modules/@scure/base": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
|
@ -8193,4 +8215,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
"globby": "^13.1.2",
|
"globby": "^13.1.2",
|
||||||
"grpc-tools": "^1.12.4",
|
"grpc-tools": "^1.12.4",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"light-bolt11-decoder": "^3.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"nostr-tools": "^2.13.0",
|
"nostr-tools": "^2.13.0",
|
||||||
"pg": "^8.4.0",
|
"pg": "^8.4.0",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ log() {
|
||||||
echo "$message" | sed 's/\\e\[[0-9;]*m//g' >> "$TMP_LOG_FILE"
|
echo "$message" | sed 's/\\e\[[0-9;]*m//g' >> "$TMP_LOG_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
SCRIPT_VERSION="0.3.0"
|
SCRIPT_VERSION="0.3.1"
|
||||||
REPO="shocknet/Lightning.Pub"
|
REPO="shocknet/Lightning.Pub"
|
||||||
BRANCH="master"
|
BRANCH="master"
|
||||||
|
|
||||||
|
|
@ -181,14 +181,14 @@ detect_os_arch
|
||||||
|
|
||||||
# Define installation paths based on user
|
# Define installation paths based on user
|
||||||
if [ "$(id -u)" -eq 0 ]; then
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
IS_ROOT=true
|
export IS_ROOT=true
|
||||||
# For root, install under /opt for system-wide access
|
# For root, install under /opt for system-wide access
|
||||||
export INSTALL_DIR="/opt/lightning_pub"
|
export INSTALL_DIR="/opt/lightning_pub"
|
||||||
export UNIT_DIR="/etc/systemd/system"
|
export UNIT_DIR="/etc/systemd/system"
|
||||||
export SYSTEMCTL_CMD="systemctl"
|
export SYSTEMCTL_CMD="systemctl"
|
||||||
log "Running as root: App will be installed in $INSTALL_DIR"
|
log "Running as root: App will be installed in $INSTALL_DIR"
|
||||||
else
|
else
|
||||||
IS_ROOT=false
|
export IS_ROOT=false
|
||||||
export INSTALL_DIR="$HOME/lightning_pub"
|
export INSTALL_DIR="$HOME/lightning_pub"
|
||||||
export UNIT_DIR="$HOME/.config/systemd/user"
|
export UNIT_DIR="$HOME/.config/systemd/user"
|
||||||
export SYSTEMCTL_CMD="systemctl --user"
|
export SYSTEMCTL_CMD="systemctl --user"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@ start_services() {
|
||||||
|
|
||||||
if [ "$OS" = "Linux" ]; then
|
if [ "$OS" = "Linux" ]; then
|
||||||
if [ "$SYSTEMCTL_AVAILABLE" = true ]; then
|
if [ "$SYSTEMCTL_AVAILABLE" = true ]; then
|
||||||
|
# Enable linger for user services so they persist after logout
|
||||||
|
if [ "$IS_ROOT" = false ] && command -v loginctl &> /dev/null; then
|
||||||
|
log "Enabling linger for user services to persist after logout..."
|
||||||
|
loginctl enable-linger || log "Warning: Failed to enable linger. Services may stop after logout."
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$UNIT_DIR"
|
mkdir -p "$UNIT_DIR"
|
||||||
|
|
||||||
# Check and create lnd.service if needed (only if it doesn't exist)
|
# Check and create lnd.service if needed (only if it doesn't exist)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import crypto from 'crypto'
|
||||||
import { credentials, Metadata } from '@grpc/grpc-js'
|
import { credentials, Metadata } from '@grpc/grpc-js'
|
||||||
import { GrpcTransport } from "@protobuf-ts/grpc-transport";
|
import { GrpcTransport } from "@protobuf-ts/grpc-transport";
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import { decode as decodeBolt11 } from 'light-bolt11-decoder'
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import { LightningClient } from '../../../proto/lnd/lightning.client.js'
|
import { LightningClient } from '../../../proto/lnd/lightning.client.js'
|
||||||
import { InvoicesClient } from '../../../proto/lnd/invoices.client.js'
|
import { InvoicesClient } from '../../../proto/lnd/invoices.client.js'
|
||||||
|
|
@ -61,6 +62,24 @@ export default class {
|
||||||
this.newBlockCb = newBlockCb
|
this.newBlockCb = newBlockCb
|
||||||
this.htlcCb = htlcCb
|
this.htlcCb = htlcCb
|
||||||
this.channelEventCb = channelEventCb
|
this.channelEventCb = channelEventCb
|
||||||
|
this.liquidProvider = liquidProvider
|
||||||
|
|
||||||
|
// Skip LND client initialization if using only liquidity provider
|
||||||
|
if (liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
this.log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping LND client initialization")
|
||||||
|
// Create minimal dummy clients - they won't be used but prevent null reference errors
|
||||||
|
// Use insecure credentials directly (can't combine them)
|
||||||
|
const { lndAddr } = this.getSettings().lndNodeSettings
|
||||||
|
const insecureCreds = credentials.createInsecure()
|
||||||
|
const dummyTransport = new GrpcTransport({ host: lndAddr || '127.0.0.1:10009', channelCredentials: insecureCreds })
|
||||||
|
this.lightning = new LightningClient(dummyTransport)
|
||||||
|
this.invoices = new InvoicesClient(dummyTransport)
|
||||||
|
this.router = new RouterClient(dummyTransport)
|
||||||
|
this.chainNotifier = new ChainNotifierClient(dummyTransport)
|
||||||
|
this.walletKit = new WalletKitClient(dummyTransport)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { lndAddr, lndCertPath, lndMacaroonPath } = this.getSettings().lndNodeSettings
|
const { lndAddr, lndCertPath, lndMacaroonPath } = this.getSettings().lndNodeSettings
|
||||||
const lndCert = fs.readFileSync(lndCertPath);
|
const lndCert = fs.readFileSync(lndCertPath);
|
||||||
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
|
const macaroon = fs.readFileSync(lndMacaroonPath).toString('hex');
|
||||||
|
|
@ -86,7 +105,6 @@ export default class {
|
||||||
this.router = new RouterClient(transport)
|
this.router = new RouterClient(transport)
|
||||||
this.chainNotifier = new ChainNotifierClient(transport)
|
this.chainNotifier = new ChainNotifierClient(transport)
|
||||||
this.walletKit = new WalletKitClient(transport)
|
this.walletKit = new WalletKitClient(transport)
|
||||||
this.liquidProvider = liquidProvider
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LockOutgoingOperations(): void {
|
LockOutgoingOperations(): void {
|
||||||
|
|
@ -105,6 +123,12 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async Warmup() {
|
async Warmup() {
|
||||||
|
// Skip LND warmup if using only liquidity provider
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
this.log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping LND warmup")
|
||||||
|
this.ready = true
|
||||||
|
return
|
||||||
|
}
|
||||||
// console.log("Warming up LND")
|
// console.log("Warming up LND")
|
||||||
this.SubscribeAddressPaid()
|
this.SubscribeAddressPaid()
|
||||||
this.SubscribeInvoicePaid()
|
this.SubscribeInvoicePaid()
|
||||||
|
|
@ -130,11 +154,26 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetInfo(): Promise<NodeInfo> {
|
async GetInfo(): Promise<NodeInfo> {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
// Return dummy info when bypass is enabled
|
||||||
|
return {
|
||||||
|
identityPubkey: '',
|
||||||
|
alias: '',
|
||||||
|
syncedToChain: false,
|
||||||
|
syncedToGraph: false,
|
||||||
|
blockHeight: 0,
|
||||||
|
blockHash: '',
|
||||||
|
uris: []
|
||||||
|
}
|
||||||
|
}
|
||||||
// console.log("Getting info")
|
// console.log("Getting info")
|
||||||
const res = await this.lightning.getInfo({}, DeadLineMetadata())
|
const res = await this.lightning.getInfo({}, DeadLineMetadata())
|
||||||
return res.response
|
return res.response
|
||||||
}
|
}
|
||||||
async ListPendingChannels(): Promise<PendingChannelsResponse> {
|
async ListPendingChannels(): Promise<PendingChannelsResponse> {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return { pendingOpenChannels: [], pendingClosingChannels: [], pendingForceClosingChannels: [], waitingCloseChannels: [], totalLimboBalance: 0n }
|
||||||
|
}
|
||||||
// console.log("Listing pending channels")
|
// console.log("Listing pending channels")
|
||||||
const res = await this.lightning.pendingChannels({ includeRawTx: false }, DeadLineMetadata())
|
const res = await this.lightning.pendingChannels({ includeRawTx: false }, DeadLineMetadata())
|
||||||
return res.response
|
return res.response
|
||||||
|
|
@ -160,6 +199,10 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async Health(): Promise<void> {
|
async Health(): Promise<void> {
|
||||||
|
// Skip health check when bypass is enabled
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// console.log("Checking health")
|
// console.log("Checking health")
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
throw new Error("not ready")
|
throw new Error("not ready")
|
||||||
|
|
@ -289,6 +332,10 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async NewAddress(addressType: Types.AddressType, { useProvider, from }: TxActionOptions): Promise<NewAddressResponse> {
|
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) {
|
||||||
|
throw new Error("Address generation not supported when USE_ONLY_LIQUIDITY_PROVIDER is enabled")
|
||||||
|
}
|
||||||
// console.log("Creating new address")
|
// console.log("Creating new address")
|
||||||
let lndAddressType: AddressType
|
let lndAddressType: AddressType
|
||||||
switch (addressType) {
|
switch (addressType) {
|
||||||
|
|
@ -320,7 +367,9 @@ export default class {
|
||||||
|
|
||||||
async NewInvoice(value: number, memo: string, expiry: number, { useProvider, from }: TxActionOptions, blind = false): Promise<Invoice> {
|
async NewInvoice(value: number, memo: string, expiry: number, { useProvider, from }: TxActionOptions, blind = false): Promise<Invoice> {
|
||||||
// console.log("Creating new invoice")
|
// console.log("Creating new invoice")
|
||||||
if (useProvider) {
|
// Force use of provider when bypass is enabled
|
||||||
|
const mustUseProvider = this.liquidProvider.getSettings().useOnlyLiquidityProvider || useProvider
|
||||||
|
if (mustUseProvider) {
|
||||||
console.log("using provider")
|
console.log("using provider")
|
||||||
const invoice = await this.liquidProvider.AddInvoice(value, memo, from, expiry)
|
const invoice = await this.liquidProvider.AddInvoice(value, memo, from, expiry)
|
||||||
const providerDst = this.liquidProvider.GetProviderDestination()
|
const providerDst = this.liquidProvider.GetProviderDestination()
|
||||||
|
|
@ -337,12 +386,40 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async DecodeInvoice(paymentRequest: string): Promise<DecodedInvoice> {
|
async DecodeInvoice(paymentRequest: string): Promise<DecodedInvoice> {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
// Use light-bolt11-decoder when LND is bypassed
|
||||||
|
try {
|
||||||
|
const decoded = decodeBolt11(paymentRequest)
|
||||||
|
let numSatoshis = 0
|
||||||
|
let paymentHash = ''
|
||||||
|
|
||||||
|
for (const section of decoded.sections) {
|
||||||
|
if (section.name === 'amount') {
|
||||||
|
// Amount is in millisatoshis
|
||||||
|
numSatoshis = Math.floor(Number(section.value) / 1000)
|
||||||
|
} else if (section.name === 'payment_hash') {
|
||||||
|
paymentHash = section.value as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paymentHash) {
|
||||||
|
throw new Error("Payment hash not found in invoice")
|
||||||
|
}
|
||||||
|
|
||||||
|
return { numSatoshis, paymentHash }
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new Error(`Failed to decode invoice: ${err.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
// console.log("Decoding invoice")
|
// console.log("Decoding invoice")
|
||||||
const res = await this.lightning.decodePayReq({ payReq: paymentRequest }, DeadLineMetadata())
|
const res = await this.lightning.decodePayReq({ payReq: paymentRequest }, DeadLineMetadata())
|
||||||
return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash }
|
return { numSatoshis: Number(res.response.numSatoshis), paymentHash: res.response.paymentHash }
|
||||||
}
|
}
|
||||||
|
|
||||||
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
async ChannelBalance(): Promise<{ local: number, remote: number }> {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return { local: 0, remote: 0 }
|
||||||
|
}
|
||||||
// console.log("Getting channel balance")
|
// console.log("Getting channel balance")
|
||||||
const res = await this.lightning.channelBalance({})
|
const res = await this.lightning.channelBalance({})
|
||||||
const r = res.response
|
const r = res.response
|
||||||
|
|
@ -354,7 +431,9 @@ export default class {
|
||||||
this.log("outgoing ops locked, rejecting payment request")
|
this.log("outgoing ops locked, rejecting payment request")
|
||||||
throw new Error("lnd node is currently out of sync")
|
throw new Error("lnd node is currently out of sync")
|
||||||
}
|
}
|
||||||
if (useProvider) {
|
// 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 res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from)
|
||||||
const providerDst = this.liquidProvider.GetProviderDestination()
|
const providerDst = this.liquidProvider.GetProviderDestination()
|
||||||
return { feeSat: res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst }
|
return { feeSat: res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst }
|
||||||
|
|
@ -409,6 +488,10 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async PayAddress(address: string, amount: number, satPerVByte: number, label = "", { useProvider, from }: TxActionOptions): Promise<SendCoinsResponse> {
|
async PayAddress(address: string, amount: number, satPerVByte: number, label = "", { useProvider, from }: TxActionOptions): Promise<SendCoinsResponse> {
|
||||||
|
// Address payments not supported when bypass is enabled
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
throw new Error("Address payments not supported when USE_ONLY_LIQUIDITY_PROVIDER is enabled")
|
||||||
|
}
|
||||||
// console.log("Paying address")
|
// console.log("Paying address")
|
||||||
if (this.outgoingOpsLocked) {
|
if (this.outgoingOpsLocked) {
|
||||||
this.log("outgoing ops locked, rejecting payment request")
|
this.log("outgoing ops locked, rejecting payment request")
|
||||||
|
|
@ -486,6 +569,9 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetBalance(): Promise<BalanceInfo> { // TODO: remove this
|
async GetBalance(): Promise<BalanceInfo> { // TODO: remove this
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return { confirmedBalance: 0, unconfirmedBalance: 0, totalBalance: 0, channelsBalance: [] }
|
||||||
|
}
|
||||||
// console.log("Getting balance")
|
// console.log("Getting balance")
|
||||||
const wRes = await this.lightning.walletBalance({ account: "", minConfs: 1 }, DeadLineMetadata())
|
const wRes = await this.lightning.walletBalance({ account: "", minConfs: 1 }, DeadLineMetadata())
|
||||||
const { confirmedBalance, unconfirmedBalance, totalBalance } = wRes.response
|
const { confirmedBalance, unconfirmedBalance, totalBalance } = wRes.response
|
||||||
|
|
@ -502,17 +588,26 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetForwardingHistory(indexOffset: number, startTime = 0, endTime = 0): Promise<ForwardingHistoryResponse> {
|
async GetForwardingHistory(indexOffset: number, startTime = 0, endTime = 0): Promise<ForwardingHistoryResponse> {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return { forwardingEvents: [], lastOffsetIndex: indexOffset }
|
||||||
|
}
|
||||||
// console.log("Getting forwarding history")
|
// console.log("Getting forwarding history")
|
||||||
const { response } = await this.lightning.forwardingHistory({ indexOffset, numMaxEvents: 0, startTime: BigInt(startTime), endTime: BigInt(endTime), peerAliasLookup: false }, DeadLineMetadata())
|
const { response } = await this.lightning.forwardingHistory({ indexOffset, numMaxEvents: 0, startTime: BigInt(startTime), endTime: BigInt(endTime), peerAliasLookup: false }, DeadLineMetadata())
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetAllPaidInvoices(max: number) {
|
async GetAllPaidInvoices(max: number) {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return { invoices: [] }
|
||||||
|
}
|
||||||
// console.log("Getting all paid invoices")
|
// console.log("Getting all paid invoices")
|
||||||
const res = await this.lightning.listInvoices({ indexOffset: 0n, numMaxInvoices: BigInt(max), pendingOnly: false, reversed: true, creationDateEnd: 0n, creationDateStart: 0n }, DeadLineMetadata())
|
const res = await this.lightning.listInvoices({ indexOffset: 0n, numMaxInvoices: BigInt(max), pendingOnly: false, reversed: true, creationDateEnd: 0n, creationDateStart: 0n }, DeadLineMetadata())
|
||||||
return res.response
|
return res.response
|
||||||
}
|
}
|
||||||
async GetAllPayments(max: number) {
|
async GetAllPayments(max: number) {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return { payments: [] }
|
||||||
|
}
|
||||||
// console.log("Getting all payments")
|
// console.log("Getting all payments")
|
||||||
const res = await this.lightning.listPayments({ countTotalPayments: false, includeIncomplete: false, indexOffset: 0n, maxPayments: BigInt(max), reversed: true, creationDateEnd: 0n, creationDateStart: 0n })
|
const res = await this.lightning.listPayments({ countTotalPayments: false, includeIncomplete: false, indexOffset: 0n, maxPayments: BigInt(max), reversed: true, creationDateEnd: 0n, creationDateStart: 0n })
|
||||||
return res.response
|
return res.response
|
||||||
|
|
@ -531,6 +626,9 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetLatestPaymentIndex(from = 0) {
|
async GetLatestPaymentIndex(from = 0) {
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return from
|
||||||
|
}
|
||||||
// console.log("Getting latest payment index")
|
// console.log("Getting latest payment index")
|
||||||
let indexOffset = BigInt(from)
|
let indexOffset = BigInt(from)
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ export default class {
|
||||||
return invoice
|
return invoice
|
||||||
}
|
}
|
||||||
|
|
||||||
async AddAppUserInvoice(appId: string, req: Types.AddAppUserInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
async AddAppUserInvoice(appId: string, req: Types.AddAppUserInvoiceRequest, clinkRequester?: { pub: string, eventId: string }): Promise<Types.NewInvoiceResponse> {
|
||||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||||
const log = getLogger({ appName: app.name })
|
const log = getLogger({ appName: app.name })
|
||||||
const receiver = await this.storage.applicationStorage.GetApplicationUser(app, req.receiver_identifier)
|
const receiver = await this.storage.applicationStorage.GetApplicationUser(app, req.receiver_identifier)
|
||||||
|
|
@ -200,7 +200,9 @@ export default class {
|
||||||
callbackUrl: cbUrl, expiry: expiry, expectedPayer: payer.user, linkedApplication: app, zapInfo,
|
callbackUrl: cbUrl, expiry: expiry, expectedPayer: payer.user, linkedApplication: app, zapInfo,
|
||||||
offerId: req.offer_string, payerData: req.payer_data?.data, rejectUnauthorized: req.rejectUnauthorized,
|
offerId: req.offer_string, payerData: req.payer_data?.data, rejectUnauthorized: req.rejectUnauthorized,
|
||||||
token: req.token,
|
token: req.token,
|
||||||
blind: req.invoice_req.blind
|
blind: req.invoice_req.blind,
|
||||||
|
clinkRequesterPub: clinkRequester?.pub,
|
||||||
|
clinkRequesterEventId: clinkRequester?.eventId
|
||||||
}
|
}
|
||||||
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
|
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,10 @@ import { AddressPaidCb, ChannelEventCb, HtlcCb, InvoicePaidCb, NewBlockCb } from
|
||||||
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
import { ERROR, getLogger, PubLogger } from "../helpers/logger.js"
|
||||||
import AppUserManager from "./appUserManager.js"
|
import AppUserManager from "./appUserManager.js"
|
||||||
import { Application } from '../storage/entity/Application.js'
|
import { Application } from '../storage/entity/Application.js'
|
||||||
import { UserReceivingInvoice, ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
|
||||||
import { UnsignedEvent } from 'nostr-tools'
|
import { UnsignedEvent } from 'nostr-tools'
|
||||||
import { NostrSend } from '../nostr/nostrPool.js'
|
import { NostrSend } from '../nostr/nostrPool.js'
|
||||||
import MetricsManager from '../metrics/index.js'
|
import MetricsManager from '../metrics/index.js'
|
||||||
import { LoggedEvent } from '../storage/eventsLog.js'
|
|
||||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||||
import { LiquidityManager } from "./liquidityManager.js"
|
import { LiquidityManager } from "./liquidityManager.js"
|
||||||
import { Utils } from "../helpers/utilsWrapper.js"
|
import { Utils } from "../helpers/utilsWrapper.js"
|
||||||
|
|
@ -206,6 +205,11 @@ export default class {
|
||||||
|
|
||||||
addressPaidCb: AddressPaidCb = (txOutput, address, amount, used) => {
|
addressPaidCb: AddressPaidCb = (txOutput, address, amount, used) => {
|
||||||
return this.storage.StartTransaction(async tx => {
|
return this.storage.StartTransaction(async tx => {
|
||||||
|
// On-chain payments not supported when bypass is enabled
|
||||||
|
if (this.liquidityProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
getLogger({})("addressPaidCb called but USE_ONLY_LIQUIDITY_PROVIDER is enabled, ignoring")
|
||||||
|
return
|
||||||
|
}
|
||||||
const { blockHeight } = await this.lnd.GetInfo()
|
const { blockHeight } = await this.lnd.GetInfo()
|
||||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
||||||
if (!userAddress) {
|
if (!userAddress) {
|
||||||
|
|
@ -284,6 +288,14 @@ export default class {
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
log(ERROR, "cannot create zap receipt", err.message || "")
|
log(ERROR, "cannot create zap receipt", err.message || "")
|
||||||
}
|
}
|
||||||
|
// Send CLINK receipt if this invoice was from a noffer request
|
||||||
|
try {
|
||||||
|
if (userInvoice.clink_requester_pub && userInvoice.clink_requester_event_id) {
|
||||||
|
await this.createClinkReceipt(log, userInvoice)
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
log(ERROR, "cannot create clink receipt", err.message || "")
|
||||||
|
}
|
||||||
this.liquidityManager.afterInInvoicePaid()
|
this.liquidityManager.afterInInvoicePaid()
|
||||||
this.utils.stateBundler.AddTxPoint('invoiceWasPaid', amount, { used, from: 'system', timeDiscount: true }, userInvoice.linkedApplication.app_id)
|
this.utils.stateBundler.AddTxPoint('invoiceWasPaid', amount, { used, from: 'system', timeDiscount: true }, userInvoice.linkedApplication.app_id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|
@ -426,6 +438,33 @@ export default class {
|
||||||
this.utils.nostrSender.Send({ 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) {
|
||||||
|
if (!invoice.clink_requester_pub || !invoice.clink_requester_event_id || !invoice.linkedApplication) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log("📤 [CLINK RECEIPT] Sending payment receipt", {
|
||||||
|
toPub: invoice.clink_requester_pub,
|
||||||
|
eventId: invoice.clink_requester_event_id
|
||||||
|
})
|
||||||
|
// Receipt payload - payer's wallet already has the preimage
|
||||||
|
const content = JSON.stringify({ res: 'ok' })
|
||||||
|
const event: UnsignedEvent = {
|
||||||
|
content,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
kind: 21001,
|
||||||
|
pubkey: "",
|
||||||
|
tags: [
|
||||||
|
["p", invoice.clink_requester_pub],
|
||||||
|
["e", invoice.clink_requester_event_id],
|
||||||
|
["clink_version", "1"]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
this.nostrSend(
|
||||||
|
{ type: 'app', appId: invoice.linkedApplication.app_id },
|
||||||
|
{ type: 'event', event, encrypt: { toPub: invoice.clink_requester_pub } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async ResetNostr() {
|
async ResetNostr() {
|
||||||
const apps = await this.storage.applicationStorage.GetApplications()
|
const apps = await this.storage.applicationStorage.GetApplications()
|
||||||
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM
|
||||||
const mainHandler = new Main(settingsManager, storageManager, adminManager, utils, unlocker)
|
const mainHandler = new Main(settingsManager, storageManager, adminManager, utils, unlocker)
|
||||||
adminManager.setLND(mainHandler.lnd)
|
adminManager.setLND(mainHandler.lnd)
|
||||||
await mainHandler.lnd.Warmup()
|
await mainHandler.lnd.Warmup()
|
||||||
if (!settingsManager.getSettings().serviceSettings.skipSanityCheck) {
|
if (!settingsManager.getSettings().serviceSettings.skipSanityCheck && !settingsManager.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
||||||
await sanityChecker.VerifyEventsLog()
|
await sanityChecker.VerifyEventsLog()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,10 @@ export class LiquidityManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
afterInInvoicePaid = async () => {
|
afterInInvoicePaid = async () => {
|
||||||
|
// Skip channel ordering if using only liquidity provider
|
||||||
|
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.orderChannelIfNeeded()
|
await this.orderChannelIfNeeded()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
@ -102,6 +106,10 @@ export class LiquidityManager {
|
||||||
afterOutInvoicePaid = async () => { }
|
afterOutInvoicePaid = async () => { }
|
||||||
|
|
||||||
shouldDrainProvider = async () => {
|
shouldDrainProvider = async () => {
|
||||||
|
// Skip draining when bypass is enabled
|
||||||
|
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const maxW = await this.liquidityProvider.GetMaxWithdrawable()
|
const maxW = await this.liquidityProvider.GetMaxWithdrawable()
|
||||||
const { remote } = await this.lnd.ChannelBalance()
|
const { remote } = await this.lnd.ChannelBalance()
|
||||||
const drainable = Math.min(maxW, remote)
|
const drainable = Math.min(maxW, remote)
|
||||||
|
|
@ -159,6 +167,10 @@ export class LiquidityManager {
|
||||||
|
|
||||||
|
|
||||||
shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => {
|
shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => {
|
||||||
|
// Skip channel operations if using only liquidity provider
|
||||||
|
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
|
return { shouldOpen: false }
|
||||||
|
}
|
||||||
const threshold = this.settings.getSettings().lspSettings.channelThreshold
|
const threshold = this.settings.getSettings().lspSettings.channelThreshold
|
||||||
if (threshold === 0) {
|
if (threshold === 0) {
|
||||||
return { shouldOpen: false }
|
return { shouldOpen: false }
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,13 @@ export class OfferManager {
|
||||||
payerData: offerReq.payer_data
|
payerData: offerReq.payer_data
|
||||||
})
|
})
|
||||||
|
|
||||||
const offerInvoice = await this.getNofferInvoice(offerReq, event.appId)
|
// Store requester info for sending receipt when invoice is paid
|
||||||
|
const clinkRequester = {
|
||||||
|
pub: event.pub,
|
||||||
|
eventId: event.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const offerInvoice = await this.getNofferInvoice(offerReq, event.appId, clinkRequester)
|
||||||
|
|
||||||
if (!offerInvoice.success) {
|
if (!offerInvoice.success) {
|
||||||
const code = offerInvoice.code
|
const code = offerInvoice.code
|
||||||
|
|
@ -185,7 +191,7 @@ export class OfferManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
async HandleDefaultUserOffer(offerReq: NofferData, appId: string, remote: number, { memo, expiry }: { memo?: string, expiry?: number }): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
|
async HandleDefaultUserOffer(offerReq: NofferData, appId: string, remote: number, { memo, expiry }: { memo?: string, expiry?: number }, clinkRequester?: { pub: string, eventId: string }): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
|
||||||
const { amount_sats: amount, offer } = offerReq
|
const { amount_sats: amount, offer } = offerReq
|
||||||
if (!amount || isNaN(amount) || amount < 10 || amount > remote) {
|
if (!amount || isNaN(amount) || amount < 10 || amount > remote) {
|
||||||
return { success: false, code: 5, max: remote }
|
return { success: false, code: 5, max: remote }
|
||||||
|
|
@ -194,17 +200,17 @@ export class OfferManager {
|
||||||
http_callback_url: "", payer_identifier: offer, receiver_identifier: offer,
|
http_callback_url: "", payer_identifier: offer, receiver_identifier: offer,
|
||||||
invoice_req: { amountSats: amount, memo: memo || "Default CLINK Offer", zap: offerReq.zap, expiry },
|
invoice_req: { amountSats: amount, memo: memo || "Default CLINK Offer", zap: offerReq.zap, expiry },
|
||||||
offer_string: 'offer'
|
offer_string: 'offer'
|
||||||
})
|
}, clinkRequester)
|
||||||
return { success: true, invoice: res.invoice }
|
return { success: true, invoice: res.invoice }
|
||||||
}
|
}
|
||||||
|
|
||||||
async HandleUserOffer(offerReq: NofferData, appId: string, remote: number): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
|
async HandleUserOffer(offerReq: NofferData, appId: string, remote: number, clinkRequester?: { pub: string, eventId: string }): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
|
||||||
const { amount_sats: amount, offer } = offerReq
|
const { amount_sats: amount, offer } = offerReq
|
||||||
const userOffer = await this.storage.offerStorage.GetOffer(offer)
|
const userOffer = await this.storage.offerStorage.GetOffer(offer)
|
||||||
const expiry = offerReq.expires_in_seconds ? offerReq.expires_in_seconds : undefined
|
const expiry = offerReq.expires_in_seconds ? offerReq.expires_in_seconds : undefined
|
||||||
|
|
||||||
if (!userOffer) {
|
if (!userOffer) {
|
||||||
return this.HandleDefaultUserOffer(offerReq, appId, remote, { memo: offerReq.description, expiry })
|
return this.HandleDefaultUserOffer(offerReq, appId, remote, { memo: offerReq.description, expiry }, clinkRequester)
|
||||||
}
|
}
|
||||||
if (userOffer.app_user_id === userOffer.offer_id) {
|
if (userOffer.app_user_id === userOffer.offer_id) {
|
||||||
if (userOffer.price_sats !== 0 || userOffer.payer_data) {
|
if (userOffer.price_sats !== 0 || userOffer.payer_data) {
|
||||||
|
|
@ -237,20 +243,28 @@ export class OfferManager {
|
||||||
offer_string: offer,
|
offer_string: offer,
|
||||||
rejectUnauthorized: userOffer.rejectUnauthorized,
|
rejectUnauthorized: userOffer.rejectUnauthorized,
|
||||||
token: userOffer.bearer_token
|
token: userOffer.bearer_token
|
||||||
})
|
}, clinkRequester)
|
||||||
return { success: true, invoice: res.invoice }
|
return { success: true, invoice: res.invoice }
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNofferInvoice(offerReq: NofferData, appId: string): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
|
async getNofferInvoice(offerReq: NofferData, appId: string, clinkRequester?: { pub: string, eventId: string }): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
|
||||||
try {
|
try {
|
||||||
const { remote } = await this.lnd.ChannelBalance()
|
// When bypass is enabled, use provider balance instead of LND channel balance
|
||||||
let maxSendable = remote
|
let maxSendable = 0
|
||||||
if (remote === 0 && (await this.liquidityManager.liquidityProvider.IsReady())) {
|
if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
maxSendable = 10_000_000
|
if (await this.liquidityManager.liquidityProvider.IsReady()) {
|
||||||
|
maxSendable = 10_000_000
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { remote } = await this.lnd.ChannelBalance()
|
||||||
|
maxSendable = remote
|
||||||
|
if (remote === 0 && (await this.liquidityManager.liquidityProvider.IsReady())) {
|
||||||
|
maxSendable = 10_000_000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const split = offerReq.offer.split(':')
|
const split = offerReq.offer.split(':')
|
||||||
if (split.length === 1) {
|
if (split.length === 1) {
|
||||||
return this.HandleUserOffer(offerReq, appId, maxSendable)
|
return this.HandleUserOffer(offerReq, appId, maxSendable, clinkRequester)
|
||||||
} else if (split[0] === 'p') {
|
} else if (split[0] === 'p') {
|
||||||
const product = await this.productManager.NewProductInvoice(split[1])
|
const product = await this.productManager.NewProductInvoice(split[1])
|
||||||
return { success: true, invoice: product.invoice }
|
return { success: true, invoice: product.invoice }
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,11 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPendingLndPayment = async (log: PubLogger, p: UserInvoicePayment) => {
|
checkPendingLndPayment = async (log: PubLogger, p: UserInvoicePayment) => {
|
||||||
|
// Skip LND payment checks when bypass is enabled
|
||||||
|
if (this.liquidityManager.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
|
log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping LND payment check for", p.serial_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
const decoded = await this.lnd.DecodeInvoice(p.invoice)
|
const decoded = await this.lnd.DecodeInvoice(p.invoice)
|
||||||
const payment = await this.lnd.GetPaymentFromHash(decoded.paymentHash)
|
const payment = await this.lnd.GetPaymentFromHash(decoded.paymentHash)
|
||||||
if (!payment || payment.paymentHash !== decoded.paymentHash) {
|
if (!payment || payment.paymentHash !== decoded.paymentHash) {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,11 @@ export class Unlocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
Unlock = async (): Promise<'created' | 'unlocked' | 'noaction'> => {
|
Unlock = async (): Promise<'created' | 'unlocked' | 'noaction'> => {
|
||||||
|
// Skip LND unlock if using only liquidity provider
|
||||||
|
if (this.settings.getSettings().liquiditySettings.useOnlyLiquidityProvider) {
|
||||||
|
this.log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping LND unlock")
|
||||||
|
return 'noaction'
|
||||||
|
}
|
||||||
const { lndCert, macaroon } = this.getCreds()
|
const { lndCert, macaroon } = this.getCreds()
|
||||||
if (macaroon === "") {
|
if (macaroon === "") {
|
||||||
const { ln, pub } = await this.InitFlow(lndCert)
|
const { ln, pub } = await this.InitFlow(lndCert)
|
||||||
|
|
@ -211,11 +216,10 @@ export class Unlocker {
|
||||||
throw new Error("node pub not found")
|
throw new Error("node pub not found")
|
||||||
}
|
}
|
||||||
const encrypted = await this.storage.liquidityStorage.GetNoodeSeed(this.nodePub)
|
const encrypted = await this.storage.liquidityStorage.GetNoodeSeed(this.nodePub)
|
||||||
if (!encrypted || !encrypted.seed) {
|
if (!encrypted) {
|
||||||
throw new Error("seed not found")
|
throw new Error("seed not found")
|
||||||
}
|
}
|
||||||
|
const decrypted = this.DecryptWalletSeed(JSON.parse(encrypted))
|
||||||
const decrypted = this.DecryptWalletSeed(JSON.parse(encrypted.seed))
|
|
||||||
return { seed: decrypted }
|
return { seed: decrypted }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@ export class Watchdog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StartWatching = async () => {
|
StartWatching = async () => {
|
||||||
|
// Skip watchdog if using only liquidity provider
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
this.log("USE_ONLY_LIQUIDITY_PROVIDER enabled, skipping watchdog")
|
||||||
|
this.ready = true
|
||||||
|
return
|
||||||
|
}
|
||||||
this.log("Starting watchdog")
|
this.log("Starting watchdog")
|
||||||
this.startedAtUnix = Math.floor(Date.now() / 1000)
|
this.startedAtUnix = Math.floor(Date.now() / 1000)
|
||||||
const info = await this.lnd.GetInfo()
|
const info = await this.lnd.GetInfo()
|
||||||
|
|
@ -205,6 +211,10 @@ export class Watchdog {
|
||||||
}
|
}
|
||||||
|
|
||||||
PaymentRequested = async () => {
|
PaymentRequested = async () => {
|
||||||
|
// Skip watchdog check when bypass is enabled
|
||||||
|
if (this.liquidProvider.getSettings().useOnlyLiquidityProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
throw new Error("Watchdog not ready")
|
throw new Error("Watchdog not ready")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,12 @@ export class UserReceivingInvoice {
|
||||||
})
|
})
|
||||||
liquidityProvider?: string
|
liquidityProvider?: string
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
clink_requester_pub?: string
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
clink_requester_event_id?: string
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
created_at: Date
|
created_at: Date
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ export class LiquidityStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetNoodeSeed(pubkey: string) {
|
async GetNoodeSeed(pubkey: string) {
|
||||||
return this.dbs.FindOne<LndNodeInfo>('LndNodeInfo', { where: { pubkey, seed: Not(IsNull()) } })
|
const node = await this.dbs.FindOne<LndNodeInfo>('LndNodeInfo', { where: { pubkey/* , seed: Not(IsNull()) */ } })
|
||||||
|
return node?.seed
|
||||||
}
|
}
|
||||||
|
|
||||||
async SaveNodeSeed(pubkey: string, seed: string) {
|
async SaveNodeSeed(pubkey: string, seed: string) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class ClinkRequester1765497600000 implements MigrationInterface {
|
||||||
|
name = 'ClinkRequester1765497600000'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// Check if columns already exist (idempotent migration for existing databases)
|
||||||
|
const tableInfo = await queryRunner.query(`PRAGMA table_info("user_receiving_invoice")`);
|
||||||
|
const hasPubColumn = tableInfo.some((col: any) => col.name === 'clink_requester_pub');
|
||||||
|
const hasEventIdColumn = tableInfo.some((col: any) => col.name === 'clink_requester_event_id');
|
||||||
|
|
||||||
|
if (!hasPubColumn) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" ADD COLUMN "clink_requester_pub" varchar(64)`);
|
||||||
|
}
|
||||||
|
if (!hasEventIdColumn) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" ADD COLUMN "clink_requester_event_id" varchar(64)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" DROP COLUMN "clink_requester_pub"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" DROP COLUMN "clink_requester_event_id"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -29,13 +29,14 @@ import { ApplicationAvatarUrl1761000001000 } from './1761000001000-application_a
|
||||||
import { AdminSettings1761683639419 } from './1761683639419-admin_settings.js'
|
import { AdminSettings1761683639419 } from './1761683639419-admin_settings.js'
|
||||||
import { TxSwap1762890527098 } from './1762890527098-tx_swap.js'
|
import { TxSwap1762890527098 } from './1762890527098-tx_swap.js'
|
||||||
import { TxSwapAddress1764779178945 } from './1764779178945-tx_swap_address.js'
|
import { TxSwapAddress1764779178945 } from './1764779178945-tx_swap_address.js'
|
||||||
|
import { ClinkRequester1765497600000 } from './1765497600000-clink_requester.js'
|
||||||
|
|
||||||
|
|
||||||
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
|
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
|
||||||
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
||||||
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513,
|
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513,
|
||||||
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175,
|
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175,
|
||||||
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, TxSwapAddress1764779178945]
|
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, TxSwapAddress1764779178945, ClinkRequester1765497600000]
|
||||||
|
|
||||||
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
|
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> => {
|
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import TransactionsQueue from "./db/transactionsQueue.js";
|
||||||
import { LoggedEvent } from './eventsLog.js';
|
import { LoggedEvent } from './eventsLog.js';
|
||||||
import { StorageInterface } from './db/storageInterface.js';
|
import { StorageInterface } from './db/storageInterface.js';
|
||||||
import { TransactionSwap } from './entity/TransactionSwap.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 }
|
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 const defaultInvoiceExpiry = 60 * 60
|
||||||
export default class {
|
export default class {
|
||||||
dbs: StorageInterface
|
dbs: StorageInterface
|
||||||
|
|
@ -130,7 +130,9 @@ export default class {
|
||||||
offer_id: options.offerId,
|
offer_id: options.offerId,
|
||||||
payer_data: options.payerData,
|
payer_data: options.payerData,
|
||||||
rejectUnauthorized: options.rejectUnauthorized,
|
rejectUnauthorized: options.rejectUnauthorized,
|
||||||
bearer_token: options.token
|
bearer_token: options.token,
|
||||||
|
clink_requester_pub: options.clinkRequesterPub,
|
||||||
|
clink_requester_event_id: options.clinkRequesterEventId
|
||||||
}, txId)
|
}, txId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue