@swapper-finance/deposit-sdk
v0.4.0
Published
Easy iframe embedding for Swapper deposit widget with full TypeScript support
Maintainers
Readme
@swapper-finance/deposit-sdk
Easy iframe embedding SDK for the Swapper Finance deposit widget with full TypeScript support.
The SDK provides two integration options: iframe embedding for seamless integration into your application, and modal popup for a focused user experience. Both options support full customization and TypeScript types.
Installation
npm install @swapper-finance/deposit-sdkQuick Start
Option 1: Embed in Container
import { SwapperIframe } from '@swapper-finance/deposit-sdk';
// Create and mount the iframe
const swapper = new SwapperIframe({
container: '#swapper-container', // or pass an HTMLElement
integratorId: 'your-integrator-id',
dstChainId: '8453',
dstTokenAddr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
depositWalletAddress: '0x2A018F2506acaEEE2C10632514Fc5DCa9eE2c28A',
});Option 2: Open in Modal Popup
import { openSwapperModal } from '@swapper-finance/deposit-sdk';
// Open in a centered modal with backdrop
const modal = openSwapperModal({
integratorId: 'your-integrator-id',
dstChainId: '8453',
dstTokenAddr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
depositWalletAddress: '0x2A018F2506acaEEE2C10632514Fc5DCa9eE2c28A',
});
// Close programmatically if needed
// modal.close();Configuration Options
Required Parameters
integratorId- Your integrator identifierdstChainId- Destination chain IDdstTokenAddr- Destination token addressdepositWalletAddress- Wallet address for deposits
Optional Parameters
Styling
The SDK supports a two-tier styling system:
const swapper = new SwapperIframe({
// ... required params
styles: {
// Level 1: Theme mode (light or dark)
themeMode: 'dark',
// Level 2: Component-specific overrides (highest priority)
componentStyles: {
primaryColor: '#FF6B35',
backgroundColor: '#1A1A1A',
textColor: '#E8E8E8',
borderRadius: '12px',
width: '100%',
primaryButtonTextColor: '#FFFFFF',
// ... more style options
},
},
});Custom Contract Calls
import { ContractCallType } from '@swapper-finance/deposit-sdk';
const swapper = new SwapperIframe({
// ... required params
customContractCalls: [
{
callType: ContractCallType.CALL,
target: '0x...',
value: '0',
callData: '0x...',
payload: '0x...'
},
],
});Custom Data
Integrator-scoped data forwarded verbatim to the widget. Use it for values only your app can supply — for example a balance the user holds on an external venue, which the widget has no way to read itself.
The widget interprets these keys per integrator: a partner with a custom home screen reads the keys it knows about and ignores everything else. Unknown or malformed values are dropped without breaking the deposit flow, so you can start sending a field before widget support ships. Values must be JSON-serializable.
const swapper = new SwapperIframe({
// ... required params
customData: {
dex: 'hyperliquid',
dexBalanceUsd: 1234.56,
instructionsUrl: 'https://docs.example.com/deposits',
},
});Keep it fresh with updateCustomData() —
typically on the transaction_success event, once a deposit has changed the
value you are displaying.
Connecting Your Wallet (wallet)
Pass a wallet and the widget skips its own connect step and uses yours. Two modes:
Signer mode — hand over an ethers v5/v6 Signer or a viem WalletClient; the
SDK normalizes it and reads the address, chain and wallet name itself:
const swapper = new SwapperIframe({
// ... required params
wallet: { signer },
});
// Keep it in sync as the user switches account or chain (pass null to disconnect):
swapper.updateSigner(nextSigner);Handler mode — bridge the requests yourself:
const swapper = new SwapperIframe({
// ... required params
wallet: {
onTransactionRequest: (tx) => wallet.sendTransaction(tx), // → { hash }
onChainSwitchRequest: (chainId) => wallet.switchChain(chainId),
// Optional, see below
onSignRequest: async (request) =>
request.method === 'eth_signTypedData_v4'
? wallet.signTypedData(request.typedData)
: wallet.signMessage(request.message, request.isHex),
autoConnect: { address, chainId },
},
});Signing (onSignRequest)
Most flows only need transactions. Deposit from Polymarket and Deposit from
Perps are different: they authorize with a signature — an EIP-712 payload or a
personal_sign — and never send a transaction from the user's wallet.
In signer mode this is automatic: whatever your signer implements
(signMessage / _signTypedData / signTypedData) is bridged, and the widget is
told on connect what it can rely on. In handler mode it's opt-in via
onSignRequest.
If the wallet can't sign, nothing breaks — those two flows just fall back to asking the user to connect a wallet inside the widget, exactly as they did before this bridge existed. Either way the widget's top-right wallet button offers Switch Wallet, so the user can always step off the wallet you passed and pick their own.
Two details worth knowing when implementing onSignRequest by hand:
request.isHexmarks a raw 32-byte digest (e.g. a Gnosis Safe transaction hash). Sign it as bytes — signing the text"0x…"recovers a different address and the Safe rejects it. With ethers,signMessage(getBytes(message)); with viem,signMessage({ message: { raw: message } }).request.typedDatais the fulleth_signTypedData_v4payload, so itstypesincludeEIP712Domain. viem takes it as-is; ethers derives that entry itself and throws if it's also passed, so strip it first.
Narrowing what we advertise (capabilities)
What the widget is told on connect is inferred, and the inference errs on the generous side in both modes:
- signer mode — we check which methods your signer implements. On any ethers or viem signer both are always present (prototype methods, injected actions), so both capabilities are advertised regardless of what the wallet behind the transport would accept.
- handler mode — supplying
onSignRequestadvertises both, since we can't tell what your handler does with eachrequest.method.
If that's wrong for your wallet — a hardware or embedded wallet that refuses
EIP-712, or a handler that only implements personal_sign — say so, and the
widget keeps the flows needing the missing piece on its own connect-wallet step
instead of offering one it can't finish:
wallet: {
signer, // or the handler trio
capabilities: { signMessage: true, signTypedData: false },
}It only ever narrows: declaring a capability your signer doesn't implement won't make it available.
API Reference
Constructor
new SwapperIframe(options: SwapperIframeOptions)Methods
mount(container: HTMLElement | string): void
Mounts the iframe to a container element.
swapper.mount('#my-container');
// or
swapper.mount(document.getElementById('my-container'));updateConfig(config: Partial<SwapperConfig>): void
Updates the configuration dynamically via postMessage.
swapper.updateConfig({
depositWalletAddress: '0xNewAddress...',
dstChainId: '1',
});updateStyles(styles: SwapperStyles): void
Updates only the styles.
swapper.updateStyles({
themeMode: 'light',
brandTheme: {
primaryColor: '#836FFF',
},
});updateCustomContractCalls(calls: ContractCall[]): void
Updates custom contract calls.
swapper.updateCustomContractCalls([
{
callType: ContractCallType.CALL,
target: '0x...',
value: '0',
callData: '0x...',
payload: '0x...',
},
]);updateCustomData(customData: Record<string, unknown>): void
Merges into the integrator-scoped customData payload — pass
only the keys that changed, the rest are preserved. Set a key to undefined to
drop it. Also available on SwapperModal, where it is safe to call before the
modal is built.
swapper.on('transaction_success', async () => {
const balanceUsd = await fetchVenueBalance();
swapper.updateCustomData({ dexBalanceUsd: balanceUsd });
});getConfig(): SwapperConfig
Returns the current configuration.
const config = swapper.getConfig();
console.log(config.depositWalletAddress);destroy(): void
Removes the iframe and cleans up event listeners.
swapper.destroy();Widget Events
The widget emits structured events via postMessage that you can listen to through the SDK. This lets you react to user actions like completed transactions.
Event Envelope
Every event posted from the iframe follows this shape:
interface SwapperWidgetEvent {
type: "SWAPPER_EVENT"; // discriminator
version: "1.0"; // protocol version
name: WidgetEventName; // e.g. "transaction_success"
timestamp: string; // ISO 8601
payload: WidgetEventPayload; // event-specific data
}Listening with onEvent (recommended)
The simplest way to listen for widget events is to pass an onEvent callback in the config. It receives every event (equivalent to .on("*", ...)):
import { SwapperIframe, WidgetEventName } from '@swapper-finance/deposit-sdk';
import type { TransactionSuccessPayload } from '@swapper-finance/deposit-sdk';
const swapper = new SwapperIframe({
container: '#swapper-container',
integratorId: 'your-integrator-id',
dstChainId: '8453',
dstTokenAddr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
depositWalletAddress: '0x2A018F2506acaEEE2C10632514Fc5DCa9eE2c28A',
onEvent: (event) => {
if (event.type === WidgetEventName.TRANSACTION_SUCCESS) {
const payload = event.data as TransactionSuccessPayload;
console.log('Transaction succeeded:', payload.txHash);
}
},
});This also works with openSwapperModal:
import { openSwapperModal, WidgetEventName } from '@swapper-finance/deposit-sdk';
openSwapperModal({
integratorId: 'your-integrator-id',
dstChainId: '8453',
dstTokenAddr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
depositWalletAddress: '0x2A018F2506acaEEE2C10632514Fc5DCa9eE2c28A',
onEvent: (event) => {
console.log('Widget event:', event.type, event.data);
},
});Listening with on() / off()
For more granular control, you can register handlers on the iframe instance directly:
import { SwapperIframe, WidgetEventName } from '@swapper-finance/deposit-sdk';
import type { TransactionSuccessPayload } from '@swapper-finance/deposit-sdk';
const swapper = new SwapperIframe({
container: '#swapper-container',
integratorId: 'your-integrator-id',
dstChainId: '8453',
dstTokenAddr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
depositWalletAddress: '0x2A018F2506acaEEE2C10632514Fc5DCa9eE2c28A',
});
// Listen for a specific event
swapper.on(WidgetEventName.TRANSACTION_SUCCESS, (event) => {
const payload = event.data as TransactionSuccessPayload;
console.log('Transaction succeeded:', payload.txHash);
console.log('Deposit option:', payload.depositOption); // "walletDeposit" | "transferCrypto" | "depositWithCash"
});
// Listen for all events with wildcard
swapper.on('*', (event) => {
console.log('Widget event:', event.type, event.data);
});
// Remove a specific handler
const handler = (event) => { /* ... */ };
swapper.on(WidgetEventName.TRANSACTION_SUCCESS, handler);
swapper.off(WidgetEventName.TRANSACTION_SUCCESS, handler);Listening with window.addEventListener
You can also listen for events directly on the window. Each event is a MessageEvent containing a SwapperWidgetEvent envelope:
import { WIDGET_EVENT_PROTOCOL_VERSION, WidgetEventName } from '@swapper-finance/deposit-sdk';
import type { SwapperWidgetEvent, TransactionSuccessPayload } from '@swapper-finance/deposit-sdk';
window.addEventListener('message', (event: MessageEvent) => {
if (event.data?.type !== 'SWAPPER_EVENT') return;
const message = event.data as SwapperWidgetEvent;
if (message.version !== WIDGET_EVENT_PROTOCOL_VERSION) return;
if (message.name === WidgetEventName.TRANSACTION_SUCCESS) {
const payload = message.payload as TransactionSuccessPayload;
console.log('Tx hash:', payload.txHash);
console.log('Explorer:', payload.explorerUrl);
}
});TransactionSuccessPayload
| Field | Type | Required | Description |
|------------------|-------------------|----------|---------------------------------------|
| depositOption | DepositFlowType | Yes | "walletDeposit", "transferCrypto", or "depositWithCash" |
| txHash | string | No | Transaction hash |
| explorerUrl | string | No | Link to block explorer |
| tokenSymbol | string | No | Token symbol (e.g. "USDC") |
| tokenAddress | string | No | Token contract address |
| chainId | string | No | Chain ID of the transaction |
| amountReceived | string | No | Amount received |
Flexible Widget Height (Auto-Resize)
The widget's home page can show a variable number of deposit options. With a fixed height this can leave an empty gap below the options. In flexible height mode the home page auto-scales to its content instead; all other pages keep the standard fixed height (560px).
SwapperModal / openSwapperModal enable this automatically — nothing to
configure. The modal container smoothly animates to the widget's requested
height (clamped to 90% of the viewport):
import { openSwapperModal } from '@swapper-finance/deposit-sdk';
openSwapperModal({
integratorId: 'your-id',
dstChainId: '8453',
dstTokenAddr: '0x...',
depositWalletAddress: '0x...',
// Home page auto-sizes; other pages stay at the fixed height.
});Notes:
- Users with
prefers-reduced-motionget an instant snap instead of the animation. - If the deployed widget doesn't support flexible height yet, it simply keeps the fixed height — safe to use ahead of the widget deploy.
Modal API Reference
Opening a Modal
Quick Function: openSwapperModal(options)
import { openSwapperModal } from '@swapper-finance/deposit-sdk';
const modal = openSwapperModal({
// Required configuration
integratorId: 'your-id',
dstChainId: '8453',
dstTokenAddr: '0x...',
depositWalletAddress: '0x...',
// Optional: Widget styling
styles: {
themeMode: 'light',
},
// Optional: Modal styling
modalStyle: {
overlayColor: 'rgba(0, 0, 0, 0.8)',
borderRadius: '16px'
},
// Optional: Callback when closed
onClose: () => {
console.log('Modal was closed');
},
});
// Close programmatically
modal.close();Modal Options
modalStyle Configuration
interface ModalStyle {
/**
* Modal width (default: '450px')
*/
width?: string;
/**
* Modal height (default: '560px')
*/
height?: string;
/**
* Background overlay color (default: 'rgba(0, 0, 0, 0.7)')
*/
overlayColor?: string;
/**
* Modal border radius (default: '16px')
*/
borderRadius?: string;
/**
* Z-index for modal (default: 10000)
*/
zIndex?: number;
/**
* Show close button (default: false)
* Set to true to show a close button
*/
showCloseButton?: boolean;
}Modal Class Methods
SwapperModal Class
For more control, use the SwapperModal class directly:
import { SwapperModal } from '@swapper-finance/deposit-sdk';
// Create modal instance
const modal = new SwapperModal({
integratorId: 'your-id',
dstChainId: '8453',
dstTokenAddr: '0x...',
depositWalletAddress: '0x...',
modalStyle: {
showCloseButton: true, // Optional: show close button
},
onClose: () => {
console.log('Modal closed');
},
});
// Open modal
modal.open();
// Close modal — hides the modal (the widget stays loaded for an instant
// reopen) and tells the widget to return to its home screen, so the next
// open() starts fresh instead of resuming on the previously selected option
modal.close();
// Check if open
if (modal.isModalOpen()) {
console.log('Modal is open');
}
// Get iframe instance
const iframe = modal.getIframe();
// Destroy completely
modal.destroy();Preloading the Modal
Build the modal hidden and load the widget in the background so the first
open() is instant:
import { preloadSwapperModal } from '@swapper-finance/deposit-sdk';
// e.g. when your page mounts
const modal = preloadSwapperModal({
integratorId: 'your-id',
dstChainId: '8453',
dstTokenAddr: '0x...',
depositWalletAddress: '0x...',
});
// later, on user action — opens instantly
button.addEventListener('click', () => modal.open());The equivalent with the class API is new SwapperModal(options) followed by
modal.preload(), or in one step with the preload option:
const modal = new SwapperModal({
// ...config
preload: true, // build hidden + load the widget immediately
});If something changed between preload and open (e.g. the deposit address), pass
a config patch to open() — it is applied to the live widget right before it
becomes visible:
modal.open({ depositWalletAddress: '0x...newAddress' });The modal also exposes updateConfig(patch) and updateSigner(signer)
directly (no need to go through getIframe()); both are safe to call before
the modal has been built.
During preload the widget loads everything (bundle, chains, tokens, UI) but
does not create a smart wallet authorization — otherwise every visitor
would get one, including those who never open the widget. The authorization
process starts on the first open(), which signals the widget that it is now
visible.
If you embed SwapperIframe directly in a hidden/off-screen container, you
can get the same behavior manually:
const iframe = new SwapperIframe({
// ...config
deferSmartWalletAuth: true,
});
// when your UI actually reveals the widget:
iframe.notifyWidgetOpened();Preloading an Inline Embed
For widgets embedded in the page layout (not a modal), use SwapperEmbed.
The iframe loads immediately on a hidden position: fixed host; calling
showIn(target) moves the host over the target element's bounding box, so
the already-loaded widget appears there instantly. (The iframe is never
reparented in the DOM — that would force a reload.) The host mirrors the
target's drawable box — bounding rect, corner radii, border widths and
stacking position (computed from the target's outermost stacking-context
ancestor, or the zIndex option if you pass one) — and follows it across
scrolling, window resizes and target size changes.
import { preloadSwapperEmbed } from '@swapper-finance/deposit-sdk';
// e.g. when your page mounts — starts loading in the background
const embed = preloadSwapperEmbed({
integratorId: 'your-id',
dstChainId: '8453',
dstTokenAddr: '0x...',
depositWalletAddress: '0x...',
});
// later, on user action — the widget appears over the placeholder instantly
button.addEventListener('click', () => embed.showIn('#widget-placeholder'));
// hide (widget stays loaded and routes back to its home screen,
// so the next showIn() starts fresh) / full teardown
embed.hide();
embed.destroy();The target element is a placeholder: size it to the box the widget should
occupy (e.g. 450px × 560px). Like the modal preload, smart wallet
authorization is deferred until the first showIn().
Pass flexibleHeight: true to let the widget's home page auto-scale: the
embed animates the placeholder's height to the widget's requested value, so
the surrounding page layout reflows with it.
Available Style Properties
ComponentStyles
interface ComponentStyles {
// Layout
width?: string; // Widget width (default: 450px)
border?: string; // Border style (e.g. "1px solid #ccc")
borderRadius?: string; // Border radius (default: 24px)
// Brand colors
primaryColor?: string; // Main brand color (buttons, links, primary accents)
accentColor?: string; // Secondary accent color for highlights
sphereColor?: string; // Color of the decorative sphere
// Backgrounds & surfaces
backgroundColor?: string; // Widget background color
surfaceColor?: string; // Surface color (cards, inputs)
surfaceAltColor?: string; // Alternative surface color
// Status colors
successColor?: string; // Success state color
successAltColor600?: string; // Alternative success color shade
errorColor?: string; // Error state color
warningColor?: string; // Warning state color
// Typography
textColor?: string; // Main text color
primaryButtonTextColor?: string; // Text color on primary buttons
}Examples
Modal Popup Usage
<!DOCTYPE html>
<html>
<head>
<title>Swapper Modal</title>
</head>
<body>
<button id="open-modal">Open Swapper</button>
<script type="module">
import { openSwapperModal } from '@swapper-finance/deposit-sdk';
document.getElementById('open-modal').onclick = () => {
openSwapperModal({
integratorId: 'your-integrator-id',
dstChainId: '8453',
dstTokenAddr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
depositWalletAddress: '0x2A018F2506acaEEE2C10632514Fc5DCa9eE2c28A',
modalStyle: {
width: '600px',
height: '800px',
},
onClose: () => {
console.log('Modal closed');
},
});
};
</script>
</body>
</html>React Example (Embedded)
import { useEffect, useRef } from 'react';
import { SwapperIframe } from '@swapper-finance/deposit-sdk';
function SwapperWidget() {
const containerRef = useRef<HTMLDivElement>(null);
const swapperRef = useRef<SwapperIframe | null>(null);
useEffect(() => {
if (containerRef.current && !swapperRef.current) {
swapperRef.current = new SwapperIframe({
container: containerRef.current,
integratorId: 'your-integrator-id',
dstChainId: '8453',
dstTokenAddr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
depositWalletAddress: '0x2A018F2506acaEEE2C10632514Fc5DCa9eE2c28A',
});
}
return () => {
swapperRef.current?.destroy();
swapperRef.current = null;
};
}, []);
return <div ref={containerRef} />;
}