Skip to main content
This is an enterprise-only feature. Please contact us to enable.
The same headless architecture as iOS: render your own native list, and drive a hidden WebView that runs the Dynamic SDK and returns results (including message and transaction signatures) over a JS bridge. No wallet SDK in the app. The basic Android flow is the recommended default.
No SDK in your app. Your app links no wallet SDK. It needs a hidden WebView pointed at the hosted engine page (https://connect.dynamicauth.com/headless.html, set as ENGINE_URL in FireblocksHeadlessConnect) and your URL scheme. All WalletConnect / MetaMask / Phantom logic (and the wallet list) comes from that hosted view. Same bridge contract as iOS.
Copy FireblocksHeadlessConnect.kt (below) and FireblocksConnect.kt from the basic Android guide (visible fallback).

1. Get the wallet menu (no static file)

The engine derives the list live from the Dynamic catalog and pushes it over the bridge (a wallets message). Set FireblocksHeadlessConnect.onWallets.

2. Drop in FireblocksHeadlessConnect

Owns a hidden WebView, bridges to it (addJavascriptInterface + evaluateJavascript), opens the wallet deeplink it returns, and calls you back. Pre-warm it at launch. Set environmentId before prewarm() to target a different Dynamic environment.
MainActivity.kt
FireblocksHeadlessConnect.kt

3. Sign a message

The hosted engine supports window.headlessConnect.sign. After a successful connect, invoke sign() with any string. The wallet app prompts the user; the callback delivers a hex signature.
Sample harness update needed. The FireblocksHeadlessConnect.kt sample currently handles connect only. Add sign() / signTransaction() methods and their bridge handlers following the same pattern as the iOS and Flutter harnesses. The bridge protocol is identical.
MainActivity.kt
Only available for wallets connected through the headless engine. Visible-flow wallets do not hold an open session.

4. Sign a transaction

Pass a serialized transaction. Signing only, no broadcast.
MainActivity.kt (EVM)
MainActivity.kt (Solana)

5. Wire the manifest and redirect

Beyond the basic flow, headless needs two manifest additions: a phantom-headless host on the same FireblocksRedirectActivity intent-filter, and a <queries> block so the app can open wallet deeplinks on Android 11+.
AndroidManifest.xml
Route the return activity to the engine first (for Phantom’s redirect), then fall through to the visible flow:
FireblocksRedirectActivity
The hidden WebView needs the INTERNET permission. Do not call webView.onPause() on it. That suspends the relay socket.

6. Render the list (your UI)

MainActivity is a sample list (search, chain picker, connecting state, auto-fallback) you would swap for your own design.
MainActivity.kt

7. Connect Phantom on EVM (its own browser)

Phantom injects an EVM provider (window.phantom.ethereum) only inside its own in-app browser. It has no WalletConnect entry in Dynamic’s wallet book and no EVM deeplink, so the hidden engine cannot drive it and the visible flow has no provider to talk to. The route that works is to open your hosted page inside Phantom’s browser and take the result back over your URL scheme. Each operation is one round trip: Phantom comes to the foreground with your page in it, the user approves, and the result arrives on <scheme>://wallet-browser.
The engine reports each wallet’s in-app-browser template in the wallets bridge message as inAppBrowser. The template contains {{encodedDappURI}}, and you replace every occurrence (Phantom’s uses it twice). A template is not a chain: it only means the wallet can open a URL in its own browser, so you decide per wallet which chains that browser serves.
FireblocksWalletBrowser.kt

Carry the two extra fields

Add the template to FireblocksHeadlessConnect.Wallet and remember it on the connection, or sign and send fall back to the engine.

Register the callback host

Add a third host to the FireblocksRedirectActivity intent-filter and route it before the Custom Tab flows, which all share wallet-callback.
AndroidManifest.xml
FireblocksRedirectActivity

Offer the option only for Phantom

Do not derive EVM support from the presence of a template. Phantom’s template comes from its Sui wallet-book entry, so a template on its own says nothing about EVM. The evidence for Phantom specifically is phantomevm.injectedConfig.windowLocations: ["phantom.ethereum"], an EIP-1193 provider inside its browser.
MainActivity.kt
Show it as a normal chain row (“Ethereum & EVM”, with “Opens in the wallet’s own browser” underneath). The synthetic value stays in your UI and is never sent to the page.

Connect, sign, and send

MainActivity.kt

What travels on the URL

The callback carries address and chain (connect), signature (sign), or txHash (send), or error=1&code=&message=, always with the nonce echoed back. A send is already broadcast when the hash arrives. One request is in flight at a time, a new one supersedes the previous, and an abandoned one times out after five minutes.

Phantom pitfalls

  • Keep the template on the connection. Sign and send must reopen the same browser, because the account exists nowhere else. Losing the stored template sends the request to the engine, which reports no wallet connected.
  • Use a separate callback host. wallet-callback is claimed by the visible flow, so a link arriving from Phantom would be dropped or complete an unrelated request.
  • Watch where the template lands on Android. The template is an https app link and reaches Phantom only if its app links are verified. Otherwise Android can hand it to Chrome, where nothing is injected and the page correctly reports no EVM path. That message in a browser that is not Phantom means the hand-off went to the wrong app.
  • Offer the return anchor. The page renders a “Return to the app” link for browsers that ignore a programmatic redirect.

8. The bridge (for reference)

Identical to iOS.
bridge messages

Common pitfalls

  • Do not call webView.onPause() on the hidden WebView. That suspends its relay socket.
  • Cancellation is not auto-detected by Custom Tabs. Treat resumed-with-no-result as cancelled, or hold the pending flow in a ViewModel.
  • Test on a device with a wallet installed, not a bare emulator.
  • Serve over HTTPS. The flow mints WalletConnect URIs via WebCrypto.
Last modified on September 21, 2026