> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Connections on iOS

> Present the hosted Connections page in an ASWebAuthenticationSession, or render your own native wallet list with the headless engine.

<Note>
  This is an enterprise-only feature. Please [contact us](https://www.dynamic.xyz/book-a-call) to enable.
</Note>

On iOS the [Connections](/docs/connections/overview) contract is the same as the web, hosted inside a web view. The result comes back on a custom URL scheme instead of an `https` callback.

You have two options:

* **Basic** — present the hosted page and read the result. One Swift file to copy.
* **Headless** — render your own native wallet list, driven by a hidden web view. Your app links no wallet SDK.

The basic flow is the recommended default. Use headless only when you need a fully native front end.

## Basic

The whole integration is one small Swift file, [`FireblocksConnectFlow.swift`](https://github.com/dynamic-labs-oss/iframe-fb/blob/main/ios-harness/Sources/FireblocksConnectFlow.swift) — copy it, call it, read the result.

<Tip>
  Present with `ASWebAuthenticationSession`: it's built for "open web, return via a callback scheme," runs ephemerally (no consent prompt, no stale-session lag), and needs no navigation glue. Use a `WKWebView` only if you need the flow embedded in custom UI.
</Tip>

### 1. Register your URL scheme

Add your app's custom scheme to `Info.plist`. It only has to match the scheme you pass to the flow.

```xml Info.plist theme={"system"}
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array><string>myapp</string></array>
  </dict>
</array>
```

### 2. Present the flow

Call `FireblocksConnectFlow.present`. It appends `redirect_uri`, a random `nonce`, and `embedded=1` to the URL, opens the session, verifies the returned nonce, and hands you a typed result.

```swift ConnectButton.swift theme={"system"}
FireblocksConnectFlow.present(
    flowURL: URL(string: "https://your-connect-page.example/")!,
    scheme: "myapp"
) { result in
    switch result {
    case .success(let wallet):
        // wallet.address, wallet.chain,
        // wallet.walletName, wallet.walletImage
    case .failure(.cancelled):
        break
    case .failure(let error):
        // .nonceMismatch / .malformedResult / …
    }
}
```

Forward your app's `onOpenURL` to `FireblocksConnectFlow.handleCallbackURL($0)`. Most wallets return inside the session, but some (Phantom) finish in their own in-app browser and hand the result back via the scheme — this makes those complete.

```swift App.swift theme={"system"}
WindowGroup {
    ContentView()
        .onOpenURL { FireblocksConnectFlow.handleCallbackURL($0) }
}
```

### 3. Use the result

`WalletConnection` carries the same fields as the web callback, with the nonce already verified for you: `address`, `chain` (`"evm" | "solana"`), `walletName`, and `walletImage`.

<Note>
  The `embedded=1` flag tells the page it's inside a native container, so it opens wallets via their native scheme and avoids redirect protocols that would escape to Safari. Render `walletImage` with a WebKit-backed `<img>` rather than `UIImage` — it's usually an SVG-sprite URL.
</Note>

## Headless

Want your app to render its **own native wallet list** with no web UI at all? Keep every bit of connection logic in the web layer and drive it from a hidden web view.

<Note>
  **No SDK in your app.** Your app links no wallet SDK — no CocoaPods, no native crypto. It needs exactly two things: a hidden web view pointed at `headless.html`, and your URL scheme. All the WalletConnect / MetaMask / Phantom logic — and the wallet list itself — comes from that hidden view. When wallets or the SDK change, you redeploy the page; the app never changes.
</Note>

Copy two files: [`FireblocksHeadlessConnect.swift`](https://github.com/dynamic-labs-oss/iframe-fb/blob/main/ios-harness/Sources/FireblocksHeadlessConnect.swift) (the drop-in engine) and [`FireblocksConnectFlow.swift`](https://github.com/dynamic-labs-oss/iframe-fb/blob/main/ios-harness/Sources/FireblocksConnectFlow.swift) (the visible fallback and the shared `WalletConnection` result).

### 1. Get the wallet menu

The engine derives the list live from the Dynamic catalog and pushes it to your app over the bridge (a `wallets` message) — no wallet file to ship or keep in sync. Set `FireblocksHeadlessConnect.shared.onWallets`. Each entry carries:

| Field           | Description                                                                                                                               |                                                 |
| :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `key`           | Catalog key you pass back when the user taps it (e.g. `metamask`).                                                                        |                                                 |
| `name` / `icon` | Display name and icon URL for the row.                                                                                                    |                                                 |
| `chains`        | Which chains the wallet supports (\`("evm"                                                                                                | "solana")\[]\`), driving a native chain picker. |
| `mode`          | `headless` → connect silently through the hidden view. `fallback` → open the visible flow (for wallets that need a passkey/email screen). |                                                 |
| `featured`      | Show by default; the rest of the catalog rides along so search matches this page.                                                         |                                                 |

### 2. Drop in FireblocksHeadlessConnect

It owns a hidden `WKWebView` pointed at `headless.html`, drives it over a message bridge, opens the wallet deeplink it returns, and calls you back. Pre-warm it at launch.

```swift Connect.swift theme={"system"}
FireblocksHeadlessConnect.shared.prewarm()   // at launch

FireblocksHeadlessConnect.shared.connect(walletKey: "metamask", chain: "evm") { result in
    switch result {
    case .success(let wallet):        // wallet.address, .chain, …
    case .fallbackRequired:           // open the visible flow for this wallet
    case .failure(let code, _):       // stable code, e.g. "user_rejected"
    }
}
```

Forward your app's `onOpenURL` to `FireblocksHeadlessConnect.shared.handleReturnURL($0)` — that's how redirect wallets (Phantom) hand their result back to the hidden view.

Auto-fallback is built in: wallets with no headless path (Base Account passkey, and similar) return `.fallbackRequired`, and you open the visible flow for that same wallet, so the user never hits a dead end.

### 3. Sign a message

After a successful connect, call `sign()` with any string. The wallet app prompts the user to approve; the callback delivers a hex signature.

```swift SignView.swift theme={"system"}
FireblocksHeadlessConnect.shared.sign(
    message: "Sign in to MyApp · \(ISO8601DateFormatter().string(from: Date()))"
) { result in
    switch result {
    case .success(let signature):
        // signature — hex string (EVM) or base58 (Solana)
    case .failure(let code, let message):
        // code: "user_rejected" | "timeout" | "unknown" | …
    }
}
```

Signing is only available for wallets connected through the headless engine (`connectedHeadlessly == true`). Wallets that completed the visible fallback flow don't hold an open session.

### 4. Sign a transaction

Pass a serialized transaction to `signTransaction()`. This only signs — it does not broadcast. The format and return value differ by chain.

```swift SignView.swift — EVM theme={"system"}
// Minimal self-transfer to demonstrate signing; substitute your tx fields.
let tx = #"{"to":"\#(wallet.address)","value":"0x0","data":"0x"}"#
FireblocksHeadlessConnect.signTransaction(transaction: tx) { result in
    switch result {
    case .success(let signedTx):
        // signedTx — RLP-encoded hex string (e.g. 0xf86c…)
        // broadcast with eth_sendRawTransaction when ready
    case .failure(let code, _): break
    }
}
```

```swift SignView.swift — Solana theme={"system"}
// txBytes: your serialized VersionedTransaction as Data
let b64 = txBytes.base64EncodedString()
FireblocksHeadlessConnect.signTransaction(transaction: b64) { result in
    switch result {
    case .success(let signedTx):
        // signedTx — base64-encoded signed transaction bytes
    case .failure(let code, _): break
    }
}
```

### The bridge

You don't write the bridge — it's what flows between the hidden view and the two files above. The component mints a fresh `requestId` (UUID) per connect and drops any message whose id doesn't match the one still in flight, so a stale or forged reply can't complete a newer request. It also drops anything posted from an origin other than the engine URL, and rejects a `connected` message without a non-empty `address` as `malformed_result`.

## Common pitfalls

* **Keep the hidden web view in the hierarchy.** A fully detached `WKWebView` gets suspended by iOS and its relay socket stalls. Keep it 1×1 and hidden.
* **Test on a physical device.** Wallets don't run in the Simulator, so real wallet round-trips require a real iPhone.
* **Serve over HTTPS.** The flow mints WalletConnect URIs via WebCrypto, which needs a secure context.
* **Native deeplinks are faster.** Universal links round-trip through the wallet's link server first; the hosted page prefers native schemes when embedded.
