Skip to content

Commit

Permalink
Merge pull request #101 from Impa10r/v1.7.8
Browse files Browse the repository at this point in the history
v1.7.8
  • Loading branch information
Impa10r authored Mar 2, 2025
2 parents 90ff3b5 + d63579c commit 7a3492f
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 179 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Versions

## 1.7.8

- Respect spendable and receivable limits for max swap calculation
- Show peg-in ETA as time and not remaining duration
- Wait for broadcasted CaimJoin tx to appear in local mempool
- Fix channel stats not resetting after a successful swap
- Fix invoiced stats not calculating without circular rebalancing
- AutoFee: confirm 'Update All' button click

## 1.7.7

- Fix Auto Fee not working for channels with no outbound forwards
Expand Down
265 changes: 144 additions & 121 deletions cmd/psweb/handlers.go

Large diffs are not rendered by default.

28 changes: 23 additions & 5 deletions cmd/psweb/ln/claimjoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,19 +484,37 @@ func Broadcast(fromNodeId string, message *Message) bool {
if ClaimJoinHandler == message.Sender {
txId := string(message.Payload)
if MyRole == "joiner" && txId != "" && config.Config.PeginClaimScript != "done" && len(ClaimParties) == 1 {
// verify that my address was funded
var decoded liquid.Transaction
_, err := liquid.GetRawTransaction(txId, &decoded)
if err != nil {
ClaimStatus = "Error decoding posted transaction"
return false
var err error

// wait 60 seconds for the posted tx to appear in our local mempool
timeout := time.After(60 * time.Second)
ticker := time.NewTicker(5 * time.Second) // Check every 5 seconds

for {
select {
case <-timeout:
ClaimStatus = "Transaction did not appear in mempool within 60 seconds"
log.Println("Transaction did not appear in mempool within 60 seconds:", err)
ticker.Stop()
return false
case <-ticker.C:
_, err = liquid.GetRawTransaction(txId, &decoded)
if err == nil {
// Transaction found, proceed
ticker.Stop()
goto FoundTransaction
}
}
}

FoundTransaction:
addressInfo, err := liquid.GetAddressInfo(ClaimParties[0].Address)
if err != nil {
return false
}

// verify that my address was funded
ok := false
for _, output := range decoded.Vout {
if output.ScriptPubKey.Address == addressInfo.Unconfidential {
Expand Down
38 changes: 38 additions & 0 deletions cmd/psweb/ln/cln.go
Original file line number Diff line number Diff line change
Expand Up @@ -1580,3 +1580,41 @@ func SendCustomMessage(peerId string, message *Message) error {

return nil
}

type ListPeerChannelsResponse struct {
Channels []PeerChannel `json:"channels"`
}

type PeerChannel struct {
PeerId string `json:"peer_id"`
PeerConnected bool `json:"peer_connected"`
State string `json:"state"`
ShortChannelId string `json:"short_channel_id,omitempty"`
TotalMsat glightning.Amount `json:"total_msat,omitempty"`
ToUsMsat glightning.Amount `json:"to_us_msat,omitempty"`
ReceivableMsat glightning.Amount `json:"receivable_msat,omitempty"`
SpendableMsat glightning.Amount `json:"spendable_msat,omitempty"`
TheirReserveMsat glightning.Amount `json:"their_reserve_msat,omitempty"`
OurReserveMsat glightning.Amount `json:"our_reserve_msat,omitempty"`
}

// spendable, receivable, mapped by channelId
func FetchChannelLimits(client *glightning.Lightning) (spendable map[uint64]uint64, receivable map[uint64]uint64, err error) {
var response ListPeerChannelsResponse
err = client.Request(&ListPeerChannelsRequest{}, &response)
if err != nil {
return
}

spendable = make(map[uint64]uint64)
receivable = make(map[uint64]uint64)

// Iterate over channels to map channel ids
for _, ch := range response.Channels {
id := ConvertClnToLndChannelId(ch.ShortChannelId)
spendable[id] = ch.SpendableMsat.MSat() / 1000
receivable[id] = ch.ReceivableMsat.MSat() / 1000
}

return
}
2 changes: 2 additions & 0 deletions cmd/psweb/ln/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ type DataPoint struct {
ChanIdOut uint64
AliasIn string
AliasOut string
Inbound bool
Outbound bool
TimeAgo string
TimeUTC string
}
Expand Down
42 changes: 33 additions & 9 deletions cmd/psweb/ln/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1599,23 +1599,23 @@ func GetChannelStats(channelId uint64, timeStamp uint64) *ChannelStats {
e := inv[i]
if uint64(e.AcceptTime) > timeStamp {
// check if it is related to a circular rebalancing
found := false
htcls, ok := rebalanceInHtlcs.Read(channelId)
if ok {
found := false
for _, r := range htcls {
if e.AmtMsat == uint64(r.Route.TotalAmtMsat-r.Route.TotalFeesMsat) {
found = true
break
}
}
if found {
// remove invoice to avoid double counting
inv = append(inv[:i], inv[i+1:]...)
invoiceHtlcs.Write(channelId, inv)
i--
} else {
invoicedMsat += e.AmtMsat
}
}
if found {
// remove invoice to avoid double counting
inv = append(inv[:i], inv[i+1:]...)
invoiceHtlcs.Write(channelId, inv)
i--
} else {
invoicedMsat += e.AmtMsat
}
}
}
Expand Down Expand Up @@ -2340,3 +2340,27 @@ try_to_connect:

return false
}

// spendable, receivable, mapped by channelId
func FetchChannelLimits(client lnrpc.LightningClient) (spendable map[uint64]uint64, receivable map[uint64]uint64, err error) {
res, err := client.ListChannels(context.Background(), &lnrpc.ListChannelsRequest{
ActiveOnly: false,
InactiveOnly: false,
PublicOnly: false,
PrivateOnly: false,
})

if err != nil {
return
}

spendable = make(map[uint64]uint64)
receivable = make(map[uint64]uint64)

for _, ch := range res.Channels {
spendable[ch.ChanId] = min(uint64(ch.GetLocalBalance()-int64(ch.GetLocalConstraints().GetChanReserveSat())), ch.GetLocalConstraints().GetMaxPendingAmtMsat()/1000)
receivable[ch.ChanId] = min(uint64(ch.GetRemoteBalance()-int64(ch.GetRemoteConstraints().GetChanReserveSat())), ch.GetRemoteConstraints().GetMaxPendingAmtMsat()/1000)
}

return
}
22 changes: 13 additions & 9 deletions cmd/psweb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@ import (

const (
// App VERSION tag
VERSION = "v1.7.7"
// Swap Out reserve to deduct from channel local balance
SWAP_OUT_CHANNEL_RESERVE = 10_000
// https://github.com/ElementsProject/peerswap/pull/304#issuecomment-2303931071
SWAP_LBTC_RESERVE = 1_200
VERSION = "v1.7.8"
// Unusable BTC balance
ANCHOR_RESERVE = 25_000
// assume creatediscountct=1 for mainnet in elements.conf
ELEMENTS_DISCOUNTED_VSIZE_VERSION = 230203
// opening tx sizes for fee estimates
OPENING_TX_SIZE_BTC = 350
OPENING_TX_SIZE_LBTC = 3000
OPENING_TX_SIZE_LBTC_DISCOUNTED = 750
// https://github.com/ElementsProject/peerswap/pull/304#issuecomment-2303931071
SWAP_LBTC_RESERVE = 1_200
)

type SwapParams struct {
Expand Down Expand Up @@ -495,7 +497,7 @@ func convertPeersToHTMLTable(

for _, swap := range swaps {
ts, ok := swapTimestamps.Read(swap.LndChanId)
if simplifySwapState(swap.State) == "success" && ok && ts < swap.CreatedAt {
if simplifySwapState(swap.State) == "success" && (!ok || ts < swap.CreatedAt) {
swapTimestamps.Write(swap.LndChanId, swap.CreatedAt)
}
}
Expand Down Expand Up @@ -707,7 +709,7 @@ func convertPeersToHTMLTable(
bal := "<100k"
if btcBalance >= 100_000 {
flooredBalance = toMil(btcBalance)
bal = formatWithThousandSeparators(btcBalance)
bal = "≥" + formatWithThousandSeparators(btcBalance)
}
peerTable += "<span title=\"Peer's BTC balance: " + bal + " sats\nLast update: " + tm + "\">" + flooredBalance + "</span>"
}
Expand All @@ -723,7 +725,7 @@ func convertPeersToHTMLTable(
bal := "<100k"
if lbtcBalance >= 100_000 {
flooredBalance = toMil(lbtcBalance)
bal = formatWithThousandSeparators(lbtcBalance)
bal = "≥" + formatWithThousandSeparators(lbtcBalance)
}
peerTable += "<span title=\"Peer's L-BTC balance: " + bal + " sats\nLast update: " + tm + "\">" + flooredBalance + "</span>"
}
Expand Down Expand Up @@ -1808,12 +1810,14 @@ func advertiseBalances() {

cutOff := time.Now().AddDate(0, 0, -1).Unix() - 120

_, receivable, err := ln.FetchChannelLimits(cl)

for _, peer := range res3.GetPeers() {
// find the largest remote balance
maxBalance := uint64(0)
if ln.AdvertiseBitcoinBalance || ln.AdvertiseLiquidBalance {
for _, ch := range peer.Channels {
maxBalance = max(maxBalance, ch.RemoteBalance-SWAP_OUT_CHANNEL_RESERVE)
maxBalance = max(maxBalance, receivable[ch.ChannelId])
}
}

Expand Down
12 changes: 6 additions & 6 deletions cmd/psweb/telegram.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,23 @@ func telegramStart() {
if ln.ClaimBlockHeight == 0 {
duration = time.Duration(10*(int32(peginBlocks)-confs)) * time.Minute
}
formattedDuration := time.Time{}.Add(duration).Format("15h 04m")
eta := time.Now().Add(duration).Format("3:04 PM")
if duration < 0 {
formattedDuration = "Past due"
eta = "Past due"
}
t = "🧬 " + ln.ClaimStatus
if ln.MyRole == "none" && ln.ClaimJoinHandler != "" {
t += ". Time left to apply: " + formattedDuration
t += ". Time limit to apply: " + eta
} else if confs > 0 {
t += ". Claim ETA: " + formattedDuration
t += ". ETA: " + eta
}
} else {
// solo peg-in
duration := time.Duration(10*(int32(peginBlocks)-confs)) * time.Minute
formattedDuration := time.Time{}.Add(duration).Format("15h 04m")
eta := time.Now().Add(duration).Format("3:04 PM")
t = "⏰ Amount: " + formatWithThousandSeparators(uint64(config.Config.PeginAmount)) + " sats, Confs: " + strconv.Itoa(int(confs))
if config.Config.PeginClaimScript != "" {
t += "/102, Time left: " + formattedDuration
t += "/102, ETA: " + eta
}
t += ". TxId: `" + config.Config.PeginTxId + "`"
}
Expand Down
23 changes: 19 additions & 4 deletions cmd/psweb/templates/af.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
style="color:{{.RedColor}}"
{{end}}>{{.LocalPct}}% local</span>, <span style="border-bottom: 2px dashed grey;">Current fee rate: {{fs .FeeRate}}</span>{{if .HasInboundFees}}, Inbound rate: {{.InboundRate}}{{end}}</p>
{{end}}
<form id="myForm" autocomplete="off" action="/submit" method="post" onsubmit="return confirmSubmit()">
<form id="myForm" autocomplete="off" action="/submit" method="post" onsubmit="return confirmSubmit(event)">
<input autocomplete="false" name="hidden" type="text" style="display:none;">
<table style="width:100%; table-layout:fixed; margin-bottom: 0.5em">
<tr>
Expand Down Expand Up @@ -275,7 +275,7 @@
<th style="width: 13ch;">Time</th>
<th>In</th>
<th>Out</th>
<th style="width: 5ch; text-align: right;">Amt</th>
<th style="width: 7ch; text-align: right;">Amount</th>
<th style="width: 7ch; text-align: right;">PPM</th>
</tr>
</thead>
Expand All @@ -285,7 +285,13 @@
<td title="{{.TimeUTC}}" class="truncate">{{.TimeAgo}}</td>
<td title="Channel Id: {{.ChanIdIn}}" class="truncate"><a href="/af?id={{.ChanIdIn}}">{{.AliasIn}}</a></td>
<td title="Channel Id: {{.ChanIdOut}}" class="truncate"><a href="/af?id={{.ChanIdOut}}">{{.AliasOut}}</a></td>
<td title="{{fmt .Amount}} sats" style="text-align: right;">{{m .Amount}}</td>
<td title="{{fmt .Amount}} sats" style="text-align: right;
{{if .Outbound}}
background:{{if eq $.ColorScheme "dark"}}darkred;{{else}}pink;{{end}}
{{end}}
{{if .Inbound}}
background:{{if eq $.ColorScheme "dark"}}darkgreen;{{else}}lightgreen;{{end}}
{{end}}">{{m .Amount}}</td>
<td title="Fee: {{ff .Fee}}" style="text-align: right;">{{fmt .PPM}}</td>
</tr>
{{end}}
Expand Down Expand Up @@ -413,7 +419,16 @@
document.getElementById(formId).submit();
}
// warning message if Max HTLC % > 0
function confirmSubmit() {
function confirmSubmit(event) {
// Detect the clicked button
const clickedButton = event.submitter; // Modern browsers support this
if (clickedButton.name == 'update_all') {
var confirmed = confirm("This will update highlighted values for all custom rules. Are you sure?");
if (!confirmed) {
// user cancels, prevent form submission
return false;
}
}
if (Number(document.getElementById("maxHtlcPct").value)>0) {
var confirmed = confirm("Setting Max HTLC size to a % of Local Balance will only take effect when Local Balance exceeds 50% of Capacity. Still, reducing Max HTLC can prevent receiving certain large swaps and inbound payments. A better way to avoid failed HTLCs is to bump channel's fee rate upon every fail. Please confirm if you still want to set Max HTLC % > 0.");
if (!confirmed) {
Expand Down
22 changes: 13 additions & 9 deletions cmd/psweb/templates/bitcoin.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Amount is capped at remote channel balance to mimic brute force discovery" for="
</div>
{{if .IsExternal}}
<p>1. Please fund this mainchain Bitcoin address externally:</p>
<input class="input is-medium" type="text" onclick="copyToClipboard('peginAddress')" id="peginAddress" value="{{.PeginAddress}}" readonly>
<input class="input is-medium" type="text" style="cursor: pointer" title="Copy to clipboard" onclick="copyToClipboard('peginAddress')" id="peginAddress" value="{{.PeginAddress}}" readonly>
<br>
<br>
<div id="qrcode-container">
Expand Down Expand Up @@ -176,8 +176,8 @@ Amount is capped at remote channel balance to mimic brute force discovery" for="
</td>
<td>
{{if eq .Confirmations -1}}
Transaction not found in local mempool!
{{if .IsPegin}}<br><br>Provide another TxId or cancel the peg-in.
Transaction not found in mempool! Refresh this page to search again.
{{if .IsPegin}}<br><br>If you fee bumped an external funding, TxId may have changed.
<br>
<br>
<form autocomplete="off" action="/submit" method="post">
Expand All @@ -188,7 +188,6 @@ Amount is capped at remote channel balance to mimic brute force discovery" for="
<center>
<input type="hidden" name="action" value="externalPeginTxId">
<input class="button is-large" type="submit" name="externalPeginTxId" value="Provide TxId">
<input class="button is-large" type="submit" name="externalPeginCancel" value="Cancel Pegin">
</center>
</form>
{{end}}
Expand All @@ -203,7 +202,7 @@ Amount is capped at remote channel balance to mimic brute force discovery" for="
ETA:
</td>
<td>
{{.Duration}}
{{.ETA}}
{{end}}
{{end}}
</td>
Expand All @@ -213,7 +212,12 @@ Amount is capped at remote channel balance to mimic brute force discovery" for="
TxId:
</td>
<td style="overflow-wrap: break-word;">
<a href="{{.BitcoinApi}}/tx/{{.PeginTxId}}" target="_blank">{{.PeginTxId}}</a>
<a href="{{.BitcoinApi}}/tx/{{.PeginTxId}}" target="_blank" id="txid" title="Open in explorer">{{.PeginTxId}}</a>
<span style="cursor: pointer; font-size: .875em; margin-right: .125em; position: relative; top: -.25em; left: -.125em"
title="Copy to clipboard"
onclick="copyToClipboard('txid')">
📄<span style="position: absolute; top: .25em; left: .25em">📄</span>
</span>
</td>
</tr>
{{if .IsClaimJoin}}
Expand Down Expand Up @@ -736,10 +740,10 @@ Amount is capped at remote channel balance to mimic brute force discovery" for="
let hours = "17 hours";
{{if .CanClaimJoin}}
if (document.getElementById("claimJoin").checked) {
hours = "17-{{.ClaimJoinETA}} hours";
hours = "17-{{.ClaimJoinHours}} hours";
}
{{end}}
text += "\nClaim ETA: " + hours;
text += "\nDuration: " + hours;
}

if ({{.BitcoinBalance}} - peginAmount < 25000) {
Expand Down Expand Up @@ -768,7 +772,7 @@ Amount is capped at remote channel balance to mimic brute force discovery" for="
</center>
</form>
{{else}}
<input class="input is-medium" type="text" onclick="copyToClipboard('bitcoinAddress')" id="bitcoinAddress" value="{{.BitcoinAddress}}" readonly>
<input class="input is-medium" type="text" style="cursor: pointer" title="Copy to clipboard" onclick="copyToClipboard('bitcoinAddress')" id="bitcoinAddress" value="{{.BitcoinAddress}}" readonly>
<br>
<br>
<div id="qrcode-container">
Expand Down
Loading

0 comments on commit 7a3492f

Please sign in to comment.