ekzyis 0032e064b2
Automated retries (#1776)
* Poll failed invoices with visibility timeout

* Don't return intermediate failed invoices

* Don't retry too old invoices

* Retry invoices on client

* Only attempt payment 3 times

* Fix fallbacks during last retry

* Rename retry column to paymentAttempt

* Fix no index used

* Resolve TODOs

* Use expiring locks

* Better comments for constants

* Acquire lock during retry

* Use expiring lock in retry mutation

* Use now() instead of CURRENT_TIMESTAMP

* Cosmetic changes

* Immediately show failed post payments in notifications

* Update hasNewNotes

* Never retry on user cancel

For a consistent UX and less mental overhead, I decided to remove the exception for ITEM_CREATE where it would still retry in the background even though we want to show the payment failure immediately in notifications.

* Fix notifications without pending retries missing if no send wallets

If a stacker has no send wallets, they would miss notifications about failed payments because they would never get retried.

This commit fixes this by making the notifications query aware if the stacker has send wallets. This way, it can tell if a notification will be retried or not.

* Stop hiding userCancel in notifications

As mentioned in a previous commit, I want to show anything that will not be attempted anymore in notifications.

Before, I wanted to hide manually cancelled invoices but to not change experience unnecessarily and to decrease mental overhead, I changed my mind.

* Also consider invoice.cancelledAt in notifications

* Always retry failed payments, even without send wallets

* Fix notification indicator on retry timeout

* Set invoice.updated_at to date slightly in the future

* Use default job priority

* Stop retrying after one hour

* Remove special case for ITEM_CREATE

* Replace retryTimeout job with notification indicator query

* Fix sortTime

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2025-02-14 19:25:11 -06:00

243 lines
4.4 KiB
JavaScript

import { gql } from '@apollo/client'
import { ITEM_FULL_FIELDS } from './items'
import { VAULT_ENTRY_FIELDS } from './vault'
export const INVOICE_FIELDS = gql`
fragment InvoiceFields on Invoice {
id
hash
hmac
bolt11
satsRequested
satsReceived
cancelled
cancelledAt
confirmedAt
expiresAt
nostr
isHeld
comment
lud18Data
actionState
actionType
actionError
confirmedPreimage
forwardedSats
forwardStatus
}`
export const INVOICE_FULL = gql`
${ITEM_FULL_FIELDS}
${INVOICE_FIELDS}
query Invoice($id: ID!) {
invoice(id: $id) {
...InvoiceFields
item {
...ItemFullFields
}
}
}`
export const INVOICE = gql`
${INVOICE_FIELDS}
query Invoice($id: ID!) {
invoice(id: $id) {
...InvoiceFields
}
}`
export const WITHDRAWL = gql`
query Withdrawl($id: ID!) {
withdrawl(id: $id) {
id
createdAt
bolt11
hash
satsPaid
satsFeePaying
satsFeePaid
status
autoWithdraw
preimage
forwardedActionType
}
}`
export const DIRECT = gql`
query Direct($id: ID!) {
direct(id: $id) {
id
createdAt
bolt11
hash
sats
preimage
comment
lud18Data
nostr
}
}`
export const WALLET_HISTORY = gql`
${ITEM_FULL_FIELDS}
query WalletHistory($cursor: String, $inc: String) {
walletHistory(cursor: $cursor, inc: $inc) {
facts {
id
bolt11
autoWithdraw
type
createdAt
sats
status
type
description
invoiceComment
invoicePayerData
subName
item {
...ItemFullFields
}
}
cursor
}
}
`
export const CREATE_WITHDRAWL = gql`
mutation createWithdrawl($invoice: String!, $maxFee: Int!) {
createWithdrawl(invoice: $invoice, maxFee: $maxFee) {
id
}
}`
export const SEND_TO_LNADDR = gql`
mutation sendToLnAddr($addr: String!, $amount: Int!, $maxFee: Int!, $comment: String, $identifier: Boolean, $name: String, $email: String) {
sendToLnAddr(addr: $addr, amount: $amount, maxFee: $maxFee, comment: $comment, identifier: $identifier, name: $name, email: $email) {
id
}
}`
export const REMOVE_WALLET =
gql`
mutation removeWallet($id: ID!) {
removeWallet(id: $id)
}
`
// XXX [WALLET] this needs to be updated if another server wallet is added
export const WALLET_FIELDS = gql`
${VAULT_ENTRY_FIELDS}
fragment WalletFields on Wallet {
id
priority
type
updatedAt
enabled
vaultEntries {
...VaultEntryFields
}
wallet {
__typename
... on WalletLightningAddress {
address
}
... on WalletLnd {
socket
macaroon
cert
}
... on WalletCln {
socket
rune
cert
}
... on WalletLnbits {
url
invoiceKey
}
... on WalletNwc {
nwcUrlRecv
}
... on WalletPhoenixd {
url
secondaryPassword
}
... on WalletBlink {
apiKeyRecv
currencyRecv
}
}
}
`
export const WALLET = gql`
${WALLET_FIELDS}
query Wallet($id: ID!) {
wallet(id: $id) {
...WalletFields
}
}
`
// XXX [WALLET] this needs to be updated if another server wallet is added
export const WALLET_BY_TYPE = gql`
${WALLET_FIELDS}
query WalletByType($type: String!) {
walletByType(type: $type) {
...WalletFields
}
}
`
export const WALLETS = gql`
${WALLET_FIELDS}
query Wallets {
wallets {
...WalletFields
}
}
`
export const WALLET_LOGS = gql`
query WalletLogs($type: String, $from: String, $to: String, $cursor: String) {
walletLogs(type: $type, from: $from, to: $to, cursor: $cursor) {
cursor
entries {
id
createdAt
wallet
level
message
context
}
}
}
`
export const SET_WALLET_PRIORITY = gql`
mutation SetWalletPriority($id: ID!, $priority: Int!) {
setWalletPriority(id: $id, priority: $priority)
}
`
export const CANCEL_INVOICE = gql`
${INVOICE_FIELDS}
mutation cancelInvoice($hash: String!, $hmac: String, $userCancel: Boolean) {
cancelInvoice(hash: $hash, hmac: $hmac, userCancel: $userCancel) {
...InvoiceFields
}
}
`
export const FAILED_INVOICES = gql`
${INVOICE_FIELDS}
query FailedInvoices {
failedInvoices {
...InvoiceFields
}
}
`