Support JIT invoicing on donations to rewards pool (#515)

* Support JIT invoicing on donations to rewards pool

Now you can just-in-time fund your account to donate to SN's reward pool.

You can also donate without an account via the @anon account, also using
JIT invoices.

* Ensure donate amount is numeric

* Explicit error checking for hash being required for invoice validation

* let donation exceptions bubble for jit funding

---------

Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
SatsAllDay 2023-09-24 15:38:37 -04:00 committed by GitHub
parent 9f333518c1
commit 9c774d596b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 21 deletions

View File

@ -38,7 +38,10 @@ export async function commentFilterClause (me, models) {
return clause
}
async function checkInvoice (models, hash, hmac, fee) {
export async function checkInvoice (models, hash, hmac, fee) {
if (!hash) {
throw new GraphQLError('hash required', { extensions: { code: 'BAD_INPUT' } })
}
if (!hmac) {
throw new GraphQLError('hmac required', { extensions: { code: 'BAD_INPUT' } })
}

View File

@ -1,8 +1,9 @@
import { GraphQLError } from 'graphql'
import { settleHodlInvoice } from 'ln-service'
import { amountSchema, ssValidate } from '../../lib/validate'
import serialize from './serial'
import { ANON_USER_ID } from '../../lib/constants'
import { getItem } from './item'
import { getItem, checkInvoice } from './item'
export default {
Query: {
@ -102,17 +103,38 @@ export default {
}
},
Mutation: {
donateToRewards: async (parent, { sats }, { me, models }) => {
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
donateToRewards: async (parent, { sats, hash, hmac }, { me, models, lnd }) => {
if (!me && !hash) {
throw new GraphQLError('you must be logged in or pay', { extensions: { code: 'FORBIDDEN' } })
}
let user
if (me) {
user = me
}
let invoice
if (hash) {
invoice = await checkInvoice(models, hash, hmac, sats)
user = invoice.user
}
await ssValidate(amountSchema, { amount: sats })
await serialize(models,
const trx = [
models.$queryRawUnsafe(
'SELECT donate($1::INTEGER, $2::INTEGER)',
sats, Number(me.id)))
sats, Number(user.id))
]
if (invoice) {
trx.unshift(models.$queryRaw`UPDATE users SET msats = msats + ${invoice.msatsReceived} WHERE id = ${user.id}`)
trx.push(models.invoice.update({ where: { hash: invoice.hash }, data: { confirmedAt: new Date() } }))
}
await serialize(models, ...trx)
if (invoice?.isHeld) {
await settleHodlInvoice({ secret: invoice.preimage, lnd })
}
return sats
}

View File

@ -7,7 +7,7 @@ export default gql`
}
extend type Mutation {
donateToRewards(sats: Int!): Int!
donateToRewards(sats: Int!, hash: String, hmac: String): Int!
}
type Rewards {

View File

@ -92,8 +92,8 @@ export function DonateButton () {
const toaster = useToast()
const [donateToRewards] = useMutation(
gql`
mutation donateToRewards($sats: Int!) {
donateToRewards(sats: $sats)
mutation donateToRewards($sats: Int!, $hash: String, $hmac: String) {
donateToRewards(sats: $sats, hash: $hash, hmac: $hmac)
}`)
return (
@ -104,24 +104,28 @@ export function DonateButton () {
amount: 1000
}}
schema={amountSchema}
onSubmit={async ({ amount }) => {
try {
await donateToRewards({
variables: {
sats: Number(amount)
}
})
onClose()
toaster.success('donated')
} catch (err) {
console.error(err)
invoiceable
onSubmit={async ({ amount, hash, hmac }) => {
const { error } = await donateToRewards({
variables: {
sats: Number(amount),
hash,
hmac
}
})
if (error) {
console.error(error)
toaster.danger('failed to donate')
} else {
toaster.success('donated')
}
onClose()
}}
>
<Input
label='amount'
name='amount'
type='number'
required
autoFocus
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}