From 396074fb7a696538167fd799fa3839f6181273a3 Mon Sep 17 00:00:00 2001 From: apotdevin Date: Tue, 8 Jun 2021 10:58:43 +0200 Subject: [PATCH] chore: lnurl channel --- server/schema/lnurl/resolvers.ts | 56 +++++++++++++++++- server/schema/lnurl/types.ts | 9 ++- server/schema/types.ts | 1 + src/graphql/fragmentTypes.json | 3 +- .../__generated__/lnUrl.generated.tsx | 56 +++++++++++++++++- src/graphql/mutations/lnUrl.ts | 12 ++++ src/graphql/types.ts | 18 +++++- .../home/quickActions/lnurl/LnChannel.tsx | 58 +++++++++++++++++++ src/views/home/quickActions/lnurl/index.tsx | 2 +- .../home/quickActions/lnurl/lnUrlModal.tsx | 5 ++ 10 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 src/views/home/quickActions/lnurl/LnChannel.tsx diff --git a/server/schema/lnurl/resolvers.ts b/server/schema/lnurl/resolvers.ts index 0bf3f094..9c550788 100644 --- a/server/schema/lnurl/resolvers.ts +++ b/server/schema/lnurl/resolvers.ts @@ -3,10 +3,17 @@ import { to } from 'server/helpers/async'; import { logger } from 'server/helpers/logger'; import { requestLimiter } from 'server/helpers/rateLimiter'; import { ContextType } from 'server/types/apiTypes'; -import { createInvoice, decodePaymentRequest, pay } from 'ln-service'; +import { + createInvoice, + decodePaymentRequest, + pay, + addPeer, + getWalletInfo, +} from 'ln-service'; import { CreateInvoiceType, DecodedType, + GetWalletInfoType, PayInvoiceType, } from 'server/types/ln-service.types'; import { lnAuthUrlGenerator } from 'server/helpers/lnAuth'; @@ -28,6 +35,7 @@ type FetchLnUrlParams = { url: string; }; +type LnUrlChannelType = { callback: string; k1: string; uri: string }; type LnUrlPayType = { callback: string; amount: number; comment: string }; type LnUrlWithdrawType = { callback: string; @@ -238,6 +246,49 @@ export const lnUrlResolvers = { throw new Error('ProblemWithdrawingFromLnUrlService'); } }, + lnUrlChannel: async ( + _: undefined, + { callback, k1, uri }: LnUrlChannelType, + context: ContextType + ) => { + await requestLimiter(context.ip, 'lnUrlChannel'); + const { lnd } = context; + + logger.debug('LnUrlChannel initiated with params: %o', { + callback, + uri, + k1, + }); + + const split = uri.split('@'); + + await to(addPeer({ lnd, socket: split[1], public_key: split[0] })); + + const info = await to(getWalletInfo({ lnd })); + + // If the callback url already has an initial query '?' identifier we don't need to add it again. + const initialIdentifier = callback.indexOf('?') != -1 ? '&' : '?'; + + const finalUrl = `${callback}${initialIdentifier}k1=${k1}&remoteid=${info.public_key}&private=0`; + + try { + const response = await fetchWithProxy(finalUrl); + const json = await response.json(); + + logger.debug('LnUrlChannel response: %o', json); + + if (json.status === 'ERROR') { + throw new Error(json.reason || 'LnServiceError'); + } + + return 'Successfully requested a channel open'; + } catch (error) { + logger.error('Error requesting channel from LnUrl service: %o', error); + throw new Error( + `Error requesting channel from LnUrl service: ${error}` + ); + } + }, }, LnUrlRequest: { __resolveType(parent: RequestWithType) { @@ -247,6 +298,9 @@ export const lnUrlResolvers = { if (parent.tag === 'withdrawRequest') { return 'WithdrawRequest'; } + if (parent.tag === 'channelRequest') { + return 'ChannelRequest'; + } return 'Unknown'; }, }, diff --git a/server/schema/lnurl/types.ts b/server/schema/lnurl/types.ts index 44c48546..dfc47943 100644 --- a/server/schema/lnurl/types.ts +++ b/server/schema/lnurl/types.ts @@ -19,7 +19,14 @@ export const lnUrlTypes = gql` tag: String } - union LnUrlRequest = WithdrawRequest | PayRequest + type ChannelRequest { + tag: String + k1: String + callback: String + uri: String + } + + union LnUrlRequest = WithdrawRequest | PayRequest | ChannelRequest type AuthResponse { status: String! diff --git a/server/schema/types.ts b/server/schema/types.ts index 2c6b4df8..e067f44a 100644 --- a/server/schema/types.ts +++ b/server/schema/types.ts @@ -117,6 +117,7 @@ export const mutationTypes = gql` lnMarketsLogout: Boolean! lnUrlAuth(url: String!): AuthResponse! lnUrlPay(callback: String!, amount: Int!, comment: String): PaySuccess! + lnUrlChannel(callback: String!, k1: String!, uri: String!): String! lnUrlWithdraw( callback: String! amount: Int! diff --git a/src/graphql/fragmentTypes.json b/src/graphql/fragmentTypes.json index 01b1b616..d0780e1d 100644 --- a/src/graphql/fragmentTypes.json +++ b/src/graphql/fragmentTypes.json @@ -2,7 +2,8 @@ "possibleTypes": { "LnUrlRequest": [ "WithdrawRequest", - "PayRequest" + "PayRequest", + "ChannelRequest" ], "Transaction": [ "InvoiceType", diff --git a/src/graphql/mutations/__generated__/lnUrl.generated.tsx b/src/graphql/mutations/__generated__/lnUrl.generated.tsx index a188fe7e..3c9d2855 100644 --- a/src/graphql/mutations/__generated__/lnUrl.generated.tsx +++ b/src/graphql/mutations/__generated__/lnUrl.generated.tsx @@ -17,6 +17,9 @@ export type FetchLnUrlMutation = ( ) | ( { __typename?: 'PayRequest' } & Pick + ) | ( + { __typename?: 'ChannelRequest' } + & Pick )> } ); @@ -61,6 +64,18 @@ export type WithdrawLnUrlMutation = ( & Pick ); +export type ChannelLnUrlMutationVariables = Types.Exact<{ + callback: Types.Scalars['String']; + k1: Types.Scalars['String']; + uri: Types.Scalars['String']; +}>; + + +export type ChannelLnUrlMutation = ( + { __typename?: 'Mutation' } + & Pick +); + export const FetchLnUrlDocument = gql` mutation FetchLnUrl($url: String!) { @@ -81,6 +96,12 @@ export const FetchLnUrlDocument = gql` commentAllowed tag } + ... on ChannelRequest { + tag + k1 + callback + uri + } } } `; @@ -222,4 +243,37 @@ export function useWithdrawLnUrlMutation(baseOptions?: Apollo.MutationHookOption } export type WithdrawLnUrlMutationHookResult = ReturnType; export type WithdrawLnUrlMutationResult = Apollo.MutationResult; -export type WithdrawLnUrlMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file +export type WithdrawLnUrlMutationOptions = Apollo.BaseMutationOptions; +export const ChannelLnUrlDocument = gql` + mutation ChannelLnUrl($callback: String!, $k1: String!, $uri: String!) { + lnUrlChannel(callback: $callback, k1: $k1, uri: $uri) +} + `; +export type ChannelLnUrlMutationFn = Apollo.MutationFunction; + +/** + * __useChannelLnUrlMutation__ + * + * To run a mutation, you first call `useChannelLnUrlMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useChannelLnUrlMutation` 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 [channelLnUrlMutation, { data, loading, error }] = useChannelLnUrlMutation({ + * variables: { + * callback: // value for 'callback' + * k1: // value for 'k1' + * uri: // value for 'uri' + * }, + * }); + */ +export function useChannelLnUrlMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(ChannelLnUrlDocument, options); + } +export type ChannelLnUrlMutationHookResult = ReturnType; +export type ChannelLnUrlMutationResult = Apollo.MutationResult; +export type ChannelLnUrlMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file diff --git a/src/graphql/mutations/lnUrl.ts b/src/graphql/mutations/lnUrl.ts index 27a13adf..c1d2a960 100644 --- a/src/graphql/mutations/lnUrl.ts +++ b/src/graphql/mutations/lnUrl.ts @@ -19,6 +19,12 @@ export const FETCH_LN_URL = gql` commentAllowed tag } + ... on ChannelRequest { + tag + k1 + callback + uri + } } } `; @@ -60,3 +66,9 @@ export const WITHDRAW_LN_URL = gql` ) } `; + +export const CHANNEL_LN_URL = gql` + mutation ChannelLnUrl($callback: String!, $k1: String!, $uri: String!) { + lnUrlChannel(callback: $callback, k1: $k1, uri: $uri) + } +`; diff --git a/src/graphql/types.ts b/src/graphql/types.ts index 82a252b2..dddfcfda 100644 --- a/src/graphql/types.ts +++ b/src/graphql/types.ts @@ -72,6 +72,14 @@ export type BosScoreResponse = { scores: Array; }; +export type ChannelRequest = { + __typename?: 'ChannelRequest'; + tag?: Maybe; + k1?: Maybe; + callback?: Maybe; + uri?: Maybe; +}; + export type CreateBoltzReverseSwapType = { __typename?: 'CreateBoltzReverseSwapType'; id: Scalars['String']; @@ -175,7 +183,7 @@ export type LnMarketsUserInfo = { last_ip?: Maybe; }; -export type LnUrlRequest = WithdrawRequest | PayRequest; +export type LnUrlRequest = WithdrawRequest | PayRequest | ChannelRequest; export type MessageType = { __typename?: 'MessageType'; @@ -194,6 +202,7 @@ export type Mutation = { lnMarketsLogout: Scalars['Boolean']; lnUrlAuth: AuthResponse; lnUrlPay: PaySuccess; + lnUrlChannel: Scalars['String']; lnUrlWithdraw: Scalars['String']; fetchLnUrl?: Maybe; createBaseTokenInvoice?: Maybe; @@ -270,6 +279,13 @@ export type MutationLnUrlPayArgs = { }; +export type MutationLnUrlChannelArgs = { + callback: Scalars['String']; + k1: Scalars['String']; + uri: Scalars['String']; +}; + + export type MutationLnUrlWithdrawArgs = { callback: Scalars['String']; amount: Scalars['Int']; diff --git a/src/views/home/quickActions/lnurl/LnChannel.tsx b/src/views/home/quickActions/lnurl/LnChannel.tsx new file mode 100644 index 00000000..e53c76e4 --- /dev/null +++ b/src/views/home/quickActions/lnurl/LnChannel.tsx @@ -0,0 +1,58 @@ +import { FC } from 'react'; +import { ChannelRequest } from 'src/graphql/types'; +import styled from 'styled-components'; +import { Title } from 'src/components/typography/Styled'; +import { Separation } from 'src/components/generic/Styled'; +import { getNodeLink, renderLine } from 'src/components/generic/helpers'; +import { ColorButton } from 'src/components/buttons/colorButton/ColorButton'; +import { useChannelLnUrlMutation } from 'src/graphql/mutations/__generated__/lnUrl.generated'; +import { toast } from 'react-toastify'; +import { getErrorContent } from 'src/utils/error'; + +const ModalText = styled.div` + width: 100%; + text-align: center; +`; + +type LnChannelProps = { + request: ChannelRequest; +}; + +export const LnChannel: FC = ({ request }) => { + const { k1, callback, uri } = request; + + const split = uri?.split('@'); + + const [channelLnUrl, { data, loading }] = useChannelLnUrlMutation({ + onError: error => toast.error(getErrorContent(error)), + onCompleted: data => toast.success(data.lnUrlChannel), + }); + + if (!callback || !k1 || !uri) { + return Missing information from LN Service; + } + + const callbackUrl = new URL(callback); + + return ( + <> + Channel + + {`Request from ${callbackUrl.host}`} + + {split?.[0] && renderLine('Peer', getNodeLink(split[0]))} + + { + channelLnUrl({ variables: { uri, k1, callback } }); + }} + > + {`Initiate Channel Request`} + + + ); +}; diff --git a/src/views/home/quickActions/lnurl/index.tsx b/src/views/home/quickActions/lnurl/index.tsx index d23ccfbd..181d7a08 100644 --- a/src/views/home/quickActions/lnurl/index.tsx +++ b/src/views/home/quickActions/lnurl/index.tsx @@ -63,7 +63,7 @@ export const LnUrlCard = () => { setLnUrl(value)} onEnter={() => handleDecode()} diff --git a/src/views/home/quickActions/lnurl/lnUrlModal.tsx b/src/views/home/quickActions/lnurl/lnUrlModal.tsx index d94d35f4..159d3b51 100644 --- a/src/views/home/quickActions/lnurl/lnUrlModal.tsx +++ b/src/views/home/quickActions/lnurl/lnUrlModal.tsx @@ -7,6 +7,7 @@ import { Title } from 'src/components/typography/Styled'; import { useFetchLnUrlMutation } from 'src/graphql/mutations/__generated__/lnUrl.generated'; import { getErrorContent } from 'src/utils/error'; import styled from 'styled-components'; +import { LnChannel } from './LnChannel'; import { LnPay } from './LnPay'; import { LnWithdraw } from './LnWithdraw'; @@ -49,6 +50,10 @@ export const LnUrlModal: FC = ({ url, type }) => { return ; } + if (data?.fetchLnUrl?.__typename === 'ChannelRequest') { + return ; + } + return ( <> Login