Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function OAuthLogin() {

`string`

**Required.** The URI the OAuth flow redirects back to after sign-in. Either a custom-scheme deep link (e.g. `Linking.createURL('oauth-callback')`) or an [App Link](/wallets/react-native/google-oauth#using-an-app-link-as-the-redirect) on your verified domain (e.g. `https://<your domain>/oauth-callback`). The URL must be allowlisted on the [ZeroDev Dashboard](https://dashboard.zerodev.app/), and your app must have a route that catches it.
**Required.** The URI the OAuth flow redirects back to after sign-in. Either a custom-scheme deep link (e.g. `Linking.createURL('oauth-callback')`) or a [verified `https` link](/wallets/react-native/google-oauth#using-a-verified-https-link-as-the-redirect) on your domain (e.g. `https://<your domain>/oauth-callback` — delivered as an App Link on Android and via the auth session's `https` callback on iOS 17.4+). The URL must be allowlisted on the [ZeroDev Dashboard](https://dashboard.zerodev.app/), and your app must have a route that catches it.

## Mutation Parameters

Expand Down
115 changes: 99 additions & 16 deletions docs/pages/wallets/react-native/domain-association.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
:::

:::warning[Example setup — for development only]
This guide continues from the Expo starter project set up in the [quickstart](/wallets/react-native/quickstart), and shows an example setup for development. The committed debug keystore and the throwaway Vercel deployment are not suitable for production — a production app signs with a securely managed release key (e.g. [EAS credentials](https://docs.expo.dev/app-signing/app-credentials/); with [Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756), `assetlinks.json` must list Google's app-signing certificate fingerprint, not your upload key) and serves `assetlinks.json` from your real product domain.
This guide continues from the Expo starter project set up in the [quickstart](/wallets/react-native/quickstart), and shows an example setup for development. The committed debug keystore and the throwaway Vercel deployment are not suitable for production — a production app signs with a securely managed release key (e.g. [EAS credentials](https://docs.expo.dev/app-signing/app-credentials/); with [Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756), `assetlinks.json` must list Google's app-signing certificate fingerprint, not your upload key) and serves the verification files from your real product domain.
:::

Some features require Android to verify that your app and your domain belong together: the installed APK's signing-cert SHA-256 must match what the domain publishes in `/.well-known/assetlinks.json`. ([Expo guide](https://docs.expo.dev/linking/android-app-links/))
Some features require the OS to verify that your app and your domain belong together:

- **Android** checks that the installed APK's signing-cert SHA-256 matches what the domain publishes in `/.well-known/assetlinks.json`. ([Expo guide](https://docs.expo.dev/linking/android-app-links/))
- **iOS** checks that the app's Team ID + bundle identifier appear in the domain's `/.well-known/apple-app-site-association` (AASA) file, and that the app declares the domain in its **Associated Domains** entitlement. ([Expo guide](https://docs.expo.dev/linking/ios-universal-links/))

Set this up once, and it unlocks:

- [Passkeys](/wallets/react-native/passkeys) — WebAuthn requires the `rpId` domain to vouch for your app.
- **App Links** — `https` links on your domain that open the app directly, used by [Magic Link](/wallets/react-native/magic-link) and optionally by the [OAuth redirect](/wallets/react-native/google-oauth#using-an-app-link-as-the-redirect).
- **Verified `https` redirects** — links on your domain that return into the app, used by [Magic Link](/wallets/react-native/magic-link) (App Links / Universal Links) and optionally by the [OAuth redirect](/wallets/react-native/google-oauth#using-a-verified-https-link-as-the-redirect).

That means: a stable signing keystore, a hosted `assetlinks.json`, and an `rpId` pointing at that domain.
That means: a stable Android signing keystore, the Associated Domains entitlement on iOS, two hosted verification files, and an `rpId` pointing at the domain that serves them.

## 1. Sign every build with the same keystore
## 1. Android: sign every build with the same keystore

Android only trusts the association if the installed APK's signing cert matches the fingerprint your domain publishes. Two defaults get in the way:

Expand Down Expand Up @@ -92,23 +95,56 @@ Register it in `app.json` under `plugins`:

> The plugin takes effect when the native project is regenerated — run `npx expo prebuild --clean`, or it happens automatically on the next `npx expo run:android` if the `android/` directory doesn't exist yet.

## 2. Extract the SHA-256 fingerprint
### Extract the SHA-256 fingerprint

```sh
keytool -list -v \
-keystore ./debug.keystore \
-alias androiddebugkey -storepass android -keypass android
```

Copy the line under `Certificate fingerprints` starting with `SHA256:`.
Copy the line under `Certificate fingerprints` starting with `SHA256:` — it goes into `assetlinks.json` below.

## 2. iOS: add the Associated Domains entitlement

:::warning[Paid Apple Developer account required]
Associated Domains is a paid-tier entitlement — it only works with a paid [Apple Developer Program](https://developer.apple.com/programs/) membership. With a free personal team the app still builds and installs, but the entitlement is silently dropped from the provisioning profile, so the AASA file is never fetched and passkeys / Universal Links fail with opaque errors.
:::

Grab your **Team ID** from the [Apple Developer membership page](https://developer.apple.com/account), then declare it and the domain in `app.json`:

```jsonc
{
"expo": {
"ios": {
"bundleIdentifier": "<your bundle id>",
"appleTeamId": "<your team id>", // [!code ++]
"associatedDomains": [ // [!code ++]
"webcredentials:<your domain>", // [!code ++]
"applinks:<your domain>?mode=developer" // [!code ++]
] // [!code ++]
},
},
}
```

- `webcredentials:` is the entry [passkeys](/wallets/react-native/passkeys) check; `applinks:` is the one Universal Links ([Magic Link](/wallets/react-native/magic-link)) check.
- `?mode=developer` makes development builds fetch the AASA file directly from your origin instead of Apple's CDN, which can cache a stale copy for up to ~24h after you deploy. App Store builds strip the flag, so production traffic still goes through the CDN.
- `appleTeamId` sets the development team for code signing in the generated Xcode project, so `npx expo run:ios` can sign without opening Xcode.

> Associated Domains is a **build-time** entitlement, not runtime config. After adding or changing an entry, regenerate the native project and rebuild — `npx expo prebuild --clean`, then `npx expo run:ios`. Re-running against an already-built binary won't pick it up.

## 3. Create and host `assetlinks.json`
## 3. Create and host the verification files

Create a folder for the two `/.well-known/` files:

```sh
mkdir -p assetlinks/public/.well-known && touch ./assetlinks/public/.well-known/assetlinks.json
mkdir -p assetlinks/public/.well-known
```

Paste this in, with your package name (from `app.json` → `android.package`) and the fingerprint from the previous step:
### `assetlinks.json` (Android)

Create `assetlinks/public/.well-known/assetlinks.json` with your package name (from `app.json` → `android.package`) and the SHA-256 fingerprint extracted in step 1:

```json
[
Expand All @@ -126,7 +162,44 @@ Paste this in, with your package name (from `app.json` → `android.package`) an
]
```

Host it on Vercel:
### `apple-app-site-association` (iOS)

Create `assetlinks/public/.well-known/apple-app-site-association` (no file extension) with your Team ID and bundle identifier:

```json
{
"applinks": {
"details": [
{
"appIDs": ["<your team id>.<your bundle id>"],
"components": [{ "/": "/verify-email*" }]
}
]
},
"webcredentials": {
"apps": ["<your team id>.<your bundle id>"]
}
}
```

- `webcredentials` is what passkeys check.
- `applinks.details[].components` lists the `https` paths that should open your app — `/verify-email*` is the one the [Magic Link](/wallets/react-native/magic-link) guide uses (the trailing `*` also matches the `?code=...` query string). Don't claim paths your app doesn't handle: every Safari navigation to a claimed URL gets intercepted by your app.

Apple requires the extension-less AASA file to be served as JSON, so pin its `Content-Type` with an `assetlinks/vercel.json`:

```json
{
"outputDirectory": "public",
"headers": [
{
"source": "/.well-known/apple-app-site-association",
"headers": [{ "key": "Content-Type", "value": "application/json" }]
}
]
}
```

### Host on Vercel

:::code-group

Expand All @@ -152,13 +225,23 @@ bunx vercel

:::

Then check `https://<vercel_project_name>.vercel.app/.well-known/assetlinks.json` to confirm it deployed correctly.
Then verify both files deployed correctly:

```sh
curl -i https://<vercel_project_name>.vercel.app/.well-known/assetlinks.json
curl -i https://<vercel_project_name>.vercel.app/.well-known/apple-app-site-association
# expect 200 + application/json for both, with no redirects
```

iOS devices don't fetch the AASA from your origin — they go through **Apple's CDN** (unless the `?mode=developer` flag from step 2 is active). Check what the CDN sees:

```sh
curl https://app-site-association.cdn-apple.com/a/v1/<vercel_project_name>.vercel.app
```

If the CDN payload is stale after a deploy, development builds with `?mode=developer` bypass it; alternatively, toggle **Settings → Developer → Universal Links → Associated Domains Development** on the test device (the Developer menu appears once the device has been connected to Xcode).

## 4. Point the SDK at the domain

- Change `RP_ID` in `wagmi.config.ts` to the deployed domain (no scheme): `<vercel_project_name>.vercel.app`.
- If you specify an Access Control List of whitelisted Origins on the [ZeroDev Dashboard](https://dashboard.zerodev.app/), add `https://<vercel_project_name>.vercel.app/` to the allowlist.

:::info[On iOS]
The same association goes through `ios.associatedDomains` (`webcredentials:<domain>` for passkeys, `applinks:<domain>` for Universal Links), and that entitlement only works with a paid [Apple Developer](https://developer.apple.com/programs/) membership — see Expo's [iOS Universal Links guide](https://docs.expo.dev/linking/ios-universal-links/).
:::
8 changes: 4 additions & 4 deletions docs/pages/wallets/react-native/export-wallet.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ It needs `react-native-webview` and `uuid` (native module — rebuild the dev cl

```bash [npm]
npx expo install react-native-webview uuid
npx expo run:android
npx expo run:android # or: npx expo run:ios
```

```bash [yarn]
yarn expo install react-native-webview uuid
yarn expo run:android
yarn expo run:android # or: yarn expo run:ios
```

```bash [pnpm]
pnpm expo install react-native-webview uuid
pnpm expo run:android
pnpm expo run:android # or: pnpm expo run:ios
```

```bash [bun]
bunx expo install react-native-webview uuid
bunx expo run:android
bunx expo run:android # or: bunx expo run:ios
```

:::
Expand Down
46 changes: 41 additions & 5 deletions docs/pages/wallets/react-native/google-oauth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
**USE FOR INTERNAL TESTING PURPOSES ONLY.** You may use these features solely for internal evaluation purposes on supported testnets. DO NOT use for production use or share with your users. Wallets created during this preview ("Alpha Wallets") will be discontinued. Any tokens remaining within Alpha Wallets will be permanently lost upon discontinuance. Any mainnet tokens sent to an Alpha Wallet will not be deposited and will be permanently lost when discontinued. We are unable to help recover any lost funds from Alpha Wallets. We provide all previews on an "as is" basis without warranty of any kind, and we may terminate or suspend the availability of any preview at any time.
:::

On the web, [Google OAuth](/wallets/auth/google-oauth) uses a popup. Native apps don't have popups — instead, the SDK opens the system auth browser and returns to your app via a deep link. The [`useAuthenticateOAuthWithExpoWebBrowser`](/wallets/hooks/use-authenticate-oauth-with-expo-web-browser) hook handles both ends of that round trip.
On the web, [Google OAuth](/wallets/auth/google-oauth) uses a popup. Native apps don't have popups — instead, the SDK opens the system auth browser and returns to your app via a deep link. The [`useAuthenticateOAuthWithExpoWebBrowser`](/wallets/hooks/use-authenticate-oauth-with-expo-web-browser) hook handles both ends of that round trip on both platforms: the auth session (`ASWebAuthenticationSession` on iOS, Custom Tabs on Android) intercepts the custom-scheme redirect itself, so no domain setup is needed.

:::info
This flow requires an [Expo development build](https://docs.expo.dev/develop/development-builds/expo-go-to-dev-build/) — see the [quickstart](/wallets/react-native/quickstart#5-switch-to-an-expo-development-build).
Expand Down Expand Up @@ -106,9 +106,16 @@ This route is for the **native** OAuth flow. If the same Expo app also runs on w

On the [ZeroDev Dashboard](https://dashboard.zerodev.app/), **allowlist the redirect URL**. After a successful sign-in the browser redirects back into the app.

## Using an App Link as the redirect
## Using a verified `https` link as the redirect

Once the [Domain Association](/wallets/react-native/domain-association) is set up on your domain, you can use an `https` **App Link** instead of the custom scheme — `https://<your domain>/...` links then open the app directly ([Expo guide](https://docs.expo.dev/linking/android-app-links/)).
Once the [Domain Association](/wallets/react-native/domain-association) is set up on your domain, you can use a verified `https` link (`https://<your domain>/oauth-callback`) instead of the custom scheme.

The two platforms deliver this redirect through different mechanisms:

- **Android** delivers it as a verified **App Link**: the OS hands the redirect to your app as an intent ([Expo guide](https://docs.expo.dev/linking/android-app-links/)). Requires an intent filter (below).
- **iOS 17.4+** resolves it inside the auth session itself: `ASWebAuthenticationSession` observes the redirect to your domain and returns it to the SDK. This is *not* Universal Link routing — iOS only opens Universal Links from a user tap, never from the server-side redirect that ends an OAuth flow — so the AASA doesn't need to claim the `/oauth-callback` path. What it does require is the `webcredentials:<your domain>` entitlement (the same one passkeys use) from the [Domain Association](/wallets/react-native/domain-association#2-ios-add-the-associated-domains-entitlement) setup.

### Android: add an intent filter

Add an `intentFilter` for `/oauth-callback` in `app.json`:

Expand Down Expand Up @@ -137,7 +144,15 @@ Add an `intentFilter` for `/oauth-callback` in `app.json`:

> After `app.json` changes, regenerate the native project: `npx expo prebuild --clean`, then rebuild. Intent filters land in `AndroidManifest.xml` and App Link verification happens at install time.

Then use the App Link as the redirect:
### iOS: check the requirements

No OAuth-specific setup beyond the [Domain Association](/wallets/react-native/domain-association) — but three things must hold:

- The build carries the `webcredentials:<your domain>` entitlement (paid Apple Developer team). Entitlement changes only land after `npx expo prebuild --clean` and a rebuild.
- The hosted `apple-app-site-association` lists your `<team id>.<bundle id>` under `webcredentials`. If the entitlement or AASA is missing or stale, the flow strands the user on the redirect page inside the auth sheet — during development, keep the `?mode=developer` flag on the entitlement so the device fetches the AASA straight from your origin instead of Apple's CDN.
- The device runs **iOS 17.4 or later**. Older versions can't observe `https` callbacks in the auth session — fall back to the custom scheme there (snippet below).

### Pass the `https` redirect

```tsx
import { RP_ID } from "@/wagmi.config";
Expand All @@ -147,4 +162,25 @@ const auth = useAuthenticateOAuthWithExpoWebBrowser({
});
```

Since this is a new redirect URL, remember to [allowlist it on the Dashboard](#5-allowlist-the-redirect-url) as well.
The hook detects the `https` redirect and configures the auth session accordingly — no extra option needed on either platform.

To keep older iOS versions working, pick the redirect per platform and version:

```tsx
import * as Linking from "expo-linking";
import { Platform } from "react-native";

import { RP_ID } from "@/wagmi.config";

// iOS can only observe https auth-session callbacks on 17.4+.
const supportsHttpsRedirect =
Platform.OS !== "ios" || Number.parseFloat(String(Platform.Version)) >= 17.4;

const auth = useAuthenticateOAuthWithExpoWebBrowser({
redirectUri: supportsHttpsRedirect
? `https://${RP_ID}/oauth-callback`
: Linking.createURL("oauth-callback"),
});
```

Since these are new redirect URLs, remember to [allowlist them on the Dashboard](#5-allowlist-the-redirect-url) as well.
Loading