SteelSwap Widget - Integration Guide
Embed a fully functional Cardano token swap interface on your website. The widget runs inside an iframe and communicates with your page via postMessage for secure wallet access.
Quick Start
Minimal Example
<div id="steelswap-widget"></div>
<script src="https://steelswap.io/widget/loader.js"></script>
<script>
SteelSwapWidget.init({
container: '#steelswap-widget'
})
</script>Recommended Example (with partner tracking)
<div id="steelswap-widget"></div>
<script src="https://steelswap.io/widget/loader.js"></script>
<script>
var widget = SteelSwapWidget.init({
container: '#steelswap-widget',
partner: 'your-partner-name',
apiKey: 'pk_live_abc123',
accentColor: '#5B21B6',
borderRadius: 16,
theme: 'dark',
inputToken: 'lovelace',
width: '420px',
height: '600px',
onSwapComplete: function (data) {
console.log('Swap completed:', data.txHash)
},
onError: function (data) {
console.error('Widget error:', data.message)
}
})
</script>Configuration Reference
All options are passed to SteelSwapWidget.init(). Only container is required.
| Option | Type | Default | Description |
|---|---|---|---|
container | string | Element | — | Required. CSS selector or DOM element to render the widget into. |
partner | string | '' | Partner identifier for fee tracking and analytics. |
apiKey | string | '' | Partner API key. Required for partner fee distribution. |
accentColor | string | '#F48020' | Primary accent color (hex). Applied to buttons, highlights, and interactive elements. |
borderRadius | number | 16 | Corner radius in pixels. Applied to the iframe and internal components (internal radius scales to 50% of this value). |
theme | 'dark' | 'light' | 'dark' | Color scheme. Dark uses the SteelSwap brand palette; light uses neutral grays. |
inputToken | string | 'lovelace' | Default input token. Use 'lovelace' for ADA or the full policyId+assetName hex string for other tokens. |
outputToken | string | '' | Default output token. Same format as inputToken. When empty, defaults to the highest-volume token. |
fontFamily | string | '' | CSS font-family override. Applied to the widget's --font-family-display CSS variable. |
width | string | '420px' | Iframe width. Any valid CSS value (px, %, vw). |
height | string | '600px' | Iframe height. Can be auto-adjusted via widget:resize messages. |
onSwapComplete | function | — | Callback fired when a swap transaction is submitted. Receives { txHash: string }. |
onError | function | — | Callback fired on widget errors. Receives { message: string }. |
Instance Methods
SteelSwapWidget.init() returns an instance object with these methods:
destroy()
Removes the widget iframe and cleans up all event listeners.
widget.destroy()connectWallet(walletId)
Connects a Cardano wallet by its CIP-30 identifier. Returns a Promise that resolves with the wallet info on success or rejects on failure. The wallet must be installed as a browser extension.
widget.connectWallet('eternl')
.then(function (info) {
console.log('Connected:', info.name)
})
.catch(function (err) {
console.error('Failed to connect:', err.message)
})Common wallet IDs: eternl, nami, lace, flint, yoroi, gerowallet, typhoncip30, nufi
disconnectWallet()
Disconnects the currently connected wallet and notifies the widget.
widget.disconnectWallet()Static Methods
SteelSwapWidget.setBaseUrl(url)
Overrides the base URL used to load the widget iframe. Useful for development and testing.
SteelSwapWidget.setBaseUrl('http://localhost:5173')Note: This must be called before SteelSwapWidget.init().
Callbacks
onSwapComplete
Fired when the user successfully submits a swap transaction.
onSwapComplete: function (data) {
console.log(data.txHash)
// data: { txHash: "abc123def456..." }
}| Field | Type | Description |
|---|---|---|
txHash | string | The Cardano transaction hash |
onError
Fired when the widget encounters an error (network failure, wallet rejection, etc.).
onError: function (data) {
console.error(data.message)
// data: { message: "User rejected transaction" }
}| Field | Type | Description |
|---|---|---|
message | string | Human-readable error description |
Theming
The widget supports visual customization through configuration options. Changes are applied via CSS custom properties inside the iframe.
Accent Color
Set accentColor to any hex color. The widget converts it to RGB for use in opacity variants.
accentColor: '#5B21B6' // Purple accentCSS properties affected:
--color-primary— the hex color--color-primary-rgb— the R, G, B components (for rgba usage)
Border Radius
Set borderRadius to control the roundness of the widget and its internal elements.
borderRadius: 24 // Very rounded
borderRadius: 0 // Square cornersThe outer iframe gets the full radius. Internal elements use 50% of the value (--radius-md), and the full value for larger containers (--radius-lg).
Light Theme
Set theme: 'light' to switch from the dark SteelSwap brand palette to a light color scheme.
Light theme overrides these CSS properties:
| Property | Dark Value | Light Value |
|---|---|---|
--color-0 | #1A1517 | #FAFAFA |
--color-1 | #221E20 | #FFFFFF |
--color-2 | #2A2527 | #F5F5F5 |
--color-3 | #322D2F | #EEEEEE |
--color-text | rgba(255,255,255,0.87) | rgba(26,21,23,1) |
--color-text-muted | rgba(255,255,255,0.5) | rgba(26,21,23,0.6) |
Font Override
Set fontFamily to use a custom font. The font must be loaded on the host page or available as a web font. The widget applies it to --font-family-display.
fontFamily: 'Inter, sans-serif'Wallet Integration
The widget uses a postMessage bridge to access the user's Cardano wallet through the host page. This is necessary because iframes cannot directly access browser extension APIs.
How It Works
- Host page loads
loader.jswhich creates the iframe - Widget sends
widget:readywhen loaded - Loader checks for available CIP-30 wallets and auto-connects if one is already enabled
- On
wallet:connected, the widget creates a CIP-30 proxy and registers it as a local wallet — MeshSDK wraps it with CBOR deserialization so all address/balance/UTXO formats are handled automatically - When the widget needs wallet access (e.g., to sign a transaction), it sends a
wallet:request - The loader calls the actual wallet API and sends the result back as
wallet:response - Requests that don't receive a response within 60 seconds are automatically timed out
Auto-Connect
When the widget signals widget:ready, the loader iterates through window.cardano and calls isEnabled() on each wallet. If a wallet is already enabled (user previously connected), it auto-connects without prompting.
Manual Connect
Call widget.connectWallet('walletId') to explicitly enable and connect a specific wallet. This calls window.cardano[walletId].enable() which may trigger the wallet's connection prompt.
Whitelisted Methods
Only these CIP-30 methods can be called through the bridge:
| Method | Description |
|---|---|
getUtxos | Get available UTXOs |
getCollateral | Get collateral UTXOs (Nami uses experimental.getCollateral) |
signTx | Sign a transaction |
submitTx | Submit a signed transaction |
getBalance | Get wallet balance |
getChangeAddress | Get the change address |
getUsedAddresses | Get all used addresses |
getNetworkId | Get the network ID (mainnet = 1) |
getRewardAddresses | Get staking/reward addresses |
Excluded: signData
signData (CIP-8 message signing) is intentionally excluded from the whitelist. This prevents the widget from requesting arbitrary data signatures, which could be used for authentication or message signing on behalf of the user without clear intent. If you need signData functionality, implement it directly on your host page.
PostMessage Protocol
All communication between the host page and widget uses window.postMessage. Messages are plain objects with a type field and optional data.
Widget to Host
| Type | Data | Description |
|---|---|---|
widget:ready | — | Widget has loaded and is ready for wallet connection |
wallet:request | { id, method, params? } | Widget needs to call a CIP-30 wallet method |
widget:resize | { height } | Widget requests a height change (in pixels) |
widget:swapComplete | { txHash } | A swap transaction was submitted |
widget:error | { message } | An error occurred in the widget |
Host to Widget
| Type | Data | Description |
|---|---|---|
wallet:connected | { name, icon } | Wallet has been connected (name and base64 icon) |
wallet:disconnected | — | Wallet has been disconnected |
wallet:response | { id, result?, error? } | Response to a wallet:request (matched by id) |
wallet:stateUpdate | { connected, address? } | Wallet connection state changed |
Request/Response Flow
Host Widget (iframe)
| |
| <-- widget:ready |
| wallet:connected --> |
| |
| <-- wallet:request |
| { id: "req_1", |
| method: "getUtxos" } |
| |
| [calls window.cardano API] |
| |
| wallet:response --> |
| { id: "req_1", |
| result: [...] } |Security
Method Whitelist
Only 9 specific CIP-30 methods are allowed through the bridge. Any request for an unrecognized method is rejected with an error response. The whitelist is enforced both in the loader (host-side) and the bridge composable (widget-side).
No signData
signData is excluded to prevent the widget from signing arbitrary messages on behalf of the user. This protects against authentication impersonation and phishing scenarios.
Origin Validation
- The loader only processes messages where
event.source === iframe.contentWindow, preventing other frames or windows from injecting messages - The widget posts messages using
parentOrigin(configurable, defaults to'*'for compatibility). In production, this should be locked to the widget's own origin - The loader posts to
WIDGET_BASE_URLas the target origin
Iframe Isolation
The widget runs in an iframe with:
border: none— no visible frameallow: clipboard-write— only clipboard access is grantedloading: lazy— deferred loadingcolorScheme: normal— prevents forced dark/light mode inheritance
Timeout Protection
Wallet requests that don't receive a response within 60 seconds are automatically rejected with a timeout error. This prevents indefinite hangs if the host page stops responding or the wallet extension crashes.
Development & Testing
Test Page
A built-in test page is available at /widget/test.html for interactive widget testing.
- Start the dev server:
bun dev - Open
http://localhost:5173/widget/test.html - The test page auto-fills the Base URL with
window.location.origin - Configure options and click Create Widget
The test page includes:
- Full configuration controls for all
init()options - Live postMessage event log with expandable JSON data
- Wallet connect/disconnect with connection status indicator
- Computed iframe URL preview
Using setBaseUrl for Development
When developing locally, call setBaseUrl before init to point the iframe at your dev server:
SteelSwapWidget.setBaseUrl('http://localhost:5173')
var widget = SteelSwapWidget.init({ container: '#widget' })COOP/COEP Headers
The SteelSwap dev server sets Cross-Origin headers (COOP, COEP, CORP) for SharedArrayBuffer support. If you're testing the widget on a separate host page, ensure your server also sets appropriate CORS headers, or the iframe may be blocked.
Testing Without a Wallet
The widget can load and display the swap interface without a wallet connected. Users can browse tokens, see prices, and configure swaps — wallet connection is only required for submitting transactions.
Troubleshooting
Widget Shows Blank
- Check the browser console for iframe loading errors
- Verify the base URL is correct (
SteelSwapWidget.setBaseUrl()) - Ensure
loader.jsloaded successfully (check forwindow.SteelSwapWidget) - Check for Content Security Policy (CSP) headers blocking iframes from the SteelSwap domain
Wallet Won't Connect
- Verify the wallet extension is installed and appears in
window.cardano - Check that the wallet ID matches exactly (e.g.,
'eternl'not'Eternl') - Look for
enable()rejections in the console — the user may have denied the connection prompt - Ensure a widget is created before calling
connectWallet()
PostMessage Not Working
- Open the browser's message log (DevTools > Console) to see raw messages
- Use the test page's event log to verify messages are flowing
- Check that
event.sourcematches — messages from other scripts will be ignored - If testing cross-origin, verify the target origin matches the widget's actual origin
Token ID Format
Token identifiers use the Cardano native format:
- ADA:
'lovelace' - Native tokens: Full policyId + assetName as a hex string (e.g.,
'279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f534e454b'for SNEK)
Partner Code Not Tracked
- Verify both
partnerandapiKeyare set — both are required for partner fee distribution - Check the iframe URL to confirm the parameters are included in the query string
- Contact SteelSwap to verify your partner credentials