otx-swap
v1.2.37
Published
A lightweight, customizable React widget for cross-chain token swaps powered by Optimex protocol.
Downloads
118
Readme
OTX Swap Widget
A lightweight, customizable React widget for cross-chain token swaps powered by Optimex protocol.
Features
- Cross-chain swaps between EVM, Bitcoin, and Solana networks
- Bring Your Own Wallet (BYOW) - integrate with your existing wallet infrastructure
- Fully customizable theming
- TypeScript support with full type definitions
- Lightweight bundle (~313KB gzipped: ~72KB)
- Lifecycle callbacks for tracking swap progress
Installation
npm install otx-swap
# or
yarn add otx-swap
# or
pnpm add otx-swapPeer Dependencies
npm install react react-dom @tanstack/react-queryQuick Start
import { useState, useEffect, useMemo } from "react";
import { SwapWidget, createEvmWalletClient } from "otx-swap";
import type { ExternalWalletClient, EvmWalletAccount, Eip1193Provider } from "otx-swap";
function App() {
const [evmWallet, setEvmWallet] = useState<EvmWalletAccount | null>(null);
// Create wallet client from EIP-1193 provider (MetaMask, WalletConnect, etc.)
useEffect(() => {
const initWallet = async () => {
if (window.ethereum) {
try {
const wallet = await createEvmWalletClient({
provider: window.ethereum as Eip1193Provider,
});
setEvmWallet(wallet);
} catch (error) {
console.error("Failed to create wallet:", error);
}
}
};
initWallet();
}, []);
const walletClient = useMemo((): ExternalWalletClient => {
if (!evmWallet) return {};
return { evm: evmWallet };
}, [evmWallet]);
return (
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
onRequestWallet={(networkId) => {
// Called when user needs to connect wallet
console.log("Connect wallet for network:", networkId);
}}
/>
);
}API Reference
SwapWidget Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| apiBaseUrl | string | Yes | - | Optimex API base URL |
| walletClient | ExternalWalletClient | Yes | - | Connected wallet client object |
| onRequestWallet | (networkId?: string) => void | No | - | Callback when wallet connection is needed |
| theme | PartialThemeConfig | No | - | Custom theme configuration |
| defaultFromToken | string | No | - | Default source token ID |
| defaultToToken | string | No | - | Default destination token ID |
| defaultSlippage | number | No | 0.75 | Default slippage tolerance (%) |
| hideHistory | boolean | No | false | Hide transaction history button |
| affiliateFee | number | No | - | Affiliate fee in basis points |
| affiliateAddress | string | No | - | Affiliate fee recipient address |
| affiliateProvider | string | No | - | Affiliate provider identifier |
| tradeTimeout | number | No | - | Trade timeout in minutes |
| scriptTimeout | number | No | - | Script timeout in minutes |
| queryClient | QueryClient | No | - | Shared React Query client |
| renderLoading | ComponentType | No | - | Custom loading component |
| renderError | ComponentType | No | - | Custom error component |
| children | ReactNode | No | - | Custom content instead of default SwapCard |
Lifecycle Callbacks
Track swap progress with these callbacks:
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
onQuote={(data) => {
// Called when quote is received
console.log("Quote received:", data.quote.trade_id);
}}
onWalletTx={(data) => {
// Called when wallet transaction is sent
console.log("Wallet TX:", data.tx_hash);
}}
onSubmitTx={(data) => {
// Called when transaction is submitted to network
console.log("Submit TX status:", data.status);
}}
onSwapComplete={(data) => {
// Called when swap is completed successfully
console.log("Swap complete:", data.trade.trade_id);
}}
onError={(data) => {
// Called when an error occurs
console.error("Error at stage:", data.stage, data.error);
}}
/>Callback Types
interface QuoteCallbackData {
quote: Quote;
from_token: Token;
to_token: Token;
amount_in: string;
}
interface WalletTxCallbackData {
trade_id: string;
tx_hash: string;
from_token: Token;
to_token: Token;
amount_in: string;
}
interface SubmitTxCallbackData {
trade_id: string;
tx_hash: string;
status: "success" | "error";
error?: string;
}
interface SwapCompleteCallbackData {
trade: Trade;
tx_hash: string;
}
interface SwapErrorCallbackData {
error: Error;
stage: "quote" | "approval" | "wallet_tx" | "submit_tx" | "tracking";
trade_id?: string;
}Wallet Integration
ExternalWalletClient Interface
The widget requires a wallet client that implements the ExternalWalletClient interface:
interface ExternalWalletClient {
evm?: EvmWalletAccount; // EVM wallet (Ethereum, Polygon, etc.)
btc?: WalletAccount; // Bitcoin wallet
solana?: WalletAccount; // Solana wallet
}EVM Wallet
interface EvmWalletAccount {
address: string;
publicKey: string;
chain: {
id: number;
name: string;
type: "EVM";
};
sendTransaction: (tx: EvmTransactionRequest) => Promise<string>;
getChainId: () => Promise<number>;
switchChain: (chainId: number) => Promise<void>;
}
interface EvmTransactionRequest {
to: `0x${string}`;
value?: bigint;
data?: `0x${string}`;
chainId?: number;
}Bitcoin Wallet
interface WalletAccount {
address: string;
publicKey: string; // BTC public key (different from address)
chain: {
id: string; // "mainnet" or "testnet"
name: string;
type: "BTC";
};
sendTransaction: (tx: BitcoinTransactionRequest) => Promise<string>;
}
interface BitcoinTransactionRequest {
toAddress: string;
amount: bigint;
options?: {
feeRate?: number;
memo?: string;
memos?: string[];
};
}Solana Wallet
interface WalletAccount {
address: string;
publicKey: string;
chain: {
id: string;
name: string;
type: "Solana";
};
sendTransaction: (tx: unknown) => Promise<string>;
}EVM Helpers
We provide two helper functions to convert any EIP-1193 provider to the required wallet interface:
createEvmWalletClient (Recommended)
Async version that automatically fetches address and chainId from the provider if not provided:
import { createEvmWalletClient } from "otx-swap";
import type { EvmWalletAccount, Eip1193Provider } from "otx-swap";
// Simplest usage - just pass the provider
const evmWallet = await createEvmWalletClient({
provider: window.ethereum as Eip1193Provider,
});
// Or with optional overrides
const evmWallet = await createEvmWalletClient({
provider: window.ethereum as Eip1193Provider,
chainName: "Ethereum", // Optional
});createEvmWalletClientSync
Sync version that requires all parameters (use when you already have address and chainId):
import { createEvmWalletClientSync } from "otx-swap";
import type { ExternalWalletClient } from "otx-swap";
const walletClient: ExternalWalletClient = {
evm: createEvmWalletClientSync({
provider: window.ethereum, // Any EIP-1193 provider
address: "0x...", // Required
chainId: 1, // Required
chainName: "Ethereum", // Optional
}),
};Integration Examples
With wagmi (EVM) - Recommended
Using createEvmWalletClient with wagmi's connector provider:
import { useState, useEffect, useMemo } from "react";
import { useAccount, useChainId } from "wagmi";
import { SwapWidget, createEvmWalletClient } from "otx-swap";
import type { ExternalWalletClient, EvmWalletAccount, Eip1193Provider } from "otx-swap";
function SwapWithWagmi() {
const { isConnected, connector } = useAccount();
const chainId = useChainId();
const [evmWallet, setEvmWallet] = useState<EvmWalletAccount | null>(null);
// Create wallet client from provider (auto-fetches address & chainId)
useEffect(() => {
if (connector?.getProvider && isConnected) {
connector.getProvider().then(async (provider) => {
try {
const wallet = await createEvmWalletClient({
provider: provider as Eip1193Provider,
});
setEvmWallet(wallet);
} catch (error) {
console.error("Failed to create wallet client:", error);
setEvmWallet(null);
}
});
} else {
setEvmWallet(null);
}
}, [connector, isConnected, chainId]); // Recreate on chain change
const walletClient = useMemo((): ExternalWalletClient => {
if (!isConnected || !evmWallet) return {};
return { evm: evmWallet };
}, [isConnected, evmWallet]);
return (
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
onRequestWallet={() => {
// Open your wallet connect modal
}}
/>
);
}With wagmi (EVM) - Using wagmi Hooks
Alternative approach using wagmi's transaction hooks instead of the helper function:
import { useMemo } from "react";
import { useAccount, useSendTransaction, useChainId, useSwitchChain } from "wagmi";
import { SwapWidget } from "otx-swap";
import type { ExternalWalletClient, EvmTransactionRequest } from "otx-swap";
function SwapWithWagmi() {
const { address, isConnected } = useAccount();
const chainId = useChainId();
const { sendTransactionAsync } = useSendTransaction();
const { switchChainAsync } = useSwitchChain();
const walletClient = useMemo((): ExternalWalletClient => {
if (!isConnected || !address) return {};
return {
evm: {
address,
publicKey: address,
chain: { id: chainId, name: "Ethereum", type: "EVM" },
sendTransaction: async (tx: unknown) => {
const evmTx = tx as EvmTransactionRequest;
return sendTransactionAsync({
to: evmTx.to,
value: evmTx.value,
data: evmTx.data,
});
},
getChainId: async () => chainId,
switchChain: async (id) => {
await switchChainAsync({ chainId: id });
},
},
};
}, [isConnected, address, chainId, sendTransactionAsync, switchChainAsync]);
return (
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
/>
);
}With Unisat (Bitcoin)
import { SwapWidget } from "otx-swap";
import type { ExternalWalletClient, BitcoinTransactionRequest } from "otx-swap";
function SwapWithUnisat() {
const [btcAddress, setBtcAddress] = useState<string | null>(null);
const [btcPublicKey, setBtcPublicKey] = useState<string | null>(null);
const connectUnisat = async () => {
const unisat = (window as any).unisat;
const accounts = await unisat.requestAccounts();
const pubKey = await unisat.getPublicKey();
setBtcAddress(accounts[0]);
setBtcPublicKey(pubKey);
};
const walletClient: ExternalWalletClient = {
btc: btcAddress && btcPublicKey ? {
address: btcAddress,
publicKey: btcPublicKey,
chain: { id: "mainnet", name: "Bitcoin", type: "BTC" },
sendTransaction: async (tx: unknown) => {
const btcTx = tx as BitcoinTransactionRequest;
const unisat = (window as any).unisat;
return unisat.sendBitcoin(btcTx.toAddress, Number(btcTx.amount));
},
} : undefined,
};
return (
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
onRequestWallet={() => connectUnisat()}
/>
);
}Theming
Customize the widget appearance with the theme prop:
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
theme={{
colors: {
primary: "#3B82F6",
primaryHover: "#2563EB",
background: "#0F172A",
surface: "#1E293B",
surfaceHover: "#334155",
text: "#F8FAFC",
textSecondary: "#94A3B8",
border: "#334155",
success: "#22C55E",
error: "#EF4444",
warning: "#F59E0B",
},
borderRadius: "md", // "none" | "sm" | "md" | "lg" | "full"
fontFamily: "Inter, sans-serif",
}}
/>Theme Colors
| Color | Description |
|-------|-------------|
| primary | Primary action color (buttons, links) |
| primaryHover | Primary color on hover |
| secondary | Secondary accent color |
| background | Widget background color |
| surface | Card/panel background color |
| surfaceHover | Surface color on hover |
| text | Primary text color |
| textSecondary | Secondary/muted text color |
| border | Border color |
| success | Success state color |
| error | Error state color |
| warning | Warning state color |
Custom Components
Custom Loading Component
import { SwapWidget } from "otx-swap";
import type { CustomLoadingProps } from "otx-swap";
function MyLoadingSpinner({ className }: CustomLoadingProps) {
return (
<div className={className}>
<span>Loading...</span>
</div>
);
}
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
renderLoading={MyLoadingSpinner}
/>Custom Error Component
import type { CustomErrorProps } from "otx-swap";
function MyErrorComponent({ error, onRetry, className }: CustomErrorProps) {
return (
<div className={className}>
<p>Error: {error?.message}</p>
<button onClick={onRetry}>Retry</button>
</div>
);
}
<SwapWidget
apiBaseUrl="https://api.optimex.io"
walletClient={walletClient}
renderError={MyErrorComponent}
/>Standalone Components
The widget also exports standalone components for custom layouts:
import { SwapCard, ConnectButton, useWallet } from "otx-swap";
// Use ConnectButton separately
<ConnectButton
connectLabel="Connect Wallet"
showNetworkBadge={true}
onClick={() => {
// Custom connect logic
}}
/>TypeScript
Full TypeScript support with exported types:
import {
// Helper functions
createEvmWalletClient, // Async version (auto-fetches from provider) - Recommended
createEvmWalletClientSync, // Sync version (requires address & chainId)
} from "otx-swap";
import type {
// Props
SwapWidgetProps,
// Wallet types
ExternalWalletClient,
WalletAccount,
EvmWalletAccount,
ChainInfo,
Eip1193Provider,
CreateEvmWalletClientOptions, // For async version
CreateEvmWalletClientSyncOptions, // For sync version
// Transaction types
EvmTransactionRequest,
BitcoinTransactionRequest,
// Data types
Token,
Quote,
Trade,
TradeState,
NetworkType,
// Theme types
ThemeConfig,
PartialThemeConfig,
ThemeColors,
// Callback types
QuoteCallbackData,
WalletTxCallbackData,
SubmitTxCallbackData,
SwapCompleteCallbackData,
SwapErrorCallbackData,
SwapCallbacks,
} from "otx-swap";Browser Support
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
License
MIT
