diff --git a/app/components/set-default-account-modal/set-default-account-modal.tsx b/app/components/set-default-account-modal/set-default-account-modal.tsx index ca3acfc31..bda4f3e36 100644 --- a/app/components/set-default-account-modal/set-default-account-modal.tsx +++ b/app/components/set-default-account-modal/set-default-account-modal.tsx @@ -10,7 +10,10 @@ import { useBreez } from "@app/hooks" import { useApolloClient } from "@apollo/client" import { useI18nContext } from "@app/i18n/i18n-react" import { useNavigation } from "@react-navigation/native" -import { useSetDefaultAccountModalQuery } from "@app/graphql/generated" +import { + useAccountUpdateDefaultWalletIdMutation, + useSetDefaultAccountModalQuery, +} from "@app/graphql/generated" import { usePersistentStateContext } from "@app/store/persistent-state" // utils @@ -34,17 +37,23 @@ export const SetDefaultAccountModal = ({ isVisible, toggleModal }: Props) => { const { LL } = useI18nContext() const { btcWallet } = useBreez() const { updateState } = usePersistentStateContext() + const [updateDefaultWalletId] = useAccountUpdateDefaultWalletIdMutation() const { data } = useSetDefaultAccountModalQuery({ fetchPolicy: "cache-only", }) const usdWallet = getUsdWallet(data?.me?.defaultAccount?.wallets) - const onPressHandler = (currency: string) => { + const onPressHandler = async (currency: string) => { let defaultWallet = usdWallet if (currency === "BTC") { defaultWallet = btcWallet } + if (defaultWallet?.id) { + await updateDefaultWalletId({ + variables: { input: { walletId: defaultWallet.id } }, + }) + } updateState((state: any) => { if (state) return { diff --git a/app/contexts/BreezContext.tsx b/app/contexts/BreezContext.tsx index a7b2e34e6..366bc5b22 100644 --- a/app/contexts/BreezContext.tsx +++ b/app/contexts/BreezContext.tsx @@ -1,9 +1,19 @@ import React, { createContext, useEffect, useRef, useState } from "react" -import { WalletCurrency } from "@app/graphql/generated" +import { useUpdateExternalWalletMutation, WalletCurrency } from "@app/graphql/generated" import { usePersistentStateContext } from "@app/store/persistent-state" import { Alert, Platform } from "react-native" import { v4 as uuidv4 } from "uuid" -import { initializeBreezSDK, getInfo, handleSparkMigration } from "@app/utils/breez-sdk" +import { + initializeBreezSDK, + getInfo, + handleSparkMigration, + registerLightningAddress, + getLightningAddress, + checkLightningAddressAvailable, +} from "@app/utils/breez-sdk" +import { useAppConfig } from "@app/hooks/use-app-config" +import { useAddressScreenQuery } from "@app/graphql/generated" +import { useIsAuthed } from "@app/graphql/is-authed-context" import SparkMigrationModal from "@app/components/spark-migration-modal" type BtcWallet = { @@ -34,6 +44,14 @@ type Props = { export const BreezProvider = ({ children }: Props) => { const { persistentState, updateState } = usePersistentStateContext() + const { appConfig } = useAppConfig() + const isAuthed = useIsAuthed() + const { data: meData } = useAddressScreenQuery({ + fetchPolicy: "cache-first", + skip: !isAuthed, + }) + const [updateExternalWallet] = useUpdateExternalWalletMutation() + const [loading, setLoading] = useState(false) const [btcWallet, setBtcWallet] = useState({ id: "", @@ -94,17 +112,16 @@ export const BreezProvider = ({ children }: Props) => { if (updatingBalanceRef.current) return updatingBalanceRef.current = true try { - const balanceSats = await getInfo() - setBtcWallet({ - id: uuidv4(), - walletCurrency: WalletCurrency.Btc, - balance: balanceSats, - }) + const walletInfo = await getInfo() + setBtcWallet((prev) => ({ + ...prev, + balance: Number(walletInfo.balanceSats), + })) updateState((state: any) => { if (state) return { ...state, - breezBalance: balanceSats, + breezBalance: Number(walletInfo.balanceSats), } return undefined }) @@ -113,6 +130,49 @@ export const BreezProvider = ({ children }: Props) => { } } + const updateExternalWalletLnurlp = async (lnurlp: string) => { + const externalWalletRes = await updateExternalWallet({ + variables: { input: { lnurlp } }, + }) + console.log("Update External Wallet Response: ", externalWalletRes) + const walletId = externalWalletRes.data?.updateExternalWallet.walletId + if (walletId) { + setBtcWallet((prev) => ({ + ...prev, + id: walletId, + })) + } + } + + const ensureLightningAddress = async () => { + const username = meData?.me?.username + if (!username) return + + try { + const existing = await getLightningAddress() + console.log("BREEZ LIGHTNING ADDRESS: ", existing) + + if (existing) { + updateExternalWalletLnurlp(existing.lnurl.bech32) + return + } + + // Register with username as the Lightning address + const lightningAddress = username + uuidv4() + const res = await registerLightningAddress( + lightningAddress, + `Pay to ${username}@${appConfig.galoyInstance.lnAddressHostname}`, + ) + console.log("BREEZ LIGHTNING ADDRESS RES: ", res) + + if (res) { + updateExternalWalletLnurlp(res.lnurl.bech32) + } + } catch (err) { + console.warn("Failed to register Lightning address:", err) + } + } + const getBreezInfo = async () => { if (initializingRef.current) return initializingRef.current = true @@ -122,6 +182,9 @@ export const BreezProvider = ({ children }: Props) => { await updateBalance() setLoading(false) + // Register Lightning address + await ensureLightningAddress() + // Trigger migration after Spark SDK is ready if (!persistentState.sparkMigrationCompleted) { await onMigrate() diff --git a/app/graphql/front-end-mutations.ts b/app/graphql/front-end-mutations.ts index b9de734ee..dcdeedcd6 100644 --- a/app/graphql/front-end-mutations.ts +++ b/app/graphql/front-end-mutations.ts @@ -212,4 +212,14 @@ gql` uploadUrl } } + + mutation UpdateExternalWallet($input: UpdateExternalWalletInput!) { + updateExternalWallet(input: $input) { + errors { + code + message + } + walletId + } + } ` diff --git a/app/screens/import-wallet-screen/ImportWallet.tsx b/app/screens/import-wallet-screen/ImportWallet.tsx index bad93e14c..1ae8e7d32 100644 --- a/app/screens/import-wallet-screen/ImportWallet.tsx +++ b/app/screens/import-wallet-screen/ImportWallet.tsx @@ -16,6 +16,7 @@ import { useI18nContext } from "@app/i18n/i18n-react" import { useCreateAccount } from "@app/hooks/useCreateAccount" import { Text, useTheme, useThemeMode } from "@rneui/themed" import { usePersistentStateContext } from "@app/store/persistent-state" +import { useAppConfig } from "@app/hooks/use-app-config" // utils import { disconnectToSDK, initializeBreezSDK } from "@app/utils/breez-sdk" @@ -31,6 +32,7 @@ const ImportWallet: React.FC = ({ navigation, route }) => { const { mode } = useThemeMode() const { LL } = useI18nContext() const { updateState } = usePersistentStateContext() + const { appConfig } = useAppConfig() const { createDeviceAccountAndLogin } = useCreateAccount() const inputRef = useRef([]) diff --git a/app/screens/settings-screen/default-wallet.tsx b/app/screens/settings-screen/default-wallet.tsx index 279bdb0d8..e126b56db 100644 --- a/app/screens/settings-screen/default-wallet.tsx +++ b/app/screens/settings-screen/default-wallet.tsx @@ -1,5 +1,8 @@ import { gql } from "@apollo/client" -import { useSetDefaultWalletScreenQuery } from "@app/graphql/generated" +import { + useAccountUpdateDefaultWalletIdMutation, + useSetDefaultWalletScreenQuery, +} from "@app/graphql/generated" import { useIsAuthed } from "@app/graphql/is-authed-context" import { useI18nContext } from "@app/i18n/i18n-react" import { Text, makeStyles } from "@rneui/themed" @@ -54,6 +57,7 @@ export const DefaultWalletScreen: React.FC = () => { fetchPolicy: "cache-first", skip: !isAuthed, }) + const [updateDefaultWalletId] = useAccountUpdateDefaultWalletIdMutation() const usdWallet = getUsdWallet(data?.me?.defaultAccount?.wallets) @@ -73,6 +77,12 @@ export const DefaultWalletScreen: React.FC = () => { defaultWallet = btcWallet } + if (defaultWallet.id) { + await updateDefaultWalletId({ + variables: { input: { walletId: defaultWallet.id } }, + }) + } + updateState((state: any) => { if (state) return { diff --git a/app/types/declaration.d.ts b/app/types/declaration.d.ts index 475131c04..457cd07b5 100644 --- a/app/types/declaration.d.ts +++ b/app/types/declaration.d.ts @@ -32,4 +32,5 @@ declare module "@env" { export const GREENLIGHT_PARTNER_KEY: string export const GOOGLE_PLACE_API_KEY: string export const MIGRATION_FEE_LNURL_W: string + export const BREEZ_LNURL_DOMAIN: string } diff --git a/app/types/transactions.ts b/app/types/transactions.ts index 9b88e49f4..50cde7643 100644 --- a/app/types/transactions.ts +++ b/app/types/transactions.ts @@ -103,7 +103,6 @@ export const getTransactionStatus = ( tx: UnifiedTransaction, ): "SUCCESS" | "PENDING" | "FAILURE" => { if (isBreezTransaction(tx)) { - // PaymentStatus: Completed = 0, Pending = 1, Failed = 2 switch (tx.payment.status) { case 0: case PaymentStatus.Completed: diff --git a/app/utils/breez-sdk/spark.ts b/app/utils/breez-sdk/spark.ts index 1561b2050..fb5a21403 100644 --- a/app/utils/breez-sdk/spark.ts +++ b/app/utils/breez-sdk/spark.ts @@ -28,11 +28,12 @@ import type { RecommendedFees, SendPaymentMethod, LnurlPayResponse, + LightningAddressInfo, Payment, Logger, LogEntry, } from "@breeztech/breez-sdk-spark-react-native" -import { API_KEY } from "@env" +import { API_KEY, BREEZ_LNURL_DOMAIN } from "@env" import { appendLog, initLogBuffer } from "./log-buffer" // Constants @@ -67,7 +68,7 @@ export const initializeBreezSDK = async (): Promise => { breezSDKInitializing = (async () => { try { - await retry(connectToSDK, 5000, 3) + await retry(() => connectToSDK(), 5000, 3) breezSDKInitialized = true return true } catch (error: unknown) { @@ -120,6 +121,7 @@ const connectToSDK = async (): Promise => { const config = defaultConfig(Network.Mainnet) config.apiKey = API_KEY + config.lnurlDomain = BREEZ_LNURL_DOMAIN config.maxDepositClaimFee = new MaxFee.NetworkRecommended({ leewaySatPerVbyte: BigInt(1), }) @@ -174,7 +176,7 @@ export const getInfo = async () => { const sdk = getSDKInstance() const info = await sdk.getInfo({ ensureSynced: true }) - return Number(info.balanceSats) + return info } // Fee Estimation @@ -624,3 +626,31 @@ export const refundDeposit = async ( return { success: false, error: message } } } + +// Lightning Address (LNURL-Pay) +export const checkLightningAddressAvailable = async ( + username: string, +): Promise => { + const sdk = getSDKInstance() + return sdk.checkLightningAddressAvailable({ username }) +} + +export const registerLightningAddress = async ( + username: string, + description?: string, +): Promise => { + const sdk = getSDKInstance() + return sdk.registerLightningAddress({ username, description }) +} + +export const getLightningAddress = async (): Promise< + LightningAddressInfo | undefined +> => { + const sdk = getSDKInstance() + return sdk.getLightningAddress() +} + +export const deleteLightningAddress = async (): Promise => { + const sdk = getSDKInstance() + await sdk.deleteLightningAddress() +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0e39cb3a3..b8a000c0f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -10,7 +10,7 @@ PODS: - breez_sdk_liquidFFI (0.11.13) - BreezSDKLiquid (0.11.13): - breez_sdk_liquidFFI (= 0.11.13) - - BreezSdkSparkReactNative (0.13.4): + - breeztech-breez-sdk-spark-react-native (0.7.14): - DoubleConversion - glog - hermes-engine @@ -3013,7 +3013,7 @@ SPEC CHECKSUMS: breez_sdk_liquid: 5c229f9ab3bcf6b648bbf2d512f6fe1eee96d121 breez_sdk_liquidFFI: f05fadc0611126ade76d1fe6761ed8b020aabefb BreezSDKLiquid: ee6bf5a57f1b2533dc3c14c24c9773496f17b756 - BreezSdkSparkReactNative: 22459556b92935587708d1e8f875a59fad16005f + breeztech-breez-sdk-spark-react-native: 0af390a27a5f95bc838cddbecead825b671f60b2 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 diff --git a/yarn.lock b/yarn.lock index a8ec0c412..c6182ea6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1334,10 +1334,10 @@ dependencies: "@noble/curves" "^1.7.0" -"@breeztech/breez-sdk-spark-react-native@^0.13.4": - version "0.13.4" - resolved "https://registry.yarnpkg.com/@breeztech/breez-sdk-spark-react-native/-/breez-sdk-spark-react-native-0.13.4.tgz#7c5429a677547fcbe3aef4fcb299c159e0ff1641" - integrity sha512-wspP9tXY4GDJWVH42xI3dhe+MS5ROYX0a2mOqHKLWGcFqQc+Yuf77NaPBhZqM3FDKxVvZkDtAhCDuqDJ4vaclQ== +"@breeztech/breez-sdk-spark-react-native@^0.7.14": + version "0.7.14" + resolved "https://registry.yarnpkg.com/@breeztech/breez-sdk-spark-react-native/-/breez-sdk-spark-react-native-0.7.14.tgz#b8914719e67620aa6b85178d7a903d7efcaedbac" + integrity sha512-xHhcwD0/aDQAGg6mJM1K1Wepp3bmf3SnuK7tIC2jSX7VA308lB92u6ag3vIQMvAtz6mZptu+8Le+br9sr2u4/w== dependencies: uniffi-bindgen-react-native "^0.29.3-1"