Skip to content

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

html
<div id="steelswap-widget"></div>
<script src="https://steelswap.io/widget/loader.js"></script>
<script>
  SteelSwapWidget.init({
    container: '#steelswap-widget'
  })
</script>
html
<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.

OptionTypeDefaultDescription
containerstring | ElementRequired. CSS selector or DOM element to render the widget into.
partnerstring''Partner identifier for fee tracking and analytics.
apiKeystring''Partner API key. Required for partner fee distribution.
accentColorstring'#F48020'Primary accent color (hex). Applied to buttons, highlights, and interactive elements.
borderRadiusnumber16Corner 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.
inputTokenstring'lovelace'Default input token. Use 'lovelace' for ADA or the full policyId+assetName hex string for other tokens.
outputTokenstring''Default output token. Same format as inputToken. When empty, defaults to the highest-volume token.
fontFamilystring''CSS font-family override. Applied to the widget's --font-family-display CSS variable.
widthstring'420px'Iframe width. Any valid CSS value (px, %, vw).
heightstring'600px'Iframe height. Can be auto-adjusted via widget:resize messages.
onSwapCompletefunctionCallback fired when a swap transaction is submitted. Receives { txHash: string }.
onErrorfunctionCallback 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.

js
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.

js
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.

js
widget.disconnectWallet()

Static Methods

SteelSwapWidget.setBaseUrl(url)

Overrides the base URL used to load the widget iframe. Useful for development and testing.

js
SteelSwapWidget.setBaseUrl('http://localhost:5173')

Note: This must be called before SteelSwapWidget.init().

Callbacks

onSwapComplete

Fired when the user successfully submits a swap transaction.

js
onSwapComplete: function (data) {
  console.log(data.txHash)
  // data: { txHash: "abc123def456..." }
}
FieldTypeDescription
txHashstringThe Cardano transaction hash

onError

Fired when the widget encounters an error (network failure, wallet rejection, etc.).

js
onError: function (data) {
  console.error(data.message)
  // data: { message: "User rejected transaction" }
}
FieldTypeDescription
messagestringHuman-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.

js
accentColor: '#5B21B6'  // Purple accent

CSS 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.

js
borderRadius: 24  // Very rounded
borderRadius: 0   // Square corners

The 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:

PropertyDark ValueLight Value
--color-0#1A1517#FAFAFA
--color-1#221E20#FFFFFF
--color-2#2A2527#F5F5F5
--color-3#322D2F#EEEEEE
--color-textrgba(255,255,255,0.87)rgba(26,21,23,1)
--color-text-mutedrgba(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.

js
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

  1. Host page loads loader.js which creates the iframe
  2. Widget sends widget:ready when loaded
  3. Loader checks for available CIP-30 wallets and auto-connects if one is already enabled
  4. 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
  5. When the widget needs wallet access (e.g., to sign a transaction), it sends a wallet:request
  6. The loader calls the actual wallet API and sends the result back as wallet:response
  7. 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:

MethodDescription
getUtxosGet available UTXOs
getCollateralGet collateral UTXOs (Nami uses experimental.getCollateral)
signTxSign a transaction
submitTxSubmit a signed transaction
getBalanceGet wallet balance
getChangeAddressGet the change address
getUsedAddressesGet all used addresses
getNetworkIdGet the network ID (mainnet = 1)
getRewardAddressesGet 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

TypeDataDescription
widget:readyWidget 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

TypeDataDescription
wallet:connected{ name, icon }Wallet has been connected (name and base64 icon)
wallet:disconnectedWallet 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_URL as the target origin

Iframe Isolation

The widget runs in an iframe with:

  • border: none — no visible frame
  • allow: clipboard-write — only clipboard access is granted
  • loading: lazy — deferred loading
  • colorScheme: 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.

  1. Start the dev server: bun dev
  2. Open http://localhost:5173/widget/test.html
  3. The test page auto-fills the Base URL with window.location.origin
  4. 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:

js
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.js loaded successfully (check for window.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.source matches — 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 partner and apiKey are 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

Cardano's DEX aggregator.