diff --git a/server/schema/invoice/resolvers.ts b/server/schema/invoice/resolvers.ts index 3be39220..14dbdf9c 100644 --- a/server/schema/invoice/resolvers.ts +++ b/server/schema/invoice/resolvers.ts @@ -1,5 +1,6 @@ import { randomBytes, createHash } from 'crypto'; import { + pay, payViaRoutes, createInvoice, decodePaymentRequest, @@ -11,11 +12,18 @@ import { ContextType } from 'server/types/apiTypes'; import { logger } from 'server/helpers/logger'; import { requestLimiter } from 'server/helpers/rateLimiter'; import { getErrorMsg } from 'server/helpers/helpers'; -import { to } from 'server/helpers/async'; +import { to, toWithError } from 'server/helpers/async'; import { CreateInvoiceType, DecodedType } from 'server/types/ln-service.types'; const KEYSEND_TYPE = '5482373484'; +type PayType = { + max_fee: Number; + max_paths: Number; + request: String; + out?: String[]; +}; + export const invoiceResolvers = { Query: { getInvoiceStatusChange: async ( @@ -176,6 +184,32 @@ export const invoiceResolvers = { return true; }, + + pay: async ( + _: undefined, + { max_fee, max_paths, out, request }: PayType, + context: ContextType + ) => { + const { lnd } = context; + const props = { + request, + max_fee, + max_paths, + outgoing_channels: out || [], + }; + + logger.debug('Paying invoice with params: %o', props); + + const [response, error] = await toWithError(pay({ lnd, ...props })); + + if (error) { + logger.error('Error paying invoice: %o', error); + throw new Error(getErrorMsg(error)); + } + + logger.debug('Paid invoice: %o', response); + return true; + }, payViaRoute: async (_: undefined, params: any, context: ContextType) => { await requestLimiter(context.ip, 'payViaRoute'); diff --git a/server/schema/types.ts b/server/schema/types.ts index 93ca4f83..2cac7261 100644 --- a/server/schema/types.ts +++ b/server/schema/types.ts @@ -165,6 +165,12 @@ export const mutationTypes = gql` includePrivate: Boolean ): newInvoiceType circularRebalance(route: String!): Boolean + pay( + max_fee: Int! + max_paths: Int! + out: [String] + request: String! + ): Boolean bosPay( max_fee: Int! max_paths: Int! diff --git a/src/graphql/mutations/__generated__/pay.generated.tsx b/src/graphql/mutations/__generated__/pay.generated.tsx new file mode 100644 index 00000000..756d1ad1 --- /dev/null +++ b/src/graphql/mutations/__generated__/pay.generated.tsx @@ -0,0 +1,51 @@ +/* eslint-disable */ +import * as Types from '../../types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} +export type PayMutationVariables = Types.Exact<{ + max_fee: Types.Scalars['Int']; + max_paths: Types.Scalars['Int']; + out?: Types.Maybe> | Types.Maybe>; + request: Types.Scalars['String']; +}>; + + +export type PayMutation = { __typename?: 'Mutation', pay?: Types.Maybe }; + + +export const PayDocument = gql` + mutation Pay($max_fee: Int!, $max_paths: Int!, $out: [String], $request: String!) { + pay(max_fee: $max_fee, max_paths: $max_paths, out: $out, request: $request) +} + `; +export type PayMutationFn = Apollo.MutationFunction; + +/** + * __usePayMutation__ + * + * To run a mutation, you first call `usePayMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `usePayMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [payMutation, { data, loading, error }] = usePayMutation({ + * variables: { + * max_fee: // value for 'max_fee' + * max_paths: // value for 'max_paths' + * out: // value for 'out' + * request: // value for 'request' + * }, + * }); + */ +export function usePayMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(PayDocument, options); + } +export type PayMutationHookResult = ReturnType; +export type PayMutationResult = Apollo.MutationResult; +export type PayMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file diff --git a/src/graphql/mutations/pay.ts b/src/graphql/mutations/pay.ts new file mode 100644 index 00000000..5c49f10c --- /dev/null +++ b/src/graphql/mutations/pay.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +export const PAY = gql` + mutation Pay( + $max_fee: Int! + $max_paths: Int! + $out: [String] + $request: String! + ) { + pay(max_fee: $max_fee, max_paths: $max_paths, out: $out, request: $request) + } +`; diff --git a/src/graphql/types.ts b/src/graphql/types.ts index a584a84c..43262d3b 100644 --- a/src/graphql/types.ts +++ b/src/graphql/types.ts @@ -217,6 +217,7 @@ export type Mutation = { lnUrlWithdraw: Scalars['String']; logout: Scalars['Boolean']; openChannel?: Maybe; + pay?: Maybe; payViaRoute?: Maybe; removePeer?: Maybe; sendMessage?: Maybe; @@ -389,6 +390,14 @@ export type MutationOpenChannelArgs = { }; +export type MutationPayArgs = { + max_fee: Scalars['Int']; + max_paths: Scalars['Int']; + out?: Maybe>>; + request: Scalars['String']; +}; + + export type MutationPayViaRouteArgs = { id: Scalars['String']; route: Scalars['String']; diff --git a/src/views/home/account/pay/Pay.tsx b/src/views/home/account/pay/Pay.tsx index 06245694..c6537fd7 100644 --- a/src/views/home/account/pay/Pay.tsx +++ b/src/views/home/account/pay/Pay.tsx @@ -2,7 +2,6 @@ import { toast } from 'react-toastify'; import { getErrorContent } from 'src/utils/error'; import { ColorButton } from 'src/components/buttons/colorButton/ColorButton'; import { useEffect, useState } from 'react'; -import { useBosPayMutation } from 'src/graphql/mutations/__generated__/bosPay.generated'; import { InputWithDeco } from 'src/components/input/InputWithDeco'; import { ChannelSelect } from 'src/components/select/specific/ChannelSelect'; import { useDecodeRequestLazyQuery } from 'src/graphql/queries/__generated__/decodeRequest.generated'; @@ -12,6 +11,7 @@ import { Camera } from 'react-feather'; import Modal from 'src/components/modal/ReactModal'; import dynamic from 'next/dynamic'; import { LoadingCard } from 'src/components/loading/LoadingCard'; +import { usePayMutation } from 'src/graphql/mutations/__generated__/pay.generated'; import { SingleLine, Separation } from '../../../../components/generic/Styled'; const QRCodeReader = dynamic(() => import('src/components/qrReader'), { @@ -39,7 +39,7 @@ export const Pay: React.FC = ({ predefinedRequest, payCallback }) => { onError: () => toast.error('Error decoding invoice'), }); - const [pay, { loading }] = useBosPayMutation({ + const [pay, { loading }] = usePayMutation({ onCompleted: () => { payCallback && payCallback(); toast.success('Payment Sent');