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();