<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 The version segment is part of the contract. A future breaking change ships as
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./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.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.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 abridge: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-srcrestricted tohttps://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 noX-Frame-Optionsorframe-ancestorsrestriction, 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.
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.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.
