Skip to main content
A hosted swap UI you embed in an <iframe>. It never asks the user to connect a second wallet: @ravn/widget-connector bridges the iframe to whatever wallet is already connected on your page, over postMessage.
1

Embed the iframe

Point src at /widget/v1, with ?origin= set to your page’s exact origin. There is no fallback or guessing on RAVN’s side, so the param is required.
The version segment is part of the contract. A future breaking change ships as /widget/v2, so an already-embedded v1 iframe keeps working unchanged. /widget with no version always redirects to the latest, which is handy for manual testing; don’t embed that one in production.
Keep allow-same-origin. The bridge’s trust model depends on the iframe reporting its real origin on every message it sends. Both sides check event.origin against a pinned, exact value, never a wildcard. Dropping allow-same-origin gives the iframe an opaque "null" origin and breaks that check completely, so the widget will never be able to talk to your bridge. Don’t add allow-top-navigation or allow-popups either; the widget never navigates or opens a window, and every wallet action is relayed over postMessage.
2

Implement your wallet interfaces

@ravn/widget-connector doesn’t hold or ask for keys. It calls back into your own wallet/signer, the one your page already has connected via wagmi, a Solana adapter, or whatever else you’re using.
These methods are your wallet-signing boundary, not RAVN’s. Implement them by calling your existing wallet/signer, the one that already shows its own confirmation UI before signing. Never wire them to an auto-signer with no confirmation step: the widget iframe relays whatever it’s asked to relay, so your implementation is the only place a user gets to see what they’re approving.
3

Create the bridge

getWallet/getSolanaWallet answer ad-hoc requests from the widget, while updateWallet/updateSolanaWallet push state changes to it proactively. EVM and Solana are independent: a user can have either, both, or neither connected, and one changing never implies anything about the other.

Why no double wallet-connect

The widget renders its own UI, but never its own wallet-connect flow while the bridge is live. It sends a bridge:ready handshake on load, and if your page answers with a bridge:init (which createRavnWidgetBridge does automatically), the widget uses that wallet state instead of prompting the user to connect one of its own. The widget only falls back to a self-contained connect UI if the bridge never responds at all, for example if it’s embedded without @ravn/widget-connector wired up on the host side.

CSP and framing

  • A CSP frame-src restricted to https://app.ravn.exchange, rather than *, on your own page is good hygiene: it stops your page from ever framing anything else under that directive by accident.
  • RAVN’s /widget/* route intentionally sends no X-Frame-Options or frame-ancestors restriction, since the whole point is that any integrator can embed it. Origin trust is enforced entirely at the application layer, through the ?origin= param plus the bridge’s origin and source checks on every message, not through browser framing headers.

Reference

createRavnWidgetBridge(options)

HTMLIFrameElement
required
The embedded iframe element.
string
required
The exact origin the widget is served from, e.g. https://app.ravn.exchange. Never *.
() => RavnWallet | null
required
Called on every wallet:* request from the widget. Return null while no EVM wallet is connected.
() => RavnSolanaWallet | null
Called on every solana:* request. Independent of getWallet; return null while no Solana wallet is connected.
Record<string, unknown>
Non-wallet config sent once at handshake: theme, allowed tokens, an integrator API key, and so on.
Returns a RavnWidgetBridge:
(wallet: { address, chainId } | null) => void
Call whenever the host’s own EVM wallet state changes: connect, account switch, chain switch, disconnect.
(wallet: { address } | null) => void
Call whenever the host’s own Solana wallet state changes. Independent of updateWallet.
() => void
Removes the message listener. Call on unmount.

Wallet interfaces

interface
address, chainId, and sendTransaction / signTypedData / signMessage / switchChain: one method per wallet:* action the widget can send.
interface
address and signTransaction, given base64 of an unsigned VersionedTransaction, returning base64 of the signed one.
An unimplemented or momentarily-null wallet isn’t a bridge-level failure. A wallet:* or solana:* request while getWallet()/getSolanaWallet() returns null gets back WALLET_NOT_CONNECTED or SOLANA_WALLET_NOT_CONNECTED, which surfaces to the widget as a rejected request, the same as any other failed wallet action.