nip60: don't lose tokens when bolt11 payment fails.

This commit is contained in:
fiatjaf
2026-02-03 19:17:14 -03:00
parent e17995d427
commit beb8a72491
4 changed files with 48 additions and 8 deletions
+40 -8
View File
@@ -94,6 +94,11 @@ meltworked:
nil, nil,
) )
// mark tokens as reserved before attempting melt
for _, i := range chosen.tokenIndexes {
w.Tokens[i].reserved = true
}
// request from mint to _melt_ into paying the invoice // request from mint to _melt_ into paying the invoice
delay := 200 * time.Millisecond delay := 200 * time.Millisecond
// this request will block until the invoice is paid or it fails // this request will block until the invoice is paid or it fails
@@ -103,17 +108,44 @@ meltworked:
Inputs: chosen.proofs, Inputs: chosen.proofs,
Outputs: preChange.bm, Outputs: preChange.bm,
}) })
inspectmeltstatusresponse: for {
if err != nil || meltStatus.State == nut05.Unpaid { if err != nil || meltStatus.State == nut05.Unpaid {
return "", fmt.Errorf("error melting token: %w", err) // unreserve tokens to available state on failure
} else if meltStatus.State == nut05.Unknown { for _, i := range chosen.tokenIndexes {
return "", fmt.Errorf("we don't know what happened with the melt at %s: %v", chosen.mint, meltStatus) w.Tokens[i].reserved = false
} else if meltStatus.State == nut05.Pending { }
for { return "", fmt.Errorf("error melting token: %w", err)
} else if meltStatus.State == nut05.Unknown {
// unreserve tokens to available state on failure
for _, i := range chosen.tokenIndexes {
w.Tokens[i].reserved = false
}
return "", fmt.Errorf("we don't know what happened with the melt at %s: %v", chosen.mint, meltStatus)
} else if meltStatus.State == nut05.Pending {
time.Sleep(delay) time.Sleep(delay)
delay *= 2 delay *= 2
meltStatus, err = client.GetMeltQuoteState(ctx, chosen.mint, meltStatus.Quote) meltStatus, err = client.GetMeltQuoteState(ctx, chosen.mint, meltStatus.Quote)
goto inspectmeltstatusresponse if err != nil {
// unreserve tokens to available state on failure
for _, i := range chosen.tokenIndexes {
w.Tokens[i].reserved = false
}
return "", fmt.Errorf("error checking melt status: %w", err)
}
if meltStatus.State == nut05.Unpaid || meltStatus.State == nut05.Unknown {
// unreserve tokens to available state on failure
for _, i := range chosen.tokenIndexes {
w.Tokens[i].reserved = false
}
return "", fmt.Errorf("melt failed with state %v", meltStatus.State)
} else if meltStatus.State == nut05.Paid {
// payment successful
break
}
// continue looping for pending state
continue
} else if meltStatus.State == nut05.Paid {
break
} }
} }
+3
View File
@@ -153,6 +153,9 @@ func (w *Wallet) getProofsForSending(
) (chosenTokens, uint64, error) { ) (chosenTokens, uint64, error) {
byMint := make(map[string]chosenTokens) byMint := make(map[string]chosenTokens)
for t, token := range w.Tokens { for t, token := range w.Tokens {
if token.reserved {
continue
}
if fromMint != "" && token.Mint != fromMint { if fromMint != "" && token.Mint != fromMint {
continue continue
} }
+1
View File
@@ -14,6 +14,7 @@ type Token struct {
Proofs cashu.Proofs `json:"proofs"` Proofs cashu.Proofs `json:"proofs"`
Deleted []nostr.ID `json:"del,omitempty"` Deleted []nostr.ID `json:"del,omitempty"`
reserved bool
mintedAt nostr.Timestamp mintedAt nostr.Timestamp
event *nostr.Event event *nostr.Event
} }
+4
View File
@@ -249,6 +249,10 @@ func (w *Wallet) removeDeletedToken(eventId nostr.ID) {
func (w *Wallet) Balance() uint64 { func (w *Wallet) Balance() uint64 {
var sum uint64 var sum uint64
for _, token := range w.Tokens { for _, token := range w.Tokens {
if token.reserved {
continue
}
sum += token.Proofs.Amount() sum += token.Proofs.Amount()
} }
return sum return sum