Skip to content

Commit 029b87d

Browse files
ZackAttaxmicbakos
andauthored
updates app link to ready wallet app and uses hook to open app (#196)
Co-authored-by: micbakos <michael.b@starkware.co>
1 parent 6877c8a commit 029b87d

6 files changed

Lines changed: 169 additions & 59 deletions

File tree

frontend/app.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import "ts-node/register";
22
import { ExpoConfig } from "expo/config";
3+
import { WalletPresets } from "./constants/WalletPresets";
34

45
const config: ExpoConfig = {
56
name: "POW!",
@@ -18,6 +19,7 @@ const config: ExpoConfig = {
1819
"argentx",
1920
"argentmobile",
2021
"ready",
22+
"ready-wallet",
2123
"braavos",
2224
"braavos-btc-straknet-wallet",
2325
"wc",
@@ -65,6 +67,12 @@ const config: ExpoConfig = {
6567
"expo-web-browser",
6668
"expo-audio",
6769
"./plugins/sentry.ts",
70+
[
71+
"./plugins/android-queries.ts",
72+
{
73+
packageNames: Object.values(WalletPresets).map((p) => p.androidPackage),
74+
},
75+
],
6876
],
6977
experiments: {
7078
typedRoutes: true,

frontend/app/components/claim-reward/SectionTitle.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ const styles = StyleSheet.create({
5151
title: {
5252
color: "#fff7ff",
5353
fontSize: 24,
54-
fontWeight: "700",
5554
position: "absolute",
5655
left: 8,
5756
fontFamily: "Pixels",

frontend/app/hooks/useOpenApp.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// useOpenApp.ts
2+
import { Linking, Platform } from "react-native";
3+
import { AppTarget } from "@/constants/WalletPresets";
4+
5+
async function tryOpenUrls(urls: string[]) {
6+
for (const url of urls) {
7+
try {
8+
const can = await Linking.canOpenURL(url);
9+
if (can) {
10+
await Linking.openURL(url);
11+
return true;
12+
} else {
13+
console.warn("Cannot open url", url);
14+
}
15+
} catch (error) {
16+
console.error(error);
17+
// keep trying fallbacks
18+
}
19+
}
20+
return false;
21+
}
22+
23+
async function openStore(iosId?: string, androidPkg?: string) {
24+
if (Platform.OS === "ios" && iosId) {
25+
return Linking.openURL(`itms-apps://itunes.apple.com/app/id${iosId}`);
26+
}
27+
if (Platform.OS === "android" && androidPkg) {
28+
return Linking.openURL(`market://details?id=${androidPkg}`);
29+
}
30+
return false;
31+
}
32+
33+
/** Hook */
34+
export function useOpenApp() {
35+
const openApp = async (target: AppTarget) => {
36+
const {
37+
iosSchemes,
38+
androidSchemes,
39+
universalLinks,
40+
iosAppStoreId,
41+
androidPackage,
42+
} = target;
43+
44+
// 1) Try app schemes/intent first
45+
const openedApp = await tryOpenUrls(
46+
Platform.OS == "ios" ? iosSchemes : androidSchemes,
47+
);
48+
49+
if (openedApp) return true;
50+
51+
// 2) If not installed, try opening the app store first
52+
try {
53+
const storeRes = await openStore(iosAppStoreId, androidPackage);
54+
if (storeRes !== false) return true;
55+
} catch {
56+
// If store open fails, continue to universal links
57+
}
58+
59+
// 3) As a final fallback, try universal links (web)
60+
if (universalLinks.length > 0) {
61+
const openedWeb = await tryOpenUrls(universalLinks);
62+
if (openedWeb) return true;
63+
}
64+
65+
return false;
66+
};
67+
68+
return { openApp };
69+
}

frontend/app/pages/settings/ClaimReward.tsx

Lines changed: 5 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { WalletButtonRow } from "../../components/claim-reward/WalletButtonRow";
2525
import { LoadingModal } from "../../components/claim-reward/LoadingModal";
2626
import { useEventManager } from "../../stores/useEventManager";
2727
import { InsufficientFundsModal } from "../../components/InsufficientFundsModal";
28+
import { useOpenApp } from "../../hooks/useOpenApp";
29+
import { WalletPresets } from "@/constants/WalletPresets";
2830

2931
// Decode common Starknet u256/BigNumberish shapes using starknet helpers
3032
function decodeU256ToBigInt(raw: any): bigint | null {
@@ -69,6 +71,7 @@ const ClaimRewardSectionComponent: React.FC<ClaimRewardProps> = ({
6971
const { width } = useCachedWindowDimensions();
7072
const { notify } = useEventManager();
7173
const [showInsufficientFunds, setShowInsufficientFunds] = useState(false);
74+
const { openApp } = useOpenApp();
7275

7376
const handleBack = useCallback(() => {
7477
if (onBack) onBack();
@@ -188,62 +191,6 @@ const ClaimRewardSectionComponent: React.FC<ClaimRewardProps> = ({
188191
})();
189192
}, []);
190193

191-
const openReadyWallet = useCallback(async () => {
192-
const scheme = "ready://open";
193-
try {
194-
await Linking.openURL(scheme);
195-
return;
196-
} catch (_e) {
197-
if (__DEV__) {
198-
console.debug("Failed to open READY wallet scheme");
199-
}
200-
}
201-
try {
202-
if (Platform.OS === "ios") {
203-
await Linking.openURL(
204-
"https://apps.apple.com/us/app/ready-earn-on-bitcoin-usdc/id1358741926",
205-
);
206-
} else {
207-
const pkg = "im.argent.contractwalletclient";
208-
const market = `market://details?id=${pkg}`;
209-
const web = `https://play.google.com/store/apps/details?id=${pkg}`;
210-
await Linking.openURL(market).catch(() => Linking.openURL(web));
211-
}
212-
} catch (_e) {
213-
if (__DEV__) {
214-
console.debug("Failed to open READY install link");
215-
}
216-
}
217-
}, []);
218-
219-
const openBraavosWallet = useCallback(async () => {
220-
const scheme = "braavos://open";
221-
try {
222-
await Linking.openURL(scheme);
223-
return;
224-
} catch (_e) {
225-
if (__DEV__) {
226-
console.debug("Failed to open BRAAVOS wallet scheme");
227-
}
228-
}
229-
try {
230-
if (Platform.OS === "ios") {
231-
await Linking.openURL(
232-
"https://apps.apple.com/us/app/braavos-wallet/id1636013523",
233-
);
234-
} else {
235-
const pkg = "app.braavos.wallet";
236-
const market = `market://details?id=${pkg}`;
237-
const web = `https://play.google.com/store/apps/details?id=${pkg}`;
238-
await Linking.openURL(market).catch(() => Linking.openURL(web));
239-
}
240-
} catch (_e) {
241-
if (__DEV__) {
242-
console.debug("Failed to open BRAAVOS install link");
243-
}
244-
}
245-
}, []);
246-
247194
const isValidAddress = /^0x[a-fA-F0-9]{63,64}$/.test(
248195
(debouncedInput || "").trim(),
249196
);
@@ -313,8 +260,8 @@ const ClaimRewardSectionComponent: React.FC<ClaimRewardProps> = ({
313260
<View style={styles.section}>
314261
<View style={styles.card}>
315262
<WalletButtonRow
316-
onPressReady={openReadyWallet}
317-
onPressBraavos={openBraavosWallet}
263+
onPressReady={() => openApp(WalletPresets.readyWallet)}
264+
onPressBraavos={() => openApp(WalletPresets.braavos)}
318265
/>
319266
<Text style={styles.hintInline}>
320267
Open a wallet above and paste an address to receive your STRK.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export type AppTarget = {
2+
iosSchemes: string[]; // e.g., ['braavos://']
3+
androidSchemes: string[]; // e.g., ['braavos://']
4+
universalLinks: string[]; // e.g., ['https://braavos.app/']
5+
iosAppStoreId: string; // numbers only, e.g., '1636013523'
6+
androidPackage: string; // e.g., 'app.braavos.wallet'
7+
};
8+
9+
export const WalletPresets = {
10+
braavos: {
11+
iosSchemes: ["braavos://"],
12+
androidSchemes: ["braavos://"],
13+
universalLinks: ["https://braavos.app/"],
14+
iosAppStoreId: "1636013523",
15+
androidPackage: "app.braavos.wallet",
16+
} satisfies AppTarget,
17+
18+
ready: {
19+
iosSchemes: ["ready://", "argent://"],
20+
androidSchemes: ["ready://", "argent://"],
21+
universalLinks: ["https://www.ready.co/", "https://ready.co/"],
22+
iosAppStoreId: "1358741926",
23+
androidPackage: "im.argent.contractwalletclient",
24+
} satisfies AppTarget,
25+
26+
readyWallet: {
27+
iosSchemes: ["ready-wallet://"],
28+
androidSchemes: ["ready-wallet://"],
29+
universalLinks: ["https://www.ready.co/download-ready-wallet"],
30+
iosAppStoreId: "6744935604",
31+
androidPackage: "com.ready.wallet",
32+
} satisfies AppTarget,
33+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import configPlugins from "@expo/config-plugins";
2+
import {
3+
AndroidManifest,
4+
ConfigPlugin,
5+
ExportedConfigWithProps,
6+
} from "expo/config-plugins";
7+
const { withAndroidManifest } = configPlugins;
8+
9+
// Exported type from expo config;
10+
type PackageQuery = {
11+
$: {
12+
"android:name": string;
13+
};
14+
};
15+
16+
/**
17+
* Adds the packageNames as queries for deep links to wallets to work
18+
* https://developer.android.com/training/package-visibility/declaring#package-name
19+
*
20+
* @param config
21+
* @param packageNames
22+
*/
23+
function setQueries(
24+
config: ExportedConfigWithProps<AndroidManifest>,
25+
packageNames: string[],
26+
): ExportedConfigWithProps<AndroidManifest> {
27+
const packageQueries: PackageQuery[] = [];
28+
29+
for (const packageName of packageNames) {
30+
packageQueries.push({
31+
$: {
32+
"android:name": packageName,
33+
},
34+
});
35+
}
36+
37+
config.modResults.manifest.queries.push({
38+
package: packageQueries,
39+
});
40+
41+
return config;
42+
}
43+
44+
type QueriesProps = {
45+
packageNames: string[];
46+
};
47+
48+
const withAndroidQueries: ConfigPlugin<QueriesProps> = (config, options) => {
49+
return withAndroidManifest(config, async (config) => {
50+
return setQueries(config, options.packageNames);
51+
});
52+
};
53+
54+
export default withAndroidQueries;

0 commit comments

Comments
 (0)