> ## 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 Flutter

> Present the hosted Connections page with flutter_web_auth_2, 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 Flutter the [Connections](/docs/connections/overview) contract is the same as every other platform, using Chrome Custom Tabs on Android and `ASWebAuthenticationSession` on iOS.

You have two options:

* **Basic** — present the hosted page and read the result. One Dart file to copy.
* **Headless** — render your own native wallet list, driven by a hidden `WebViewWidget`. 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 integration is [`fireblocks_connect.dart`](https://github.com/dynamic-labs-oss/iframe-fb/blob/main/flutter-harness/lib/src/fireblocks_connect.dart).

<Tip>
  `flutter_web_auth_2` wraps both platform auth-session APIs in one call — the Flutter equivalent of iOS's `ASWebAuthenticationSession` and Android's Chrome Custom Tabs.
</Tip>

### 1. Add dependencies

```yaml pubspec.yaml theme={"system"}
dependencies:
  flutter_web_auth_2: 4.0.1
  url_launcher: 6.3.1
```

### 2. Register your URL scheme

For iOS, add to `ios/Runner/Info.plist`.

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

For Android, add inside your `<activity>` in `AndroidManifest.xml`.

```xml AndroidManifest.xml theme={"system"}
<intent-filter android:autoVerify="false">
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="myapp" android:host="wallet-callback" />
</intent-filter>
```

### 3. Connect and use the result

```dart connect_button.dart theme={"system"}
import 'package:flutter_harness/fireblocks_connect.dart';

try {
  final wallet = await FireblocksConnect.connect(
    flowUrl: 'https://your-connect-page.example/',
    scheme: 'myapp',
  );
  // wallet.address, wallet.chain,
  // wallet.walletName, wallet.walletImage
} on FireblocksConnectCancelled {
  // user dismissed
} on FireblocksConnectError catch (e) {
  // e.message — nonce mismatch or malformed callback
}
```

`walletImage` is usually an SVG-sprite URL — render it in a `WebViewWidget` rather than `Image.network`.

## Headless

Render your **own native list** and drive a hidden `WebViewWidget` that runs the Dynamic SDK and returns results — including message and transaction signatures — over a JS bridge.

<Note>
  **No SDK in your app.** Your app links no wallet SDK — no CocoaPods, no native crypto, no Gradle dep. It needs a hidden `WebViewWidget` pointed at `headless.html` and your URL scheme. All the WalletConnect / MetaMask / Phantom logic — and the wallet list itself — comes from that hosted view.
</Note>

Copy [`fireblocks_headless_connect.dart`](https://github.com/dynamic-labs-oss/iframe-fb/blob/main/flutter-harness/lib/src/fireblocks_headless_connect.dart).

### 1. Add dependencies

```yaml pubspec.yaml theme={"system"}
dependencies:
  webview_flutter: 4.10.0      # hidden engine WebView
  url_launcher: 6.3.1          # open wallet deeplinks
  flutter_web_auth_2: 4.0.1    # visible fallback flow
```

### 2. Register URL schemes

You need two hosts under your scheme — one for the visible flow callback (`wallet-callback`) and one for Phantom's redirect (`phantom-headless`) — plus `LSApplicationQueriesSchemes` on iOS for the wallet schemes you open.

### 3. Mount the engine and connect

Wrap your home screen with `FireblocksEngineHost` — it keeps the hidden `WebViewWidget` alive at all times. Prewarm at launch and connect on tap.

```dart main.dart theme={"system"}
MaterialApp(
  home: FireblocksEngineHost(child: ExampleScreen()),
)
```

```dart example_screen.dart theme={"system"}
// prewarm at app start
FireblocksHeadlessConnect.shared.prewarm();

// receive the live wallet list
FireblocksHeadlessConnect.shared.onWallets = (wallets) {
  setState(() => _wallets = wallets);
};

// connect on tap
FireblocksHeadlessConnect.shared.connect(
  walletKey: 'metamask',
  chain: 'evm',
  onResult: (result) => switch (result) {
    ConnectSuccess(:final wallet) =>
      setState(() => _connection = wallet),
    ConnectFallbackRequired() =>
      FireblocksConnect.connect(flowUrl: _flowUrl, scheme: _scheme)
        .then((w) => setState(() => _connection = w)),
    ConnectFailure(:final code, :final message) =>
      setState(() => _error = '[$code] $message'),
  },
);
```

Forward deep-links from Phantom to the engine in `MaterialApp.onGenerateRoute` (or your router's redirect hook) with `FireblocksHeadlessConnect.shared.handleReturnUrl(uri)`.

### 4. Sign a message and transaction

After a successful connect, call `sign()` with any string, or `signTransaction()` with a serialized transaction (signing only — no broadcast).

```dart example_screen.dart theme={"system"}
FireblocksHeadlessConnect.shared.sign(
  message: 'Sign in to MyApp',
  onResult: (result) => switch (result) {
    SignSuccess(:final value) =>
      setState(() => _signature = value),   // hex string
    SignFailure(:final error) =>
      setState(() => _error = error.toString()),
  },
);
```

For EVM, pass a JSON transaction (only `to` is required) and receive an RLP-encoded hex string. For Solana, pass a base64-encoded serialized `VersionedTransaction` and receive base64-encoded signed bytes. Signing is only available for wallets connected through the headless engine.

### 5. Disconnect

Clears `localStorage` in the hidden `WebView` and reloads the engine so stale SDK state doesn't bleed into the next connect.

```dart example_screen.dart theme={"system"}
await FireblocksHeadlessConnect.shared.disconnect();
setState(() => _connection = null);
```

## Common pitfalls

* **Match the channel name exactly.** Flutter bridges over `addJavaScriptChannel('walletNative', …)`. A mismatch is invisible — the channel just never receives anything.
* **Test on a physical device.** Wallets don't run in the Simulator or a bare emulator.
* **Serve over HTTPS.** The flow mints WalletConnect URIs via WebCrypto, which needs a secure context.
