diff --git a/components/CreateInvoice.tsx b/components/CreateInvoice.tsx
index 38afa1b..56983f5 100644
--- a/components/CreateInvoice.tsx
+++ b/components/CreateInvoice.tsx
@@ -1,9 +1,7 @@
import type { Nip47Transaction } from "@getalby/sdk";
-import * as Clipboard from "expo-clipboard";
import { router } from "expo-router";
import React from "react";
import { Platform, Share, View } from "react-native";
-import Toast from "react-native-toast-message";
import { DualCurrencyInput } from "~/components/DualCurrencyInput";
import { CopyIcon, ShareIcon } from "~/components/Icons";
import Loading from "~/components/Loading";
@@ -13,7 +11,7 @@ import { Text } from "~/components/ui/text";
import { useGetFiatAmount } from "~/hooks/useGetFiatAmount";
import { errorToast } from "~/lib/errorToast";
import { useAppStore } from "~/lib/state/appStore";
-import { cn, formatBitcoinAmount } from "~/lib/utils";
+import { cn, copyToClipboard, formatBitcoinAmount } from "~/lib/utils";
export function CreateInvoice() {
const getFiatAmount = useGetFiatAmount();
@@ -53,17 +51,13 @@ export function CreateInvoice() {
})();
}
- function copy() {
+ async function copy() {
const text = invoice;
if (!text) {
errorToast(new Error("Nothing to copy"));
return;
}
- Clipboard.setStringAsync(text);
- Toast.show({
- type: "success",
- text1: "Copied to clipboard",
- });
+ await copyToClipboard(text);
}
async function share() {
diff --git a/lib/utils.ts b/lib/utils.ts
index 1f1c675..9045bf3 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -5,8 +5,11 @@ import { sha256 } from "@noble/hashes/sha2.js";
import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils.js";
import { Buffer } from "buffer";
import { clsx, type ClassValue } from "clsx";
+import * as Clipboard from "expo-clipboard";
import { getPublicKey, nip19 } from "nostr-tools";
+import Toast from "react-native-toast-message";
import { twMerge } from "tailwind-merge";
+import { errorToast } from "~/lib/errorToast";
import { BitcoinDisplayFormat } from "~/lib/state/appStore";
export function cn(...inputs: ClassValue[]) {
@@ -39,6 +42,34 @@ export function safeNpubEncode(hex: string): string | undefined {
}
}
+export async function copyToClipboard(
+ text: string,
+ successMessage = "Copied to clipboard",
+) {
+ // Clipboard.setStringAsync always resolves to true in iOS and
+ // android so we don't have to add a catch block for errors
+ await Clipboard.setStringAsync(text);
+ Toast.show({
+ type: "success",
+ text1: successMessage,
+ });
+}
+
+export async function readClipboardText() {
+ try {
+ const text = await Clipboard.getStringAsync();
+ if (!text) {
+ errorToast(new Error("Your clipboard is empty."));
+ return undefined;
+ }
+ return text;
+ } catch (error) {
+ console.error("Failed to read clipboard", error);
+ errorToast(error, "Failed to read clipboard");
+ return undefined;
+ }
+}
+
export function formatBitcoinAmount(
amount: number,
displayFormat: BitcoinDisplayFormat = "bip177",
diff --git a/pages/Transaction.tsx b/pages/Transaction.tsx
index cc45812..23f5cc9 100644
--- a/pages/Transaction.tsx
+++ b/pages/Transaction.tsx
@@ -1,7 +1,6 @@
import type { Nip47Transaction, Nip47TransactionMetadata } from "@getalby/sdk";
import { hexToBytes } from "@noble/hashes/utils.js";
import dayjs from "dayjs";
-import * as Clipboard from "expo-clipboard";
import { Link, useLocalSearchParams } from "expo-router";
import { nip19 } from "nostr-tools";
import React from "react";
@@ -12,7 +11,6 @@ import {
TouchableOpacity,
View,
} from "react-native";
-import Toast from "react-native-toast-message";
import { LinkIcon } from "~/components/Icons";
import AcceptedTransactionIcon from "~/components/icons/AcceptedTransaction";
import FailedTransactionIcon from "~/components/icons/FailedTransaction";
@@ -23,7 +21,12 @@ import Screen from "~/components/Screen";
import { Text } from "~/components/ui/text";
import { useGetFiatAmount } from "~/hooks/useGetFiatAmount";
import { BitcoinDisplayFormat, useAppStore } from "~/lib/state/appStore";
-import { cn, formatBitcoinAmount, safeNpubEncode } from "~/lib/utils";
+import {
+ cn,
+ copyToClipboard,
+ formatBitcoinAmount,
+ safeNpubEncode,
+} from "~/lib/utils";
type TLVRecord = {
type: number;
@@ -308,12 +311,8 @@ function TransactionDetailRow(props: {
{props.title}
{
- Clipboard.setStringAsync(props.content);
- Toast.show({
- type: "success",
- text1: "Copied to clipboard",
- });
+ onPress={async () => {
+ await copyToClipboard(props.content);
}}
>
diff --git a/pages/receive/Receive.tsx b/pages/receive/Receive.tsx
index d3ae824..26c76bb 100644
--- a/pages/receive/Receive.tsx
+++ b/pages/receive/Receive.tsx
@@ -1,8 +1,6 @@
-import * as Clipboard from "expo-clipboard";
import { router } from "expo-router";
import React from "react";
import { Share, TouchableOpacity, View } from "react-native";
-import Toast from "react-native-toast-message";
import { CreateInvoice } from "~/components/CreateInvoice";
import {
AddressIcon,
@@ -18,23 +16,20 @@ import { Button } from "~/components/ui/button";
import { Text } from "~/components/ui/text";
import { errorToast } from "~/lib/errorToast";
import { useAppStore } from "~/lib/state/appStore";
+import { copyToClipboard } from "~/lib/utils";
export function Receive() {
const selectedWalletId = useAppStore((store) => store.selectedWalletId);
const wallets = useAppStore((store) => store.wallets);
const lightningAddress = wallets[selectedWalletId].lightningAddress;
- function copy() {
+ async function copy() {
const text = lightningAddress;
if (!text) {
errorToast(new Error("Nothing to copy"));
return;
}
- Clipboard.setStringAsync(text);
- Toast.show({
- type: "success",
- text1: "Copied to clipboard",
- });
+ await copyToClipboard(text);
}
async function share() {
diff --git a/pages/receive/Withdraw.tsx b/pages/receive/Withdraw.tsx
index f29742f..c24b960 100644
--- a/pages/receive/Withdraw.tsx
+++ b/pages/receive/Withdraw.tsx
@@ -1,4 +1,3 @@
-import * as Clipboard from "expo-clipboard";
import { router, useLocalSearchParams } from "expo-router";
import {
lnurl as lnurlLib,
@@ -16,6 +15,7 @@ import { Text } from "~/components/ui/text";
import { WalletSwitcher } from "~/components/WalletSwitcher";
import { errorToast } from "~/lib/errorToast";
import { useAppStore } from "~/lib/state/appStore";
+import { readClipboardText } from "~/lib/utils";
export function Withdraw() {
const { url } = useLocalSearchParams<{ url: string }>();
@@ -57,14 +57,10 @@ export function Withdraw() {
}, [url]);
async function paste() {
- let clipboardText;
- try {
- clipboardText = await Clipboard.getStringAsync();
- } catch (error) {
- console.error("Failed to read clipboard", error);
- return;
+ const clipboardText = await readClipboardText();
+ if (clipboardText) {
+ loadWithdrawal(clipboardText);
}
- loadWithdrawal(clipboardText);
}
const handleScanned = (data: string) => {
@@ -72,11 +68,6 @@ export function Withdraw() {
};
async function loadWithdrawal(text: string): Promise {
- if (!text) {
- errorToast(new Error("Your clipboard is empty."));
- return false;
- }
-
text = text.toLowerCase();
console.info("loading withdrawal", text);
diff --git a/pages/send/Address.tsx b/pages/send/Address.tsx
index 8915b77..c07271e 100644
--- a/pages/send/Address.tsx
+++ b/pages/send/Address.tsx
@@ -6,7 +6,6 @@ import {
BottomSheetTextInput,
BottomSheetView,
} from "@gorhom/bottom-sheet";
-import * as Clipboard from "expo-clipboard";
import React, { useCallback, useRef, useState } from "react";
import {
Keyboard,
@@ -27,7 +26,7 @@ import { Text } from "~/components/ui/text";
import { initiatePaymentFlow } from "~/lib/initiatePaymentFlow";
import { useAppStore } from "~/lib/state/appStore";
import { useThemeColor } from "~/lib/useThemeColor";
-import { cn } from "~/lib/utils";
+import { cn, readClipboardText } from "~/lib/utils";
interface ContactInputProps {
lnAddress: string;
@@ -204,14 +203,10 @@ export function Address() {
}, [addressBookEntries, keyboardText]);
const paste = async () => {
- let clipboardText;
- try {
- clipboardText = await Clipboard.getStringAsync();
- } catch (error) {
- console.error("Failed to read clipboard", error);
- return;
+ const clipboardText = await readClipboardText();
+ if (clipboardText) {
+ setKeyboardText(clipboardText);
}
- setKeyboardText(clipboardText);
};
return (
diff --git a/pages/send/Send.tsx b/pages/send/Send.tsx
index b82177f..c435416 100644
--- a/pages/send/Send.tsx
+++ b/pages/send/Send.tsx
@@ -1,6 +1,5 @@
import { NWAClient } from "@getalby/sdk/nwc";
import { Camera } from "expo-camera";
-import * as Clipboard from "expo-clipboard";
import * as ImagePicker from "expo-image-picker";
import { router, useLocalSearchParams } from "expo-router";
import React from "react";
@@ -13,6 +12,7 @@ import { Button } from "~/components/ui/button";
import { Text } from "~/components/ui/text";
import { errorToast } from "~/lib/errorToast";
import { initiatePaymentFlow } from "~/lib/initiatePaymentFlow";
+import { readClipboardText } from "~/lib/utils";
export function Send() {
const { url, amount } = useLocalSearchParams<{
@@ -63,14 +63,10 @@ export function Send() {
}
async function paste() {
- let clipboardText;
- try {
- clipboardText = await Clipboard.getStringAsync();
- } catch (error) {
- console.error("Failed to read clipboard", error);
- return;
+ const clipboardText = await readClipboardText();
+ if (clipboardText) {
+ await loadPayment(clipboardText);
}
- await loadPayment(clipboardText);
}
const handleScanned = async (data: string) => {
@@ -78,11 +74,6 @@ export function Send() {
};
const loadPayment = async (text: string, amount = "") => {
- if (!text) {
- errorToast(new Error("Your clipboard is empty."));
- return false;
- }
-
if (text.startsWith("nostr+walletauth") /* can have : or +alby: */) {
const nwaOptions = NWAClient.parseWalletAuthUrl(text);
router.replace({
diff --git a/pages/settings/wallets/EditWallet.tsx b/pages/settings/wallets/EditWallet.tsx
index e3d3501..94f9550 100644
--- a/pages/settings/wallets/EditWallet.tsx
+++ b/pages/settings/wallets/EditWallet.tsx
@@ -1,4 +1,3 @@
-import * as Clipboard from "expo-clipboard";
import { Link, router, useLocalSearchParams } from "expo-router";
import React, { useState } from "react";
import {
@@ -25,7 +24,7 @@ import { Text } from "~/components/ui/text";
import { IS_EXPO_GO, REQUIRED_CAPABILITIES } from "~/lib/constants";
import { deregisterWalletNotifications } from "~/lib/notifications";
import { useAppStore } from "~/lib/state/appStore";
-import { cn } from "~/lib/utils";
+import { cn, copyToClipboard } from "~/lib/utils";
export function EditWallet() {
const { id } = useLocalSearchParams() as { id: string };
@@ -171,7 +170,7 @@ export function EditWallet() {
},
{
text: "Confirm",
- onPress: () => {
+ onPress: async () => {
const isSuperuser = useAppStore
.getState()
.wallets[
@@ -195,11 +194,10 @@ export function EditWallet() {
if (!nwcUrl) {
return;
}
- Clipboard.setStringAsync(nwcUrl);
- Toast.show({
- type: "success",
- text1: "Connection Secret copied to clipboard",
- });
+ await copyToClipboard(
+ nwcUrl,
+ "Connection Secret copied to clipboard",
+ );
},
},
],
diff --git a/pages/settings/wallets/SetupWallet.tsx b/pages/settings/wallets/SetupWallet.tsx
index 04f9545..579fe5d 100644
--- a/pages/settings/wallets/SetupWallet.tsx
+++ b/pages/settings/wallets/SetupWallet.tsx
@@ -1,5 +1,4 @@
import { NWCClient, type Nip47Capability } from "@getalby/sdk/nwc";
-import * as Clipboard from "expo-clipboard";
import { router, useLocalSearchParams } from "expo-router";
import { useAppStore } from "lib/state/appStore";
import React from "react";
@@ -23,7 +22,7 @@ import { Text } from "~/components/ui/text";
import { IS_EXPO_GO, REQUIRED_CAPABILITIES } from "~/lib/constants";
import { errorToast } from "~/lib/errorToast";
import { registerWalletNotifications } from "~/lib/notifications";
-import { cn } from "~/lib/utils";
+import { cn, readClipboardText } from "~/lib/utils";
export function SetupWallet() {
const { nwcUrl: nwcUrlFromSchemeLink } = useLocalSearchParams<{
@@ -48,15 +47,10 @@ export function SetupWallet() {
};
async function paste() {
- let nostrWalletConnectUrl;
- try {
- nostrWalletConnectUrl = await Clipboard.getStringAsync();
- } catch (error) {
- console.error("Failed to read clipboard", error);
- errorToast(error, "Failed to read clipboard");
- return;
+ const nostrWalletConnectUrl = await readClipboardText();
+ if (nostrWalletConnectUrl) {
+ connect(nostrWalletConnectUrl);
}
- connect(nostrWalletConnectUrl);
}
const connect = React.useCallback(