diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 8d028514b..0d1d08166 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -63,9 +63,9 @@ const resolvers = { export default resolvers -export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, headers, protocol, logger }) { +export async function createWithdrawal (parent, { invoice, maxFee, amount }, { me, models, lnd, headers, protocol, logger }) { assertApiKeyNotPermitted({ me }) - await validateSchema(withdrawlSchema, { invoice, maxFee }) + await validateSchema(withdrawlSchema, { invoice, maxFee, amount }) await assertGofacYourself({ models, headers }) // remove 'lightning:' prefix if present @@ -95,7 +95,10 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model } if (!decoded.mtokens || BigInt(decoded.mtokens) <= 0) { - throw new GqlInputError('invoice must specify an amount') + if (!amount) { + throw new GqlInputError('invoice must specify an amount') + } + decoded.mtokens = BigInt(amount) * 1000n } if (decoded.mtokens > Number.MAX_SAFE_INTEGER) { diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index 95d4640eb..5ced5075f 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -13,7 +13,7 @@ const typeDefs = gql` } extend type Mutation { - createWithdrawl(invoice: String!, maxFee: Int!): PayIn! + createWithdrawl(invoice: String!, maxFee: Int!, amount: Int): PayIn! sendToLnAddr(addr: String!, amount: Int!, maxFee: Int!, comment: String, identifier: Boolean, name: String, email: String): PayIn! dropBolt11(hash: String!): Boolean buyCredits(credits: Int!): PayIn! diff --git a/fragments/withdrawal.js b/fragments/withdrawal.js index 4df98ca03..38cf002df 100644 --- a/fragments/withdrawal.js +++ b/fragments/withdrawal.js @@ -1,8 +1,8 @@ import { gql } from '@apollo/client' export const CREATE_WITHDRAWL = gql` - mutation createWithdrawl($invoice: String!, $maxFee: Int!) { - createWithdrawl(invoice: $invoice, maxFee: $maxFee) { + mutation createWithdrawl($invoice: String!, $maxFee: Int!, $amount: Int) { + createWithdrawl(invoice: $invoice, maxFee: $maxFee, amount: $amount) { id } }` diff --git a/lib/validate.js b/lib/validate.js index dc6d47ce5..3de0486e4 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -490,7 +490,8 @@ export const lastAuthRemovalSchema = object({ export const withdrawlSchema = object({ invoice: string().required('required').trim(), - maxFee: intValidator.required('required').min(0, 'must be at least 0') + maxFee: intValidator.required('required').min(0, 'must be at least 0'), + amount: intValidator.optional().min(1, 'must be at least 1 sat') }) export const bioSchema = object({ diff --git a/pages/withdraw.js b/pages/withdraw.js index 4cdc3280f..dcb0984cc 100644 --- a/pages/withdraw.js +++ b/pages/withdraw.js @@ -7,7 +7,7 @@ import styles from '@/styles/nav.module.css' import { useMutation } from '@apollo/client' import { CREATE_WITHDRAWL, SEND_TO_LNADDR } from '@/fragments/withdrawal' import { requestProvider } from 'webln' -import { useEffect, useState } from 'react' +import { useEffect, useState, useCallback } from 'react' import { useMe } from '@/components/me' import { Checkbox, Form, Input, InputUserSuggest, SubmitButton } from '@/components/form' import { lnAddrSchema, withdrawlSchema } from '@/lib/validate' @@ -82,6 +82,15 @@ export function InvWithdrawal () { const [createWithdrawl] = useMutation(CREATE_WITHDRAWL) const maxFeeDefault = me?.privates?.withdrawMaxFeeDefault + const [zeroAmount, setZeroAmount] = useState(false) + const checkInvoice = useCallback((invoice) => { + try { + const decoded = decode(invoice) + setZeroAmount(!decoded.mtokens || BigInt(decoded.mtokens) <= 0) + } catch { + setZeroAmount(false) + } + }, []) useEffect(() => { async function effect () { @@ -106,11 +115,14 @@ export function InvWithdrawal () { autoComplete='off' initial={{ invoice: '', - maxFee: maxFeeDefault + maxFee: maxFeeDefault, + amount: '' }} schema={withdrawlSchema} - onSubmit={async ({ invoice, maxFee }) => { - const { data } = await createWithdrawl({ variables: { invoice, maxFee: Number(maxFee) } }) + onSubmit={async ({ invoice, maxFee, amount }) => { + const variables = { invoice, maxFee: Number(maxFee) } + if (zeroAmount) variables.amount = Number(amount) + const { data } = await createWithdrawl({ variables }) router.push(`/transactions/${data.createWithdrawl.id}`) }} > @@ -120,8 +132,18 @@ export function InvWithdrawal () { required autoFocus clear - append={} + append={} + onChange={(_, e) => { checkInvoice(e.target.value) }} /> + {zeroAmount && ( + + )} { result = result.toLowerCase() - if (result.split('lightning=')[1]) { - helpers.setValue(result.split('lightning=')[1].split(/[&?]/)[0]) - } else if (decode(result.replace(/^lightning:/, ''))) { - helpers.setValue(result.replace(/^lightning:/, '')) + let invoice + if (result.includes('lightning=')) { + invoice = result.split('lightning=')[1].split(/[&?]/)[0] + helpers.setValue(invoice) } else { - throw new Error('Not a proper lightning payment request') + try { + invoice = result.replace(/^lightning:/, '') + decode(invoice) + helpers.setValue(invoice) + } catch { + toaster.danger('Not a proper lightning payment request') + onClose() + return + } } + if (onInvoiceChange) onInvoiceChange(invoice) onClose() }} styles={{