Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 7 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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=="],

Expand Down
4 changes: 2 additions & 2 deletions client/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2976,7 +2976,7 @@ SPEC CHECKSUMS:
FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12
hermes-engine: 8bb0bdb1bf49c3bbc17cf4c2d02dcc2f706ab555
MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df
NitroArk: e29874d62421cb89d89c67e938624fb6c3aabf08
NitroArk: 61ef84ad85ad0ee2edd5ec319373f8f6c165c1c9
NitroMmkv: a7f267ea1359e25072960fc17f8b2c2e023d7bac
NitroModules: b7b51ba7f49acf5c4c13a56f2b0037b5f7bf55a7
NoahTools: efea1336628719c3ab4a565f3c03d2deeadd3d14
Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 10 additions & 1 deletion client/src/components/Bip321Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,6 +71,15 @@ export const Bip321Picker = ({
onSelect={onSelect}
/>
)}
{bip321Data.offer && (
<PaymentOption
method="offer"
icon={<LightningIcon className="w-6 h-6 text-primary" />}
label="Lightning Offer"
isSelected={selectedPaymentMethod === "offer"}
onSelect={onSelect}
/>
)}
{bip321Data.onchainAddress && (
<PaymentOption
method="onchain"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/SendConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface SendConfirmationProps {
comment?: string;
btcPrice?: number;
bip321Data?: ParsedBip321 | null;
selectedPaymentMethod?: "ark" | "lightning" | "onchain";
selectedPaymentMethod?: "ark" | "lightning" | "onchain" | "offer";
onConfirm: () => void;
onCancel: () => void;
isLoading?: boolean;
Expand Down
60 changes: 42 additions & 18 deletions client/src/hooks/usePayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {
sendArkoorPayment,
payLightningInvoice,
payLightningAddress,
payLightningOffer,
checkLightningPayment,
type ArkoorPaymentResult,
type Bolt11PaymentResult,
type LnurlPaymentResult,
type LightningSendResult,
type OnchainPaymentResult,
boardAllArk,
offboardAllArk,
Expand Down Expand Up @@ -163,11 +164,22 @@ type SendVariables = {
btcPrice?: number;
};

type SendResult =
| ArkoorPaymentResult
| Bolt11PaymentResult
| LnurlPaymentResult
| OnchainPaymentResult;
type SendResult = ArkoorPaymentResult | LightningSendResult | OnchainPaymentResult;

const awaitLightningPayment = async (
paymentPromise: Promise<Result<LightningSendResult, Error>>,
): Promise<LightningSendResult> => {
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) {
Expand All @@ -179,6 +191,8 @@ const mapDestinationToPaymentType = (destinationType: DestinationTypes): Payment
return "Lnurl";
case "onchain":
return "Onchain";
case "offer":
return "Bolt12";
default:
return null;
}
Expand Down Expand Up @@ -210,23 +224,32 @@ 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");
}

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<LightningSendResult, Error>),
);
}
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");
}
Expand Down Expand Up @@ -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));
Expand All @@ -302,7 +325,7 @@ async function handleNoahWalletPayment(
destination: string,
amountSat: number,
comment: string | null,
): Promise<Result<ArkoorPaymentResult | Bolt11PaymentResult, Error> | null> {
): Promise<Result<ArkoorPaymentResult | LightningSendResult | OnchainPaymentResult, Error> | null> {
try {
const [user, domain] = destination.split("@");
const lnurlEndpoint = `https://${domain}/.well-known/lnurlp/${user}`;
Expand All @@ -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.",
Expand Down
Loading