Embed the Gale Bridge cross-chain interface in your website. Your app supplies a container and the user's wallet Provider. The Widget handles asset selection, quotes, approval prompts, transaction submission and order tracking. npm and CDN expose the same SDK API; choose one loading method.
| Resource | URL |
|---|---|
| npm package | @galebridge/widget |
| CDN SDK | https://galebridge.net/widget/v1/widget.js |
| Hosted Widget | https://galebridge.net/widget |
| Available networks and tokens | Integration Networks API |
Before you start#
- Serve production pages over HTTPS. Use
http://localhostorhttp://127.0.0.1for local development; do not open the HTML withfile://. - Create the DOM container before mounting. Use
width: 100%and a maximum width around500px. The SDK adjusts the iframe height to its content. - Use a modern browser with BigInt and
crypto.randomUUID(). In React / Next.js, create the Widget after the client component mounts. - Embedded transactions use wallet adapters supplied by the host. EVM uses an EIP-1193 Provider; TRON uses TronLink or a compatible Provider. Creating the Widget does not request a wallet connection or signature.
- Replace
your-project-webwith a stable project identifier. Use distinct identifiers for independent Widgets on the same host origin to separate pending transaction recovery.clientIdis public and does not establish authentication or commission attribution.
Without a wallet adapter, the Widget still loads and offers a link to its standalone page. Transacting inside the iframe requires an adapter for the selected ecosystem.
Option 1 npm integration#
Install a fixed version:
npm install @galebridge/[email protected]Add a container and a status message to your page:
<div id="bridge" style="width:100%;max-width:500px;margin:auto"></div>
<p id="bridge-status" role="status" aria-live="polite"></p>Add the following to a client entry point built with Vite, Webpack or a similar tool:
import { createWidget, createEvmAdapter, type EVMProvider } from '@galebridge/widget'
const provider = (window as Window & { ethereum?: EVMProvider }).ethereum
const showStatus = (text: string) => {
const element = document.querySelector('#bridge-status')
if (element) element.textContent = text
}
const widget = createWidget({
container: '#bridge',
clientId: 'your-project-web',
theme: 'light',
lang: 'en',
wallets: provider ? { evm: createEvmAdapter(provider) } : {},
})
widget.on('error', ({ code, message }) => {
showStatus(message)
console.error('[Gale Widget]', code, message)
})
widget.on('transactionSent', ({ txHash }) => {
showStatus('Source transaction submitted; waiting for completion: ' + txHash)
})
widget.on('completed', ({ txHash, order }) => {
showStatus('Bridge order completed: ' + txHash)
console.log('Order details', order)
})
void widget.ready.catch((error: Error) => showStatus(error.message))
// Call when the SPA page or component unmounts:
// widget.destroy()This example uses the browser's injected EVM wallet. If your app already uses a wallet connector, pass its selected EIP-1193 Provider to createEvmAdapter(provider) so the host and Widget use the same wallet. Compile this TypeScript with your build tool; do not load it directly as a plain browser script.
React and Next.js example
The host's wallet connection module supplies provider. Keep its reference stable. Without it, users can open the standalone page. Keep 'use client' at the top when using the Next.js App Router.
'use client'
import { useEffect, useRef, useState } from 'react'
import { createWidget, createEvmAdapter, type EVMProvider } from '@galebridge/widget'
export function GaleBridge({ provider }: { provider?: EVMProvider }) {
const container = useRef<HTMLDivElement>(null)
const [status, setStatus] = useState('')
useEffect(() => {
if (!container.current) return
setStatus('')
const widget = createWidget({
container: container.current,
clientId: 'your-project-web',
lang: 'en',
theme: 'light',
wallets: provider ? { evm: createEvmAdapter(provider) } : {},
})
widget.on('error', ({ message }) => setStatus(message))
widget.on('transactionSent', ({ txHash }) => setStatus('Transaction submitted: ' + txHash))
widget.on('completed', ({ txHash }) => setStatus('Bridge order completed: ' + txHash))
void widget.ready.catch((error: Error) => setStatus(error.message))
return () => widget.destroy()
}, [provider])
return (
<section>
<div ref={container} style={{ width: '100%', maxWidth: 500 }} />
<p role="status" aria-live="polite">{status}</p>
</section>
)
}Changing provider recreates the Widget in this component. Avoid switching Providers or unmounting while a transaction is signing or being tracked. Unmounting does not cancel a submitted on-chain transaction.
Option 2 CDN integration#
No npm installation is required. Save the following as cdn.html and serve it over HTTP(S). The SDK exposes its API as GaleBridge. Load the SDK before calling createWidget.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Gale Bridge integration example</title>
</head>
<body>
<main style="max-width:500px;margin:24px auto;padding:0 12px">
<div id="bridge"></div>
<p id="bridge-status" role="status" aria-live="polite"></p>
</main>
<script src="https://galebridge.net/widget/v1/widget.js"></script>
<script>
const status = document.getElementById('bridge-status')
if (!window.GaleBridge) {
status.textContent = 'SDK failed to load. Check the network and CSP settings.'
} else {
const provider = window.ethereum
const widget = GaleBridge.createWidget({
container: '#bridge',
clientId: 'your-project-web',
lang: 'en',
theme: 'light',
wallets: provider ? { evm: GaleBridge.createEvmAdapter(provider) } : {},
})
widget.on('error', ({ code, message }) => {
status.textContent = message
console.error('[Gale Widget]', code, message)
})
widget.on('transactionSent', ({ txHash }) => {
status.textContent = 'Source transaction submitted; waiting for completion: ' + txHash
})
widget.on('completed', ({ txHash, order }) => {
status.textContent = 'Bridge order completed: ' + txHash
console.log('Order details', order)
})
widget.ready.catch((error) => { status.textContent = error.message })
// Call widget.destroy() when leaving this SPA page.
}
</script>
</body>
</html>/widget/v1/widget.js is a maintained production URL, not an immutable npm version URL. To pin the SDK, install 0.1.0 through npm. Pinning the SDK does not freeze the hosted page, available assets or backend routes.
Set the Widget language
The hosted Widget defaults to English. For Traditional Chinese, use the existing zh value when creating the Widget or update it while idle:
widget.update({ lang: 'zh' }) // Traditional Chinese
// widget.update({ lang: 'en' }) // EnglishUsers can also switch language in the Widget header, including while following a pending transfer. This changes presentation only and does not send or cancel a transaction.
Wallet integration#
EVM wallets
import { createEvmAdapter } from '@galebridge/widget'
// provider is the EIP-1193 Provider selected by the host app.
widget.setWallet('evm', createEvmAdapter(provider))After setWallet replaces an adapter, users reconnect using the Widget's connect button. The EVM adapter can switch to networks already configured in the wallet; it does not add unknown networks automatically. It checks the active account and chain again before submitting. Approvals use the exact transfer amount. Tokens that require resetting an existing allowance to zero need an appropriate custom adapter.
TRON wallets
After TronLink or a compatible TRON Provider is injected into the host, keep the getter so the adapter can read tronWeb after connection:
// npm: import createTronAdapter from @galebridge/widget.
// CDN: use const { createTronAdapter } = GaleBridge.
const provider = window.tronLink ?? window.tron ?? window.okxwallet?.tronLink
if (provider?.request) {
widget.setWallet('tron', createTronAdapter({
request: provider.request.bind(provider),
get tronWeb() { return provider.tronWeb ?? window.tronWeb },
}))
}The built-in TRON adapter requires mainnet. For legacy Providers without network queries, it accepts only a node origin of https://api.trongrid.io. Other legacy custom RPCs require a custom adapter that verifies the network. Approval and bridge calls use fee limits of 100 TRX and 150 TRX respectively. These are caps; check actual costs in the wallet confirmation.
CDN wallet functions match the npm API, including GaleBridge.createEvmAdapter and GaleBridge.createTronAdapter. Version 0.1.0 does not include WalletConnect or mobile wallet deep links. Validate the wallet versions and mobile wallet browsers you intend to support in your integration environment.
Configuration#
All options except container are optional. Supply chain IDs, amounts and addresses as strings. Use the token address returned by the Networks API, not an internal database ID.
| Option | Description |
|---|---|
container | Required CSS selector or HTMLElement; the container must exist before mounting |
widgetUrl | Defaults to https://galebridge.net/widget; use a trusted hosted URL |
clientId | Stable project / Widget identifier; omitted values share the default recovery group |
wallets | { evm, tron } adapters; can be supplied later with setWallet |
theme | light or dark; defaults to light |
lang | en (English) or zh (Traditional Chinese); defaults to English. The Widget also has a language selector; documentation language is selected separately |
accentColor | Six-digit hex color, such as #245fef |
fromChainId, toChainId | Decimal source and destination chain IDs, such as "1", "137" |
fromTokenAddress, toTokenAddress | Token addresses on the corresponding networks; use the API's placeholder for native assets |
amount | Prefilled source amount in human-readable token units, such as "10.5"; do not pass an empty string or an approximate floating-point result |
receiver | Prefilled destination address; users may enter it themselves. Connecting a wallet in the same ecosystem can fill the active wallet address when empty |
lockDestination | Defaults to false. If true, both destination chain and token are required. A supplied receiver is also locked |
allowedChains | Allowed chain IDs for both sides, such as ["1", "137"] |
allowedTokens | Allowed { chainId, address } pairs for both sides |
slippage | Fraction string: "0.05" means 5%, the default. Must be greater than 0 and at most 0.5. When configured, the UI cannot edit it. It is not a guaranteed destination minimum |
protocol | all, v1 or v2; defaults to all. Actual quotes determine available routes |
Preserve the case of TRON Base58 addresses. Networks, assets and routes can change; use the live API and quotes. Prefilling is not locking: unavailable prefilled assets may fall back to another available asset. An unavailable locked destination blocks submission. allowedChains and allowedTokens restrict selection; they do not enable unsupported backend assets.
Example lock the destination asset
This configuration locks the destination to Ethereum DAI while users select a source asset and enter a receiver. Supplying a real receiver also locks that address. Check the Networks API for availability before going live.
const destinationConfig = {
toChainId: '1',
toTokenAddress: '0x6b175474e89094c44da98b954eedeac495271d0f',
lockDestination: true,
}
// On creation: createWidget({ container: '#bridge', ...destinationConfig, wallets })
// Or update while no transaction is in progress:
widget.update(destinationConfig)Events and lifecycle#
Register listeners immediately, then wait for widget.ready. Readiness means the host / iframe handshake is complete; it does not mean assets have loaded, a wallet is connected or an executable quote is available.
| Event | Payload and meaning |
|---|---|
ready | { version: 1 }; 1 is the messaging protocol version |
needConnect | { ecosystem }; a wallet connection is being requested |
connected | { address, chainId, ecosystem }; wallet connected |
transactionSent | { txHash, chainId }; source transaction submitted, not destination settlement |
statusChanged | { txHash, status, order? }; order state changed; can also report source_failed |
completed | { txHash, order }; the completed order matches the transfer. txHash is still the source transaction hash |
error | { code, message }; initialization, configuration or transaction error |
const off = widget.on('statusChanged', ({ txHash, status }) => {
console.log(txHash, status)
})
off() // Remove this listener
widget.update({ theme: 'dark', lang: 'en', accentColor: '#245fef' })
// Recreate the widget to change container or widgetUrl.
// Invalid configuration can throw; a locked update emits an error event.
widget.destroy() // Remove the iframe, listeners and SDK timers; safe to call againThe Widget language selector changes presentation without clearing the draft, wallet connection or pending transfer. While idle, SDK updates to only lang, theme or accentColor also preserve the draft. Changing transfer configuration resets form selections, the wallet connection and quote; fields without explicit configuration, including the amount, can reset to defaults. During signing and order tracking, the Widget rejects update with CONFIG_LOCKED. destroy() does not cancel on-chain transactions. Recreating the Widget can resume tracking from its saved pending record. Recovery uses localStorage; blocked third-party storage can prevent signing and prompt users to open the standalone page.
completed is a UI notification and may be emitted again after a refresh or remount. For crediting, delivery or similar business actions, verify the order and actual settlement independently on your server and handle duplicates by transaction or order. Do not credit funds solely from a browser event.
CSP and deployment#
If the host uses CSP, extend its existing policy to allow:
https://galebridge.netinframe-src.- For CDN loading,
https://galebridge.netinscript-src. Use your approved nonce / hash for the inline example, or move it into an external script.
The Widget calls its hosted origin's API. The host does not need additional API URLs or credentials for those iframe requests. Embed with the SDK rather than a manually constructed iframe. Keep its sandbox settings to preserve the handshake and standalone-page link.
Troubleshooting#
| Symptom | Action |
|---|---|
Widget container not found | Check the selector and mount after the container exists |
INIT_TIMEOUT or a blank Widget | Serve over HTTP(S), check SDK / Widget connectivity and the host's script / frame CSP |
| Cannot connect inside the iframe | Supply the appropriate ecosystem adapter and the Provider actually selected by the user |
| EVM chain switching fails | Add the network in the wallet, then reconnect and get a fresh quote |
INVALID_CONFIG / CONFIG_LOCKED | Check formats and required locked-destination fields; wait for the current transaction before updating |
| Order temporarily not found | Source indexing may be pending. Continue tracking; do not send again because of indexing delays |
| Wallet timeout or uncertain transaction result | Check wallet history and chain records, then recover the existing hash; do not automatically resend |
| Third-party storage unavailable | Use the Widget's standalone-page link to continue or recover |
Scope and acceptance#
Version 0.1.0 includes npm / CDN loading, EVM / TRON adapters, themes and languages, asset filters, destination locking, order tracking and recovery. Destination amounts are estimates. slippage does not provide an enforceable destination minimum. Displayed fees exclude wallet / network gas. Partner fee sharing, authenticated attribution and an enforced domain allowlist are not included.
Before launch, validate narrow screens, wallet connection and chain switching, quotes and approvals, source submission, final settlement, refresh recovery and teardown. Agree on a test plan first; real approvals and transactions must be confirmed by the authorized wallet holder.
Example files#
examples/widget/npm.ts: npm client entry for your container and build tool.examples/widget/cdn.html: complete CDN page to serve over HTTP(S).examples/widget/react.tsx: React / Next.js client component.
These examples connect to the production Widget. Wallet actions require user confirmation.