diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 5eede0cb..0ae6ec7c 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -1,11 +1,29 @@ import { createInvoice, decodePaymentRequest, payViaPaymentRequest } from 'ln-service' -import { UserInputError, AuthenticationError } from 'apollo-server-micro' +import { UserInputError, AuthenticationError, ForbiddenError } from 'apollo-server-micro' import serialize from './serial' import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor' import lnpr from 'bolt11' import { SELECT } from './item' import { lnurlPayDescriptionHash } from '../../lib/lnurl' +const INVOICE_LIMIT = 10 + +export async function belowInvoiceLimit (models, userId) { + // make sure user has not exceeded INVOICE_LIMIT + const count = await models.invoice.count({ + where: { + userId, + expiresAt: { + gt: new Date() + }, + confirmedAt: null, + cancelled: false + } + }) + + return count < INVOICE_LIMIT +} + export async function getInvoice (parent, { id }, { me, models }) { if (!me) { throw new AuthenticationError('you must be logged in') @@ -181,6 +199,10 @@ export default { const user = await models.user.findUnique({ where: { id: me.id } }) + if (!await belowInvoiceLimit(models, me.id)) { + throw new ForbiddenError('too many pending invoices') + } + // set expires at to 3 hours into future const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3)) const description = `${amount} sats for @${user.name} on stacker.news` diff --git a/pages/api/lnurlp/[username]/pay.js b/pages/api/lnurlp/[username]/pay.js index a3c045fc..5f89fa31 100644 --- a/pages/api/lnurlp/[username]/pay.js +++ b/pages/api/lnurlp/[username]/pay.js @@ -3,6 +3,7 @@ import lnd from '../../../../api/lnd' import { createInvoice } from 'ln-service' import { lnurlPayDescriptionHashForUser } from '../../../../lib/lnurl' import serialize from '../../../../api/resolvers/serial' +import { belowInvoiceLimit } from '../../../../api/resolvers/wallet' export default async ({ query: { username, amount } }, res) => { const user = await models.user.findUnique({ where: { name: username } }) @@ -14,8 +15,12 @@ export default async ({ query: { username, amount } }, res) => { return res.status(400).json({ status: 'ERROR', reason: 'amount must be >=1000 msats' }) } + if (!await belowInvoiceLimit(models, user.id)) { + return res.status(400).json({ status: 'ERROR', reason: 'too many pending invoices' }) + } + // generate invoice - const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3)) + const expiresAt = new Date(new Date().setMinutes(new Date().getMinutes() + 1)) const description = `${amount} msats for @${user.name} on stacker.news` const descriptionHash = lnurlPayDescriptionHashForUser(username) try { diff --git a/pages/wallet.js b/pages/wallet.js index f034b7a0..18966628 100644 --- a/pages/wallet.js +++ b/pages/wallet.js @@ -114,6 +114,7 @@ export function FundForm () { initial={{ amount: 1000 }} + initialError={error?.toString()} schema={FundSchema} onSubmit={async ({ amount }) => { const { data } = await createInvoice({ variables: { amount: Number(amount) } })