diff --git a/Cargo.lock b/Cargo.lock index f4168d7..f225c0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,12 +279,13 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "ark-lib" -version = "0.1.0-beta.2" -source = "git+https://gitlab.com/ark-bitcoin/bark?tag=server-0.1.0-beta.2#2d81f096ad41e866eeb8262830d8f59d4567096a" +version = "0.1.0-beta.5" +source = "git+https://gitlab.com/ark-bitcoin/bark?tag=server-0.1.0-beta.5#916e0d61d0fa1be274eb731a545a73774e2df492" dependencies = [ "bark-bitcoin-ext", "bitcoin", "chrono", + "getrandom 0.3.3", "hex-conservative 0.3.0", "lazy_static", "lightning", @@ -868,8 +869,8 @@ dependencies = [ [[package]] name = "bark-bitcoin-ext" -version = "0.1.0-beta.2" -source = "git+https://gitlab.com/ark-bitcoin/bark?tag=server-0.1.0-beta.2#2d81f096ad41e866eeb8262830d8f59d4567096a" +version = "0.1.0-beta.5" +source = "git+https://gitlab.com/ark-bitcoin/bark?tag=server-0.1.0-beta.5#916e0d61d0fa1be274eb731a545a73774e2df492" dependencies = [ "bitcoin", "lazy_static", @@ -880,8 +881,8 @@ dependencies = [ [[package]] name = "bark-server-rpc" -version = "0.1.0-beta.2" -source = "git+https://gitlab.com/ark-bitcoin/bark?tag=server-0.1.0-beta.2#2d81f096ad41e866eeb8262830d8f59d4567096a" +version = "0.1.0-beta.5" +source = "git+https://gitlab.com/ark-bitcoin/bark?tag=server-0.1.0-beta.5#916e0d61d0fa1be274eb731a545a73774e2df492" dependencies = [ "ark-lib", "bitcoin", diff --git a/bun.lock b/bun.lock index 17edb59..d8188cd 100644 --- a/bun.lock +++ b/bun.lock @@ -65,7 +65,7 @@ "react-native-gesture-handler": "~2.28.0", "react-native-keychain": "^10.0.0", "react-native-mmkv": "^4.1.0", - "react-native-nitro-ark": "^0.0.79", + "react-native-nitro-ark": "^0.0.82", "react-native-nitro-modules": "^0.31.10", "react-native-qrcode-svg": "^6.3.21", "react-native-reanimated": "~4.1.6", @@ -1728,7 +1728,7 @@ "react-native-mmkv": ["react-native-mmkv@4.1.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-ia76WnU6dkLZxFkSSflxqFgHT2pIaML763aucEu7nMglF41oEWTdTtBu0o8a1cxbhZOaONk6KF8RQp5fLvPitA=="], - "react-native-nitro-ark": ["react-native-nitro-ark@0.0.79", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.31.4" } }, "sha512-PXUxmWDi6H7+ugkb2bl0LI/Qa+T23LSPlzSv46S9Xxt7q1+TrK7bZ9SAVxWgCteQ1sUQeCg29JQLcOL3b0iqWg=="], + "react-native-nitro-ark": ["react-native-nitro-ark@0.0.82", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.31.10" } }, "sha512-IrcSNWdH/5UqZPw9uKfRsQtzEOFvLxPDcz5lUvWxgaCCKMkRRzfZ7QnQ8lAgktokXIMbQil92wityN8iMdz/Vw=="], "react-native-nitro-modules": ["react-native-nitro-modules@0.31.10", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-hcvjTu9YJE9fMmnAUvhG8CxvYLpOuMQ/2eyi/S6GyrecezF6Rmk/uRQEL6v09BRFWA/xRVZNQVulQPS+2HS3mQ=="], diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index 6c6b1b2..09e02cc 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -262,7 +262,7 @@ PODS: - hermes-engine/Pre-built (= 0.81.5) - hermes-engine/Pre-built (0.81.5) - MMKVCore (2.2.4) - - NitroArk (0.0.79): + - NitroArk (0.0.82): - hermes-engine - NitroModules - RCTRequired @@ -2976,7 +2976,7 @@ SPEC CHECKSUMS: FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12 hermes-engine: 8bb0bdb1bf49c3bbc17cf4c2d02dcc2f706ab555 MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df - NitroArk: e29874d62421cb89d89c67e938624fb6c3aabf08 + NitroArk: 61ef84ad85ad0ee2edd5ec319373f8f6c165c1c9 NitroMmkv: a7f267ea1359e25072960fc17f8b2c2e023d7bac NitroModules: b7b51ba7f49acf5c4c13a56f2b0037b5f7bf55a7 NoahTools: efea1336628719c3ab4a565f3c03d2deeadd3d14 diff --git a/client/package.json b/client/package.json index c390557..4d67d68 100644 --- a/client/package.json +++ b/client/package.json @@ -102,7 +102,7 @@ "react-native-gesture-handler": "~2.28.0", "react-native-keychain": "^10.0.0", "react-native-mmkv": "^4.1.0", - "react-native-nitro-ark": "^0.0.79", + "react-native-nitro-ark": "^0.0.82", "react-native-nitro-modules": "^0.31.10", "react-native-qrcode-svg": "^6.3.21", "react-native-reanimated": "~4.1.6", diff --git a/client/src/components/Bip321Picker.tsx b/client/src/components/Bip321Picker.tsx index 12891d7..16f09f0 100644 --- a/client/src/components/Bip321Picker.tsx +++ b/client/src/components/Bip321Picker.tsx @@ -6,7 +6,7 @@ import { LightningIcon } from "~/lib/icons/Lightning"; import { OnchainIcon } from "~/lib/icons/Onchain"; import { cn } from "~/lib/utils"; -type PaymentMethod = "ark" | "lightning" | "onchain"; +type PaymentMethod = "ark" | "lightning" | "onchain" | "offer"; type Bip321PickerProps = { bip321Data: ParsedBip321; @@ -71,6 +71,15 @@ export const Bip321Picker = ({ onSelect={onSelect} /> )} + {bip321Data.offer && ( + } + label="Lightning Offer" + isSelected={selectedPaymentMethod === "offer"} + onSelect={onSelect} + /> + )} {bip321Data.onchainAddress && ( void; onCancel: () => void; isLoading?: boolean; diff --git a/client/src/hooks/usePayments.ts b/client/src/hooks/usePayments.ts index 1725c0d..f5bdd09 100644 --- a/client/src/hooks/usePayments.ts +++ b/client/src/hooks/usePayments.ts @@ -9,9 +9,10 @@ import { sendArkoorPayment, payLightningInvoice, payLightningAddress, + payLightningOffer, + checkLightningPayment, type ArkoorPaymentResult, - type Bolt11PaymentResult, - type LnurlPaymentResult, + type LightningSendResult, type OnchainPaymentResult, boardAllArk, offboardAllArk, @@ -163,11 +164,22 @@ type SendVariables = { btcPrice?: number; }; -type SendResult = - | ArkoorPaymentResult - | Bolt11PaymentResult - | LnurlPaymentResult - | OnchainPaymentResult; +type SendResult = ArkoorPaymentResult | LightningSendResult | OnchainPaymentResult; + +const awaitLightningPayment = async ( + paymentPromise: Promise>, +): Promise => { + const result = await paymentPromise; + if (result.isErr()) { + throw result.error; + } + const checkResult = await checkLightningPayment(result.value.payment_hash, true); + if (checkResult.isErr()) { + throw checkResult.error; + } + result.value.preimage = checkResult.value; + return result.value; +}; const mapDestinationToPaymentType = (destinationType: DestinationTypes): PaymentTypes | null => { switch (destinationType) { @@ -179,6 +191,8 @@ const mapDestinationToPaymentType = (destinationType: DestinationTypes): Payment return "Lnurl"; case "onchain": return "Onchain"; + case "offer": + return "Bolt12"; default: return null; } @@ -210,9 +224,8 @@ export function useSend(destinationType: DestinationTypes) { result = await sendArkoorPayment(destination, amountSat); break; case "lightning": - result = await payLightningInvoice(destination, amountSat); - break; - case "lnurl": + return awaitLightningPayment(payLightningInvoice(destination, amountSat)); + case "lnurl": { if (amountSat === undefined) { throw new Error("Amount is required for LNURL payments"); } @@ -220,13 +233,23 @@ export function useSend(destinationType: DestinationTypes) { if (destination.toLowerCase().endsWith(getLnurlDomain())) { const noahResult = await handleNoahWalletPayment(destination, amountSat, comment); if (noahResult) { - result = noahResult; - break; + if (noahResult.isErr()) { + throw noahResult.error; + } + const data = noahResult.value; + if ("payment_hash" in data) { + return awaitLightningPayment( + Promise.resolve(noahResult as Result), + ); + } + return data; } } - result = await payLightningAddress(destination, amountSat, comment || ""); - break; + return awaitLightningPayment(payLightningAddress(destination, amountSat, comment || "")); + } + case "offer": + return awaitLightningPayment(payLightningOffer(destination, amountSat)); default: throw new Error("Invalid destination type"); } @@ -276,11 +299,11 @@ export function useCheckAndClaimLnReceive() { log.d("Claim result", [result]); - if (result.isOk()) { + if (result.isOk() && result.value && result.value.length > 0) { return { amountSat }; } - log.d(`Attempt ${i + 1}/${maxAttempts} failed:`, [result.error.message]); + log.d(`Attempt ${i + 1}/${maxAttempts} failed:`, [result]); if (i < maxAttempts - 1) { await new Promise((resolve) => setTimeout(resolve, intervalMs)); @@ -302,7 +325,7 @@ async function handleNoahWalletPayment( destination: string, amountSat: number, comment: string | null, -): Promise | null> { +): Promise | null> { try { const [user, domain] = destination.split("@"); const lnurlEndpoint = `https://${domain}/.well-known/lnurlp/${user}`; @@ -323,7 +346,8 @@ async function handleNoahWalletPayment( return await sendArkoorPayment(callbackJson.ark, amountSat); } else if (callbackJson.pr) { log.d("Paying via Lightning Invoice from LNURL"); - return await payLightningInvoice(callbackJson.pr, amountSat); + const lnResult = await payLightningInvoice(callbackJson.pr, amountSat); + return lnResult; } else { log.w( "Invalid LNURL callback response for optimized Noah payment, falling back to standard LNURL.", diff --git a/client/src/hooks/useSendScreen.ts b/client/src/hooks/useSendScreen.ts index 6427e00..d263851 100644 --- a/client/src/hooks/useSendScreen.ts +++ b/client/src/hooks/useSendScreen.ts @@ -9,13 +9,7 @@ import { ParsedBip321, } from "../lib/sendUtils"; import { useSend } from "./usePayments"; -import { - type ArkoorPaymentResult, - type Bolt11PaymentResult, - type LnurlPaymentResult, - type OnchainPaymentResult, - type PaymentResult, -} from "../lib/paymentsApi"; +import { type PaymentResult } from "../lib/paymentsApi"; import { useQRCodeScanner } from "~/hooks/useQRCodeScanner"; import { useBtcToUsdRate } from "./useMarketData"; import { satsToUsd, usdToSats } from "../lib/utils"; @@ -48,7 +42,7 @@ export const useSendScreen = () => { const [parsedAmount, setParsedAmount] = useState(null); const [bip321Data, setBip321Data] = useState(null); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState< - "ark" | "lightning" | "onchain" + "ark" | "lightning" | "onchain" | "offer" >("onchain"); const [showConfirmation, setShowConfirmation] = useState(false); const [showSuccess, setShowSuccess] = useState(false); @@ -90,6 +84,8 @@ export const useSendScreen = () => { setSelectedPaymentMethod("ark"); } else if (bip321.lightningInvoice) { setSelectedPaymentMethod("lightning"); + } else if (bip321.offer) { + setSelectedPaymentMethod("offer"); } else { setSelectedPaymentMethod("onchain"); } @@ -147,60 +143,51 @@ export const useSendScreen = () => { let displayResult: DisplayResult | null = null; - const processResult = (res: PaymentResult) => { - switch (res.payment_type) { - case "Onchain": { - const onchainRes = res as OnchainPaymentResult; - return { - success: true, - amount_sat: onchainRes.amount_sat, - destination: onchainRes.destination_address, - txid: onchainRes.txid, - type: res.payment_type, - }; - } - case "Arkoor": { - const arkoorRes = res as ArkoorPaymentResult; - return { - success: true, - amount_sat: arkoorRes.amount_sat, - destination: arkoorRes.destination_pubkey, - type: res.payment_type, - }; - } - case "Lnurl": { - const lnurlRes = res as LnurlPaymentResult; - return { - success: true, - amount_sat: amountSat, - destination: lnurlRes.lnurl, - preimage: lnurlRes.preimage, - type: res.payment_type, - }; - } - case "Bolt11": { - const bolt11Res = res as Bolt11PaymentResult; - return { - success: true, - amount_sat: amountSat, - destination: bolt11Res.bolt11_invoice, - preimage: bolt11Res.preimage, - type: res.payment_type, - }; - } - default: - log.e("Could not process the transaction result. Unknown result type:", [result]); - showAlert({ - title: "Error", - description: "Could not process the transaction result. Unknown result type.", - }); - return { - success: false, - amount_sat: 0, - destination: "", - type: "error", - }; + const processResult = (res: PaymentResult): DisplayResult => { + // Check for onchain payment (has txid and destination_address) + if ("txid" in res && "destination_address" in res) { + return { + success: true, + amount_sat: res.amount_sat, + destination: res.destination_address, + txid: res.txid, + type: "Onchain", + }; + } + + // Check for arkoor payment (has destination_pubkey) + if ("destination_pubkey" in res) { + return { + success: true, + amount_sat: res.amount_sat, + destination: res.destination_pubkey, + type: "Arkoor", + }; } + + // Check for lightning payment (has invoice) + if ("invoice" in res) { + return { + success: true, + amount_sat: res.amount, + destination: res.invoice, + preimage: res.preimage ?? undefined, + type: "Lightning", + }; + } + + // Unknown type + log.e("Could not process the transaction result. Unknown result type:", [result]); + showAlert({ + title: "Error", + description: "Could not process the transaction result. Unknown result type.", + }); + return { + success: false, + amount_sat: 0, + destination: "", + type: "error", + }; }; displayResult = processResult(result); @@ -243,6 +230,9 @@ export const useSendScreen = () => { } else if (selectedPaymentMethod === "lightning" && bip321Data.lightningInvoice) { destinationToSend = bip321Data.lightningInvoice; newDestinationType = "lightning"; + } else if (selectedPaymentMethod === "offer" && bip321Data.offer) { + destinationToSend = bip321Data.offer; + newDestinationType = "offer"; } else if (selectedPaymentMethod === "onchain" && bip321Data.onchainAddress) { destinationToSend = bip321Data.onchainAddress; newDestinationType = "onchain"; @@ -258,7 +248,11 @@ export const useSendScreen = () => { send({ destination: destinationToSend, - amountSat: newDestinationType === "lightning" && !isAmountEditable ? undefined : amountSat, + amountSat: + (newDestinationType === "lightning" || newDestinationType === "offer") && + !isAmountEditable + ? undefined + : amountSat, resolvedAmountSat: amountSat, comment: comment || null, btcPrice, diff --git a/client/src/lib/balanceUtils.ts b/client/src/lib/balanceUtils.ts index 8ae5d4c..21b23e4 100644 --- a/client/src/lib/balanceUtils.ts +++ b/client/src/lib/balanceUtils.ts @@ -31,7 +31,6 @@ export function calculateOffchainBalance(offchain: OffchainBalanceResult): numbe return ( (offchain.pending_exit ?? 0) + (offchain.pending_lightning_send ?? 0) + - (offchain.pending_lightning_receive?.claimable ?? 0) + (offchain.pending_in_round ?? 0) + (offchain.spendable ?? 0) + (offchain.pending_board ?? 0) diff --git a/client/src/lib/paymentsApi.ts b/client/src/lib/paymentsApi.ts index 74ca27d..46fdf76 100644 --- a/client/src/lib/paymentsApi.ts +++ b/client/src/lib/paymentsApi.ts @@ -5,16 +5,17 @@ import { offboardAll as offboardAllNitro, sendArkoorPayment as sendArkoorPaymentNitro, payLightningAddress as payLightningAddressNitro, + payLightningOffer as payLightningOfferNitro, + checkLightningPayment as checkLightningPaymentNitro, bolt11Invoice as bolt11InvoiceNitro, type ArkoorPaymentResult, type OnchainPaymentResult, - type Bolt11PaymentResult, - type LnurlPaymentResult, + type LightningSendResult, newAddress as newAddressNitro, onchainAddress as onchainAddressNitro, payLightningInvoice as payLightningInvoiceNitro, onchainSend as onchainSendNitro, - movements as movementsNitro, + history as historyNitro, tryClaimAllLightningReceives as tryClaimAllLightningReceivesNitro, tryClaimLightningReceive as tryClaimLightningReceiveNitro, peakAddress as peakAddressNitro, @@ -23,16 +24,13 @@ import { Bolt11Invoice, BoardResult, RoundStatus, + BarkVtxo, } from "react-native-nitro-ark"; import { Result, ResultAsync } from "neverthrow"; -export type { ArkoorPaymentResult, OnchainPaymentResult, Bolt11PaymentResult, LnurlPaymentResult }; +export type { ArkoorPaymentResult, OnchainPaymentResult, LightningSendResult }; -export type PaymentResult = - | ArkoorPaymentResult - | OnchainPaymentResult - | Bolt11PaymentResult - | LnurlPaymentResult; +export type PaymentResult = ArkoorPaymentResult | OnchainPaymentResult | LightningSendResult; export const newAddress = async (): Promise> => { return ResultAsync.fromPromise( @@ -116,7 +114,7 @@ export const sendArkoorPayment = async ( export const payLightningInvoice = async ( destination: string, amountSat: number | undefined, -): Promise> => { +): Promise> => { return ResultAsync.fromPromise(payLightningInvoiceNitro(destination, amountSat), (error) => { const e = new Error( `Failed to send bolt11 payment: ${error instanceof Error ? error.message : String(error)}`, @@ -125,6 +123,18 @@ export const payLightningInvoice = async ( }); }; +export const payLightningOffer = async ( + destination: string, + amountSat: number | undefined, +): Promise> => { + return ResultAsync.fromPromise(payLightningOfferNitro(destination, amountSat), (error) => { + const e = new Error( + `Failed to send bolt12 payment: ${error instanceof Error ? error.message : String(error)}`, + ); + return e; + }); +}; + export const onchainSend = async ({ destination, amountSat, @@ -145,7 +155,7 @@ export const payLightningAddress = async ( addr: string, amountSat: number, comment: string, -): Promise> => { +): Promise> => { return ResultAsync.fromPromise(payLightningAddressNitro(addr, amountSat, comment), (error) => { const e = new Error( `Failed to send to lightning address: ${ @@ -157,6 +167,21 @@ export const payLightningAddress = async ( }); }; +export const checkLightningPayment = async ( + paymentHash: string, + wait: boolean = false, +): Promise> => { + return ResultAsync.fromPromise(checkLightningPaymentNitro(paymentHash, wait), (error) => { + const e = new Error( + `Failed to check lightning payment: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + + return e; + }); +}; + export const syncPendingBoards = async (): Promise> => { return ResultAsync.fromPromise(syncPendingBoardsNitro(), (error) => { const e = new Error( @@ -167,8 +192,8 @@ export const syncPendingBoards = async (): Promise> => { }); }; -export const movements = async (): Promise> => { - return ResultAsync.fromPromise(movementsNitro(), (error) => { +export const history = async (): Promise> => { + return ResultAsync.fromPromise(historyNitro(), (error) => { const e = new Error( `Failed to get movements: ${error instanceof Error ? error.message : String(error)}`, ); @@ -180,7 +205,7 @@ export const movements = async (): Promise> => { export const tryClaimLightningReceive = async ( paymentHash: string, wait: boolean = false, -): Promise> => { +): Promise> => { return ResultAsync.fromPromise(tryClaimLightningReceiveNitro(paymentHash, wait), (error) => { const e = new Error( `Failed to check and claim lightning receive: ${error instanceof Error ? error.message : String(error)}`, diff --git a/client/src/lib/pushNotifications.ts b/client/src/lib/pushNotifications.ts index c8d15bb..ba2c37b 100644 --- a/client/src/lib/pushNotifications.ts +++ b/client/src/lib/pushNotifications.ts @@ -148,7 +148,7 @@ TaskManager.defineTask( true, ); - if (claimResult.isOk()) { + if (claimResult.isOk() && claimResult.value && claimResult.value.length > 0) { const sats = notificationData.amount / 1000; await Notifications.scheduleNotificationAsync({ content: { diff --git a/client/src/lib/sendUtils.ts b/client/src/lib/sendUtils.ts index 0bf3dfd..24faeed 100644 --- a/client/src/lib/sendUtils.ts +++ b/client/src/lib/sendUtils.ts @@ -12,12 +12,20 @@ import { isNetworkMatch } from "./utils"; const log = logger("sendUtils"); -export type DestinationTypes = "onchain" | "lightning" | "ark" | "lnurl" | "bip321" | null; +export type DestinationTypes = + | "onchain" + | "lightning" + | "ark" + | "lnurl" + | "bip321" + | "offer" + | null; export type ParsedBip321 = { onchainAddress?: string; arkAddress?: string; lightningInvoice?: string; + offer?: string; }; export type ParsedDestination = { @@ -106,6 +114,9 @@ export const parseBip321Uri = (uri: string): ParsedDestination => { case "lightning": bip321.lightningInvoice = method.value; break; + case "offer": + bip321.offer = method.value; + break; } } diff --git a/client/src/lib/syncTransactions.ts b/client/src/lib/syncTransactions.ts index 69bd873..8fbd96c 100644 --- a/client/src/lib/syncTransactions.ts +++ b/client/src/lib/syncTransactions.ts @@ -4,7 +4,7 @@ import type { Transaction } from "../types/transaction"; import uuid from "react-native-uuid"; import { getHistoricalBtcToUsdRate } from "~/hooks/useMarketData"; import logger from "~/lib/log"; -import { movements } from "./paymentsApi"; +import { history } from "./paymentsApi"; import type { BarkMovement as NitroBarkMovement, MovementStatus } from "react-native-nitro-ark"; import type { MovementKind } from "~/types/movement"; import { INCOMING_MOVEMENT_KINDS } from "~/types/movement"; @@ -27,7 +27,7 @@ const SUBSYSTEM_KIND_TO_MOVEMENT_KIND: Record = { const INCOMING_MOVEMENT_KIND_SET = new Set(INCOMING_MOVEMENT_KINDS); export const syncArkReceives = async () => { - const movementsResult = await movements(); + const movementsResult = await history(); if (movementsResult.isErr()) { log.e("Failed to fetch movements:", [movementsResult.error]); diff --git a/client/src/screens/BackupSettingsScreen.tsx b/client/src/screens/BackupSettingsScreen.tsx index b20efcb..0d5d05a 100644 --- a/client/src/screens/BackupSettingsScreen.tsx +++ b/client/src/screens/BackupSettingsScreen.tsx @@ -13,6 +13,7 @@ import { CheckCircle } from "lucide-react-native"; import { NoahActivityIndicator } from "../components/ui/NoahActivityIndicator"; import { NoahButton } from "~/components/ui/NoahButton"; import * as Haptics from "expo-haptics"; +import { AlertCircle } from "lucide-react-native"; export const BackupSettingsScreen = () => { const navigation = useNavigation(); @@ -29,6 +30,8 @@ export const BackupSettingsScreen = () => { const [showBackups, setShowBackups] = useState(false); const [showSuccessAlert, setShowSuccessAlert] = useState(false); + const [showErrorAlert, setShowErrorAlert] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); return ( @@ -56,6 +59,13 @@ export const BackupSettingsScreen = () => { )} + {showErrorAlert && ( + + Backup Failed + {errorMessage ?? "An unknown error occurred"} + + )} + { const result = await triggerBackup(); @@ -65,6 +75,9 @@ export const BackupSettingsScreen = () => { setTimeout(() => setShowSuccessAlert(false), 3000); } else { await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + setErrorMessage(result.error.message); + setShowErrorAlert(true); + setTimeout(() => setShowErrorAlert(false), 5000); } }} className="mb-4" @@ -80,6 +93,11 @@ export const BackupSettingsScreen = () => { const result = await listBackups(); if (result.isOk()) { setShowBackups(true); + } else { + await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + setErrorMessage(result.error.message); + setShowErrorAlert(true); + setTimeout(() => setShowErrorAlert(false), 5000); } }} className="mb-8 border-border" diff --git a/client/src/screens/DebugScreen.tsx b/client/src/screens/DebugScreen.tsx index bf295ec..545842b 100644 --- a/client/src/screens/DebugScreen.tsx +++ b/client/src/screens/DebugScreen.tsx @@ -203,9 +203,9 @@ const DebugScreen = () => { {resultMessage && ( - {resultMessage} + {resultMessage} {copied ? "Copied!" : "Long press to copy"} diff --git a/client/src/screens/HomeScreen.tsx b/client/src/screens/HomeScreen.tsx index 2e0910b..6498665 100644 --- a/client/src/screens/HomeScreen.tsx +++ b/client/src/screens/HomeScreen.tsx @@ -258,15 +258,6 @@ const HomeScreen = () => { {formatBip177(balance?.offchain.pending_lightning_send ?? 0)} - - Pending Receive - - {formatBip177( - balance?.offchain.pending_lightning_receive.claimable ?? 0, - )} - - - Pending In Round {formatBip177(balance?.offchain.pending_in_round ?? 0)} diff --git a/client/src/types/transaction.ts b/client/src/types/transaction.ts index 0ce9d22..6a1fad9 100644 --- a/client/src/types/transaction.ts +++ b/client/src/types/transaction.ts @@ -1,4 +1,6 @@ -import type { MovementStatus, PaymentTypes } from "react-native-nitro-ark"; +import type { MovementStatus } from "react-native-nitro-ark"; + +export type PaymentTypes = "Bolt11" | "Bolt12" | "Lnurl" | "Arkoor" | "Onchain"; import type { MovementKind } from "./movement"; export type Transaction = { @@ -28,8 +30,6 @@ export type Transaction = { exitedVtxos?: string[]; }; -export type { PaymentTypes }; - export type MovementDestination = { destination: string; amount_sat: number; diff --git a/scripts/captaind.toml b/scripts/captaind.toml index e2e7787..5e9ef95 100644 --- a/scripts/captaind.toml +++ b/scripts/captaind.toml @@ -46,6 +46,15 @@ htlc_send_expiry_delta = 258 # invoice generation with. max_user_invoice_cltv_delta = 250 +# The duration after which a generated invoice will expire. +invoice_expiry = "10m" + +# The duration for which the server will hold inbound HTLC(s) while +# waiting for a user to claim a lightning receive. +# After this timeout the server will fail the HTLC(s) back to the sender and +# also cancel the hold invoice. +receive_htlc_forward_timeout = "30s" + # Interval to retry connecting to disconnected CLN nodes cln_reconnect_interval = "10s" @@ -64,9 +73,6 @@ invoice_check_max_delay = "10m" # FIXME invoice_poll_interval = "30s" -# Timeout on the HTLC subscription RPC call -htlc_subscription_timeout = "10m" - ln_receive_anti_dos_required = false ################ diff --git a/scripts/docker-compose.yml b/scripts/docker-compose.yml index 2c5bb03..5611d15 100644 --- a/scripts/docker-compose.yml +++ b/scripts/docker-compose.yml @@ -73,7 +73,7 @@ services: condition: service_healthy captaind: - image: niteshbalusu/captaind:nightly-2025-11-21 + image: niteshbalusu/captaind:nightly-2025-12-24 volumes: - captaind:/data/captaind - ./captaind.toml:/data/captaind/captaind.toml @@ -90,7 +90,7 @@ services: condition: service_healthy bark: - image: niteshbalusu/bark:nightly-2025-11-21 + image: niteshbalusu/bark:nightly-2025-12-24 volumes: - bark:/root depends_on: diff --git a/server/Cargo.toml b/server/Cargo.toml index 633c213..3416361 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -23,7 +23,7 @@ tokio-cron-scheduler = { version = "0.15.1", features = ["english"] } serde_json = "1.0.145" random_word = { version = "0.5.0", features = ["en"] } futures-util = "0.3.30" -bark-server-rpc = { git = "https://gitlab.com/ark-bitcoin/bark", tag = "server-0.1.0-beta.2" } +bark-server-rpc = { git = "https://gitlab.com/ark-bitcoin/bark", tag = "server-0.1.0-beta.5" } deadpool-redis = { version = "0.22.0", features = ["serde", "rt_tokio_1"] } redis = { version = "0.32.7", features = ["aio", "tokio-comp"] } diff --git a/server/src/ark_client.rs b/server/src/ark_client.rs index 8c62567..fb5cc02 100644 --- a/server/src/ark_client.rs +++ b/server/src/ark_client.rs @@ -103,7 +103,7 @@ async fn establish_connection_and_process( while let Some(item) = stream.next().await { match item { Ok(round_event) => { - if let Some(round_event::Event::Start(event)) = round_event.event { + if let Some(round_event::Event::Attempt(event)) = round_event.event { round_counter += 1; // Handle offboarding requests for every round @@ -118,7 +118,7 @@ async fn establish_connection_and_process( service = "ark_client", event = "maintenance_triggered", round_seq = event.round_seq, - offboard_feerate = event.offboard_feerate_sat_vkb, + round_attempt_challenge = %event.round_attempt_challenge.to_lower_hex_string(), "triggering maintenance" ); let app_state_clone = app_state.clone();