Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: handle lnurl success action #3362

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
788c041
feat(lnurl): implement successAction to display message on send-bitco…
esaugomez31 Feb 14, 2025
9f9479f
revert(config): restore default endOfLine setting to 'lf'
esaugomez31 Feb 14, 2025
1a77f0f
fix(lnurl): ensure successAction updates persist in confirmation screen
esaugomez31 Feb 14, 2025
cba0563
Merge branch 'main' into feat--handle-lnurl-success-action
esaugomez31 Feb 17, 2025
9046c4f
chore: improve code structure and optimization
esaugomez31 Feb 21, 2025
da0627a
feat: url success action - LUD-09
esaugomez31 Feb 21, 2025
e655e1c
revert: restore previous duration for Send Bitcoin Completed screen
esaugomez31 Feb 21, 2025
8645fd8
feat: LUD-10 AES implemented using decipherAES
esaugomez31 Feb 24, 2025
8633633
test: send bitcoin completed screen
esaugomez31 Feb 24, 2025
7c06888
fix(success-action): open link button UI
esaugomez31 Feb 26, 2025
a4fe8b0
test(send bitcoin screen): LUD-09 tests (message|url|url + message)
esaugomez31 Feb 26, 2025
d6626cb
fix: graphql lnInvoicePaymentSend transaction object added
esaugomez31 Feb 26, 2025
ad8a973
fix(tsc): preimage validated to SettlementViaLn type
esaugomez31 Feb 27, 2025
6b7c942
chore: code refatoring in the send bitcoin completed and send bitcoin…
esaugomez31 Feb 28, 2025
728fecd
test: LUD-10 (AES) to send bitcoin completed screen
esaugomez31 Feb 28, 2025
a10bb25
feat: description support added to LUD-10 AES
esaugomez31 Feb 28, 2025
7dee4af
feat(send-bitcoin-completed): disable auto-redirect after successful …
esaugomez31 Feb 28, 2025
ff70e8d
test: send bitcoin confirmation screen test
esaugomez31 Feb 28, 2025
d1600df
chore: updating language labels to send bitcoin completed screen and …
esaugomez31 Feb 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/navigation/stack-param-lists.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LNURLPaySuccessAction } from "lnurl-pay/dist/types/types"
import { PhoneCodeChannelType, UserContact, WalletCurrency } from "@app/graphql/generated"
import { EarnSectionType } from "@app/screens/earns-screen/sections"
import { PhoneLoginInitiateType } from "@app/screens/phone-auth-screen"
Expand Down Expand Up @@ -52,6 +53,7 @@ export type RootStackParamList = {
sendBitcoinCompleted: {
arrivalAtMempoolEstimate?: number
status: PaymentSendCompletedStatus
successAction?: LNURLPaySuccessAction | null
}
language: undefined
currency: undefined
Expand Down
24 changes: 21 additions & 3 deletions app/screens/send-bitcoin-screen/payment-details/index.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { LnUrlPayServiceResponse } from "lnurl-pay/dist/types/types"

import {
LnUrlPayServiceResponse,
LNURLPaySuccessAction,
} from "lnurl-pay/dist/types/types"
import {
GraphQlApplicationError,
IntraLedgerPaymentSendMutationHookResult,
Expand Down Expand Up @@ -101,6 +103,10 @@ export type SetInvoice<T extends WalletCurrency> = (params: {
paymentRequestAmount: BtcMoneyAmount
}) => PaymentDetail<T>

export type SetSuccessAction<T extends WalletCurrency> = (
successAction: LNURLPaySuccessAction | undefined,
) => PaymentDetail<T>

type BasePaymentDetail<T extends WalletCurrency> = {
memo?: string
paymentType:
Expand Down Expand Up @@ -152,6 +158,16 @@ export type PaymentDetailSetAmount<T extends WalletCurrency> =
destinationSpecifiedAmount: BtcMoneyAmount // the amount that comes from the destination
}

export type PaymentDetailSetSuccessAction<T extends WalletCurrency> =
| {
setSuccessAction: SetSuccessAction<T>
successAction?: LNURLPaySuccessAction
}
| {
setSuccessAction?: undefined
successAction?: undefined
}

// sendPayment and getFee are defined together
export type PaymentDetailSendPaymentGetFee<T extends WalletCurrency> =
| {
Expand Down Expand Up @@ -179,14 +195,16 @@ type LnurlSpecificProperties<T extends WalletCurrency> =
paymentType: typeof PaymentType.Lnurl
lnurlParams: LnUrlPayServiceResponse
setInvoice: SetInvoice<T>
setSuccessAction: SetSuccessAction<T>
}

// combine all rules together with base type
export type PaymentDetail<T extends WalletCurrency> = BasePaymentDetail<T> &
LnurlSpecificProperties<T> &
PaymentDetailSetMemo<T> &
PaymentDetailSetAmount<T> &
PaymentDetailSendPaymentGetFee<T>
PaymentDetailSendPaymentGetFee<T> &
PaymentDetailSetSuccessAction<T>

export const AmountInvalidReason = {
InsufficientBalance: "InsufficientBalance",
Expand Down
17 changes: 16 additions & 1 deletion app/screens/send-bitcoin-screen/payment-details/lightning.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { LnUrlPayServiceResponse } from "lnurl-pay/dist/types/types"
import {
LnUrlPayServiceResponse,
LNURLPaySuccessAction,
} from "lnurl-pay/dist/types/types"

import { WalletCurrency } from "@app/graphql/generated"
import {
Expand All @@ -13,6 +16,7 @@ import { PaymentType } from "@galoymoney/client"

import {
ConvertMoneyAmount,
SetSuccessAction,
SetInvoice,
GetFee,
PaymentDetail,
Expand Down Expand Up @@ -368,6 +372,7 @@ export type CreateLnurlPaymentDetailsParams<T extends WalletCurrency> = {
paymentRequest?: string
paymentRequestAmount?: BtcMoneyAmount
unitOfAccountAmount: MoneyAmount<WalletOrDisplayCurrency>
successAction?: LNURLPaySuccessAction
} & BaseCreatePaymentDetailsParams<T>

export const createLnurlPaymentDetails = <T extends WalletCurrency>(
Expand All @@ -383,6 +388,7 @@ export const createLnurlPaymentDetails = <T extends WalletCurrency>(
sendingWalletDescriptor,
destinationSpecifiedMemo,
senderSpecifiedMemo,
successAction,
} = params

const destinationSpecifiedAmount =
Expand Down Expand Up @@ -465,6 +471,13 @@ export const createLnurlPaymentDetails = <T extends WalletCurrency>(
})
}

const setSuccessAction: SetSuccessAction<T> = (newSuccessAction) => {
return createLnurlPaymentDetails({
...params,
successAction: newSuccessAction,
})
}

const setSendingWalletDescriptor: SetSendingWalletDescriptor<T> = (
newSendingWalletDescriptor,
) => {
Expand All @@ -488,6 +501,8 @@ export const createLnurlPaymentDetails = <T extends WalletCurrency>(
setInvoice,
convertMoneyAmount,
setConvertMoneyAmount,
successAction,
setSuccessAction,
...setAmount,
...setMemo,
...sendPaymentAndGetFee,
Expand Down
92 changes: 90 additions & 2 deletions app/screens/send-bitcoin-screen/send-bitcoin-completed-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useCallback, useEffect } from "react"
import { View, Alert } from "react-native"
import { View, Alert, TouchableOpacity } from "react-native"
import InAppReview from "react-native-in-app-review"
import Clipboard from "@react-native-clipboard/clipboard"

import { useApolloClient } from "@apollo/client"
import { GaloyIcon } from "@app/components/atomic/galoy-icon"
import { Screen } from "@app/components/screen"
import { toastShow } from "@app/utils/toast"
import {
SuccessIconAnimation,
CompletedTextAnimation,
Expand Down Expand Up @@ -36,7 +38,7 @@ type Props = {
type StatusProcessed = "SUCCESS" | "PENDING" | "QUEUED"

const SendBitcoinCompletedScreen: React.FC<Props> = ({ route }) => {
const { arrivalAtMempoolEstimate, status: statusRaw } = route.params
const { arrivalAtMempoolEstimate, status: statusRaw, successAction } = route.params
const styles = useStyles()
const {
theme: { colors },
Expand Down Expand Up @@ -114,6 +116,52 @@ const SendBitcoinCompletedScreen: React.FC<Props> = ({ route }) => {
}
}, [client, feedbackModalShown, LL, showSuggestionModal, navigation, requestFeedback])

const copyToClipboard = (text: string, message: string) => {
Clipboard.setString(text)
toastShow({
type: "success",
message,
LL,
})
}

const SuccessActionComponent = () => {
if (!successAction) return null

const originalText = successAction.message ?? ""
const truncatedText =
originalText.slice(0, 300) + (originalText.length > 300 ? "..." : "")

switch (successAction.tag) {
case "message":
return (
<View style={styles.successActionContainer}>
<Text style={styles.fieldTitleText}>{LL.SendBitcoinScreen.note()}</Text>

<View style={styles.successActionFieldContainer}>
<View style={styles.disabledFieldBackground}>
<Text style={styles.truncatedText}>{truncatedText}</Text>
</View>
<TouchableOpacity
style={styles.iconContainer}
onPress={() =>
copyToClipboard(
originalText ?? "",
LL.SendBitcoinScreen.copiedDestination(),
)
}
hitSlop={30}
>
<GaloyIcon name={"copy-paste"} size={18} color={colors.primary} />
</TouchableOpacity>
</View>
</View>
)
default:
return null
}
}

const MainIcon = () => {
switch (status) {
case "SUCCESS":
Expand Down Expand Up @@ -151,6 +199,7 @@ const SendBitcoinCompletedScreen: React.FC<Props> = ({ route }) => {
{SuccessText()}
</Text>
</CompletedTextAnimation>
<SuccessActionComponent />
</View>
<SuggestionModal
navigation={navigation}
Expand Down Expand Up @@ -198,6 +247,45 @@ const useStyles = makeStyles(({ colors }) => ({
padding: 20,
borderRadius: 20,
},
truncatedText: {
fontSize: 14,
},
fieldTitleText: {
fontWeight: "bold",
marginBottom: 4,
},
successActionContainer: {
minWidth: "100%",
paddingHorizontal: 40,
marginTop: 30,
},
successMessage: {
fontSize: 18,
fontWeight: "bold",
textAlign: "center",
color: colors.primary,
},
successActionFieldContainer: {
flexDirection: "row",
borderStyle: "solid",
overflow: "hidden",
backgroundColor: colors.grey5,
borderRadius: 10,
alignItems: "center",
padding: 14,
minHeight: 60,
},
disabledFieldBackground: {
flex: 1,
opacity: 0.5,
flexDirection: "row",
alignItems: "center",
},
iconContainer: {
justifyContent: "center",
alignItems: "flex-start",
paddingLeft: 20,
},
}))

export default SendBitcoinCompletedScreen
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
params: {
arrivalAtMempoolEstimate: extraInfo?.arrivalAtMempoolEstimate,
status,
successAction: paymentDetail?.successAction || null,
},
},
]
Expand Down Expand Up @@ -196,7 +197,7 @@
setPaymentError(err.message || err.toString())
}
}
}, [

Check warning on line 200 in app/screens/send-bitcoin-screen/send-bitcoin-confirmation-screen.tsx

View workflow job for this annotation

GitHub Actions / Check Code

React Hook React.useCallback has a missing dependency: 'paymentDetail?.successAction'. Either include it or remove the dependency array
LL,
navigation,
paymentDetail.paymentType,
Expand Down
13 changes: 13 additions & 0 deletions app/screens/send-bitcoin-screen/send-bitcoin-details-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,15 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
}

const result = await requestInvoice(requestInvoiceParams)

setPaymentDetail((prev) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, why prev? this is more like paymentDetail

if (!prev || !prev.setSuccessAction) {
return prev
}

return prev.setSuccessAction(result.successAction)
})

setIsLoadingLnurl(false)
const invoice = result.invoice
const decodedInvoice = decodeInvoiceString(invoice, network as NetworkLibGaloy)
Expand All @@ -396,6 +405,10 @@ const SendBitcoinDetailsScreen: React.FC<Props> = ({ route }) => {
paymentRequest: invoice,
paymentRequestAmount: btcAmount,
})

paymentDetailForConfirmation =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if result.successAction is undefined?

paymentDetailForConfirmation?.setSuccessAction?.(result.successAction) ??
paymentDetailForConfirmation
} catch (error) {
setIsLoadingLnurl(false)
if (error instanceof Error) {
Expand Down
Loading