Compare commits
No commits in common. "6cf16d3da7790e4d36fe42295242506e9a3cc9fd" and "371084016740fc29751e5964e17c687420c6ea57" have entirely different histories.
6cf16d3da7
...
3710840167
@ -104,7 +104,7 @@ COMMANDS
|
|||||||
|
|
||||||
#### Running specific services
|
#### Running specific services
|
||||||
|
|
||||||
By default all services will be run. If you want to exclude specific services from running, set `COMPOSE_PROFILES` to use one or more of `minimal|images|search|payments|wallets|email|capture`. To only run mininal services without images, search, email, wallets, or payments:
|
By default all services will be run. If you want to exclude specific services from running, set `COMPOSE_PROFILES` to use one or more of `minimal|images|search|payments|email|capture`. To only run mininal services without images, search, or payments:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ COMPOSE_PROFILES=minimal ./sndev start
|
$ COMPOSE_PROFILES=minimal ./sndev start
|
||||||
|
@ -11,7 +11,7 @@ export async function getCost ({ sats }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function perform ({ invoiceId, sats, id: itemId, ...args }, { me, cost, tx }) {
|
export async function perform ({ invoiceId, sats, id: itemId, ...args }, { me, cost, tx }) {
|
||||||
const feeMsats = cost / BigInt(10) // 10% fee
|
const feeMsats = cost / BigInt(100)
|
||||||
const zapMsats = cost - feeMsats
|
const zapMsats = cost - feeMsats
|
||||||
itemId = parseInt(itemId)
|
itemId = parseInt(itemId)
|
||||||
|
|
||||||
@ -79,16 +79,12 @@ export async function onPaid ({ invoice, actIds }, { models, tx }) {
|
|||||||
FROM forwardees
|
FROM forwardees
|
||||||
), forward AS (
|
), forward AS (
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET
|
SET msats = users.msats + forwardees.msats
|
||||||
msats = users.msats + forwardees.msats,
|
|
||||||
"stackedMsats" = users."stackedMsats" + forwardees.msats
|
|
||||||
FROM forwardees
|
FROM forwardees
|
||||||
WHERE users.id = forwardees."userId"
|
WHERE users.id = forwardees."userId"
|
||||||
)
|
)
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET
|
SET msats = msats + ${itemAct.msats}::BIGINT - (SELECT msats FROM total_forwarded)::BIGINT
|
||||||
msats = msats + ${itemAct.msats}::BIGINT - (SELECT msats FROM total_forwarded)::BIGINT,
|
|
||||||
"stackedMsats" = "stackedMsats" + ${itemAct.msats}::BIGINT - (SELECT msats FROM total_forwarded)::BIGINT
|
|
||||||
WHERE id = ${itemAct.item.userId}::INTEGER`
|
WHERE id = ${itemAct.item.userId}::INTEGER`
|
||||||
|
|
||||||
// perform denomormalized aggregates: weighted votes, upvotes, msats, lastZapAt
|
// perform denomormalized aggregates: weighted votes, upvotes, msats, lastZapAt
|
||||||
|
@ -8,6 +8,7 @@ import { amountSchema } from '@/lib/validate'
|
|||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
import { useLightning } from './lightning'
|
import { useLightning } from './lightning'
|
||||||
import { nextTip } from './upvote'
|
import { nextTip } from './upvote'
|
||||||
|
import { InvoiceCanceledError } from './payment'
|
||||||
import { ZAP_UNDO_DELAY_MS } from '@/lib/constants'
|
import { ZAP_UNDO_DELAY_MS } from '@/lib/constants'
|
||||||
import { usePaidMutation } from './use-paid-mutation'
|
import { usePaidMutation } from './use-paid-mutation'
|
||||||
import { ACT_MUTATION } from '@/fragments/paidAction'
|
import { ACT_MUTATION } from '@/fragments/paidAction'
|
||||||
@ -71,7 +72,7 @@ export default function ItemAct ({ onClose, item, down, children, abortSignal })
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { error } = await act({
|
await act({
|
||||||
variables: {
|
variables: {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
sats: Number(amount),
|
sats: Number(amount),
|
||||||
@ -94,7 +95,6 @@ export default function ItemAct ({ onClose, item, down, children, abortSignal })
|
|||||||
if (!me) setItemMeAnonSats({ id: item.id, amount })
|
if (!me) setItemMeAnonSats({ id: item.id, amount })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (error) throw error
|
|
||||||
addCustomTip(Number(amount))
|
addCustomTip(Number(amount))
|
||||||
}, [me, act, down, item.id, onClose, abortSignal, strike])
|
}, [me, act, down, item.id, onClose, abortSignal, strike])
|
||||||
|
|
||||||
@ -219,15 +219,15 @@ export function useZap () {
|
|||||||
try {
|
try {
|
||||||
await abortSignal.pause({ me, amount: sats })
|
await abortSignal.pause({ me, amount: sats })
|
||||||
strike()
|
strike()
|
||||||
const { error } = await act({ variables, optimisticResponse })
|
await act({ variables, optimisticResponse })
|
||||||
if (error) throw error
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ActCanceledError) {
|
if (error instanceof InvoiceCanceledError || error instanceof ActCanceledError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = error?.message || error?.toString?.()
|
const reason = error?.message || error?.toString?.()
|
||||||
toaster.danger(reason)
|
|
||||||
|
toaster.danger('zap failed: ' + reason)
|
||||||
}
|
}
|
||||||
}, [me?.id, strike])
|
}, [me?.id, strike])
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import { MuteSubDropdownItem, PinSubDropdownItem } from './territory-header'
|
|||||||
import UserPopover from './user-popover'
|
import UserPopover from './user-popover'
|
||||||
import { useQrPayment } from './payment'
|
import { useQrPayment } from './payment'
|
||||||
import { useRetryCreateItem } from './use-item-submit'
|
import { useRetryCreateItem } from './use-item-submit'
|
||||||
import { useToast } from './toast'
|
|
||||||
|
|
||||||
export default function ItemInfo ({
|
export default function ItemInfo ({
|
||||||
item, full, commentsText = 'comments',
|
item, full, commentsText = 'comments',
|
||||||
@ -33,7 +32,6 @@ export default function ItemInfo ({
|
|||||||
}) {
|
}) {
|
||||||
const editThreshold = new Date(item.invoice?.confirmedAt ?? item.createdAt).getTime() + 10 * 60000
|
const editThreshold = new Date(item.invoice?.confirmedAt ?? item.createdAt).getTime() + 10 * 60000
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
const toaster = useToast()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [canEdit, setCanEdit] =
|
const [canEdit, setCanEdit] =
|
||||||
useState(item.mine && (Date.now() < editThreshold))
|
useState(item.mine && (Date.now() < editThreshold))
|
||||||
@ -74,14 +72,7 @@ export default function ItemInfo ({
|
|||||||
if (me && item.invoice?.actionState && item.invoice?.actionState !== 'PAID') {
|
if (me && item.invoice?.actionState && item.invoice?.actionState !== 'PAID') {
|
||||||
if (item.invoice?.actionState === 'FAILED') {
|
if (item.invoice?.actionState === 'FAILED') {
|
||||||
Component = () => <span className='text-warning'>retry payment</span>
|
Component = () => <span className='text-warning'>retry payment</span>
|
||||||
onClick = async () => {
|
onClick = async () => await retryCreateItem({ variables: { invoiceId: parseInt(item.invoice?.id) } }).catch(console.error)
|
||||||
try {
|
|
||||||
const { error } = await retryCreateItem({ variables: { invoiceId: parseInt(item.invoice?.id) } })
|
|
||||||
if (error) throw error
|
|
||||||
} catch (error) {
|
|
||||||
toaster.danger(error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Component = () => (
|
Component = () => (
|
||||||
<span
|
<span
|
||||||
|
@ -36,7 +36,6 @@ import { usePollVote } from './poll'
|
|||||||
import { paidActionCacheMods } from './use-paid-mutation'
|
import { paidActionCacheMods } from './use-paid-mutation'
|
||||||
import { useRetryCreateItem } from './use-item-submit'
|
import { useRetryCreateItem } from './use-item-submit'
|
||||||
import { payBountyCacheMods } from './pay-bounty'
|
import { payBountyCacheMods } from './pay-bounty'
|
||||||
import { useToast } from './toast'
|
|
||||||
|
|
||||||
function Notification ({ n, fresh }) {
|
function Notification ({ n, fresh }) {
|
||||||
const type = n.__typename
|
const type = n.__typename
|
||||||
@ -335,7 +334,6 @@ function useActRetry ({ invoice }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Invoicification ({ n: { invoice, sortTime } }) {
|
function Invoicification ({ n: { invoice, sortTime } }) {
|
||||||
const toaster = useToast()
|
|
||||||
const actRetry = useActRetry({ invoice })
|
const actRetry = useActRetry({ invoice })
|
||||||
const retryCreateItem = useRetryCreateItem({ id: invoice.item?.id })
|
const retryCreateItem = useRetryCreateItem({ id: invoice.item?.id })
|
||||||
const retryPollVote = usePollVote({ query: RETRY_PAID_ACTION, itemId: invoice.item?.id })
|
const retryPollVote = usePollVote({ query: RETRY_PAID_ACTION, itemId: invoice.item?.id })
|
||||||
@ -392,13 +390,8 @@ function Invoicification ({ n: { invoice, sortTime } }) {
|
|||||||
<Button
|
<Button
|
||||||
size='sm' variant='outline-warning ms-2 border-1 rounded py-0'
|
size='sm' variant='outline-warning ms-2 border-1 rounded py-0'
|
||||||
style={{ '--bs-btn-hover-color': '#fff', '--bs-btn-active-color': '#fff' }}
|
style={{ '--bs-btn-hover-color': '#fff', '--bs-btn-active-color': '#fff' }}
|
||||||
onClick={async () => {
|
onClick={() => {
|
||||||
try {
|
retry({ variables: { invoiceId: parseInt(invoiceId) } }).catch(console.error)
|
||||||
const { error } = await retry({ variables: { invoiceId: parseInt(invoiceId) } })
|
|
||||||
if (error) throw error
|
|
||||||
} catch (error) {
|
|
||||||
toaster.danger(error?.message || error?.toString?.())
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
retry
|
retry
|
||||||
|
@ -7,6 +7,7 @@ import { numWithUnits } from '@/lib/format'
|
|||||||
import { useShowModal } from './modal'
|
import { useShowModal } from './modal'
|
||||||
import { useRoot } from './root'
|
import { useRoot } from './root'
|
||||||
import { ActCanceledError, useAct } from './item-act'
|
import { ActCanceledError, useAct } from './item-act'
|
||||||
|
import { InvoiceCanceledError } from './payment'
|
||||||
import { useLightning } from './lightning'
|
import { useLightning } from './lightning'
|
||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
|
|
||||||
@ -57,15 +58,15 @@ export default function PayBounty ({ children, item }) {
|
|||||||
const handlePayBounty = async onCompleted => {
|
const handlePayBounty = async onCompleted => {
|
||||||
try {
|
try {
|
||||||
strike()
|
strike()
|
||||||
const { error } = await act({ onCompleted })
|
await act({ onCompleted })
|
||||||
if (error) throw error
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ActCanceledError) {
|
if (error instanceof InvoiceCanceledError || error instanceof ActCanceledError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = error?.message || error?.toString?.()
|
const reason = error?.message || error?.toString?.()
|
||||||
toaster.danger(reason)
|
|
||||||
|
toaster.danger('pay bounty failed: ' + reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ export class InvoiceCanceledError extends Error {
|
|||||||
super(actionError ?? `invoice canceled: ${hash}`)
|
super(actionError ?? `invoice canceled: ${hash}`)
|
||||||
this.name = 'InvoiceCanceledError'
|
this.name = 'InvoiceCanceledError'
|
||||||
this.hash = hash
|
this.hash = hash
|
||||||
this.actionError = actionError
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { useMe } from './me'
|
|||||||
import styles from './poll.module.css'
|
import styles from './poll.module.css'
|
||||||
import { signIn } from 'next-auth/react'
|
import { signIn } from 'next-auth/react'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
import { useQrPayment } from './payment'
|
import { InvoiceCanceledError, useQrPayment } from './payment'
|
||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
import { usePaidMutation } from './use-paid-mutation'
|
import { usePaidMutation } from './use-paid-mutation'
|
||||||
import { POLL_VOTE, RETRY_PAID_ACTION } from '@/fragments/paidAction'
|
import { POLL_VOTE, RETRY_PAID_ACTION } from '@/fragments/paidAction'
|
||||||
@ -25,14 +25,18 @@ export default function Poll ({ item }) {
|
|||||||
const variables = { id: v.id }
|
const variables = { id: v.id }
|
||||||
const optimisticResponse = { pollVote: { __typename: 'PollVotePaidAction', result: { id: v.id } } }
|
const optimisticResponse = { pollVote: { __typename: 'PollVotePaidAction', result: { id: v.id } } }
|
||||||
try {
|
try {
|
||||||
const { error } = await pollVote({
|
await pollVote({
|
||||||
variables,
|
variables,
|
||||||
optimisticResponse
|
optimisticResponse
|
||||||
})
|
})
|
||||||
if (error) throw error
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof InvoiceCanceledError) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const reason = error?.message || error?.toString?.()
|
const reason = error?.message || error?.toString?.()
|
||||||
toaster.danger(reason)
|
|
||||||
|
toaster.danger('poll vote failed: ' + reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: signIn}
|
: signIn}
|
||||||
|
@ -41,7 +41,7 @@ export default function TerritoryForm ({ sub }) {
|
|||||||
: await upsertSub({ variables: { oldName: sub?.name, ...variables } })
|
: await upsertSub({ variables: { oldName: sub?.name, ...variables } })
|
||||||
|
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
if (payError) return
|
if (payError) throw new Error('payment required')
|
||||||
|
|
||||||
// modify graphql cache to include new sub
|
// modify graphql cache to include new sub
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
|
@ -16,13 +16,15 @@ export default function TerritoryPaymentDue ({ sub }) {
|
|||||||
const client = useApolloClient()
|
const client = useApolloClient()
|
||||||
const [paySub] = usePaidMutation(SUB_PAY)
|
const [paySub] = usePaidMutation(SUB_PAY)
|
||||||
|
|
||||||
const onSubmit = useCallback(async ({ ...variables }) => {
|
const onSubmit = useCallback(
|
||||||
const { error } = await paySub({
|
async ({ ...variables }) => {
|
||||||
variables
|
const { error, payError } = await paySub({
|
||||||
})
|
variables
|
||||||
|
})
|
||||||
|
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
}, [client, paySub])
|
if (payError) throw new Error('payment required')
|
||||||
|
}, [client, paySub])
|
||||||
|
|
||||||
if (!sub || sub.userId !== Number(me?.id) || sub.status === 'ACTIVE') return null
|
if (!sub || sub.userId !== Number(me?.id) || sub.status === 'ACTIVE') return null
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ export const ToastProvider = ({ children }) => {
|
|||||||
>
|
>
|
||||||
<ToastBody>
|
<ToastBody>
|
||||||
<div className='d-flex align-items-center'>
|
<div className='d-flex align-items-center'>
|
||||||
<div className='flex-grow-1 overflow-hidden'>{toast.body}</div>
|
<div className='flex-grow-1'>{toast.body}</div>
|
||||||
<Button
|
<Button
|
||||||
variant={null}
|
variant={null}
|
||||||
className='p-0 ps-2'
|
className='p-0 ps-2'
|
||||||
|
@ -59,7 +59,7 @@ export default function useItemSubmit (mutation,
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
if (payError) return
|
if (payError) throw new Error('payment required')
|
||||||
|
|
||||||
// we don't know the mutation name, so we have to extract the result
|
// we don't know the mutation name, so we have to extract the result
|
||||||
const response = Object.values(data)[0]
|
const response = Object.values(data)[0]
|
||||||
|
@ -63,14 +63,6 @@ export function usePaidMutation (mutation,
|
|||||||
|
|
||||||
// if the mutation returns an invoice, pay it
|
// if the mutation returns an invoice, pay it
|
||||||
if (invoice) {
|
if (invoice) {
|
||||||
// adds payError, escalating to a normal error if the invoice is not canceled or
|
|
||||||
// has an actionError
|
|
||||||
const addPayError = (e, rest) => ({
|
|
||||||
...rest,
|
|
||||||
payError: e,
|
|
||||||
error: e instanceof InvoiceCanceledError && e.actionError ? e : undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// should we wait for the invoice to be paid?
|
// should we wait for the invoice to be paid?
|
||||||
if (response?.paymentMethod === 'OPTIMISTIC' && !forceWaitForPayment) {
|
if (response?.paymentMethod === 'OPTIMISTIC' && !forceWaitForPayment) {
|
||||||
// onCompleted is called before the invoice is paid for optimistic updates
|
// onCompleted is called before the invoice is paid for optimistic updates
|
||||||
@ -83,7 +75,7 @@ export function usePaidMutation (mutation,
|
|||||||
// onPayError is called after the invoice fails to pay
|
// onPayError is called after the invoice fails to pay
|
||||||
// useful for updating invoiceActionState to FAILED
|
// useful for updating invoiceActionState to FAILED
|
||||||
onPayError?.(e, client.cache, { data })
|
onPayError?.(e, client.cache, { data })
|
||||||
setInnerResult(r => addPayError(e, r))
|
setInnerResult(r => ({ payError: e, ...r }))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// the action is pessimistic
|
// the action is pessimistic
|
||||||
@ -103,7 +95,7 @@ export function usePaidMutation (mutation,
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('usePaidMutation: failed to pay invoice', e)
|
console.error('usePaidMutation: failed to pay invoice', e)
|
||||||
onPayError?.(e, client.cache, { data })
|
onPayError?.(e, client.cache, { data })
|
||||||
rest = addPayError(e, rest)
|
rest = { ...rest, payError: e, error: e }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import { useWalletLogger } from '../logger'
|
import { useWalletLogger } from '../logger'
|
||||||
|
import LNC from '@lightninglabs/lnc-web'
|
||||||
import { Status, migrateLocalStorage } from '.'
|
import { Status, migrateLocalStorage } from '.'
|
||||||
import { bolt11Tags } from '@/lib/bolt11'
|
import { bolt11Tags } from '@/lib/bolt11'
|
||||||
import useModal from '../modal'
|
import useModal from '../modal'
|
||||||
@ -15,7 +16,6 @@ const mutex = new Mutex()
|
|||||||
|
|
||||||
async function getLNC ({ me }) {
|
async function getLNC ({ me }) {
|
||||||
if (window.lnc) return window.lnc
|
if (window.lnc) return window.lnc
|
||||||
const { default: LNC } = await import('@lightninglabs/lnc-web')
|
|
||||||
// backwards compatibility: migrate to new storage key
|
// backwards compatibility: migrate to new storage key
|
||||||
if (me) migrateLocalStorage('lnc-web:default', `lnc-web:stacker:${me.id}`)
|
if (me) migrateLocalStorage('lnc-web:default', `lnc-web:stacker:${me.id}`)
|
||||||
window.lnc = new LNC({ namespace: me?.id ? `stacker:${me.id}` : undefined })
|
window.lnc = new LNC({ namespace: me?.id ? `stacker:${me.id}` : undefined })
|
||||||
|
@ -373,7 +373,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./docker/litd
|
context: ./docker/litd
|
||||||
profiles:
|
profiles:
|
||||||
- wallets
|
- payments
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
<<: *healthcheck
|
<<: *healthcheck
|
||||||
@ -479,7 +479,7 @@ services:
|
|||||||
context: ./docker/nwc
|
context: ./docker/nwc
|
||||||
container_name: nwc
|
container_name: nwc
|
||||||
profiles:
|
profiles:
|
||||||
- wallets
|
- payments
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
stacker_lnd:
|
stacker_lnd:
|
||||||
@ -509,7 +509,7 @@ services:
|
|||||||
image: lnbits/lnbits:0.12.5
|
image: lnbits/lnbits:0.12.5
|
||||||
container_name: lnbits
|
container_name: lnbits
|
||||||
profiles:
|
profiles:
|
||||||
- wallets
|
- payments
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${LNBITS_WEB_PORT}:5000"
|
- "${LNBITS_WEB_PORT}:5000"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user