# @socket.tech/bungee

## What is Bungee?

Bungee is a swap and bridge widget for moving tokens across blockchains. The `@socket.tech/bungee` package exposes a React component you can embed in your app.

- Live product: [bungee.exchange](https://bungee.exchange/)
- NPM package: [@socket.tech/bungee](https://www.npmjs.com/package/@socket.tech/bungee)
- Documentation: [docs.bungee.exchange](https://docs.bungee.exchange)

> **v1.0.0 Breaking Change**: The widget no longer bundles wagmi or a wallet provider. Your app provides a minimal wallet adapter via `config.wallet`, and the widget handles routing, reads, writes, balances, receipts, and signing internally.

## Before You Start

Bungee is a widget, not a full wallet/connect solution.

- Provide wallet state and wallet connect UI.
- Mount `QueryClientProvider` above `<Bungee />`.
- Pass a minimal wallet adapter via `config.wallet`.
- Support EVM only, Solana only, Tron only, or any combination by providing the matching adapter methods. **You only need to pass the methods for the chains you support** — e.g. for EVM-only apps, provide `getEVMWalletClient` and `switchChain`; you do not need to pass `getSolanaSigner` or `getTronWeb`.

Minimal React Query setup:

```tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

root.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);
```

## Install

Install the package and its peer dependencies:

```bash
pnpm install @socket.tech/bungee react react-dom viem @tanstack/react-query
```

Peer dependencies are not installed automatically.

This installs the widget, not a wallet/connect solution. You must already have, or choose, wallet infrastructure in your app.

Import the widget styles in your app entrypoint:

```tsx
import "@socket.tech/bungee/styles.css";
import "@socket.tech/bungee/fonts.css";
```

## Quickstart

For a transaction-capable integration, the setup is:

1. Install the package and peer dependencies.
2. Mount `QueryClientProvider`.
3. Import the widget CSS.
4. Add a minimal wallet adapter.
5. Render `<Bungee config={config} />`.

Choose the smallest path that matches your app:

- **EVM only:** provide `accounts`, `getEVMWalletClient`, and usually `switchChain`. You do not need to pass `getSolanaSigner` or `getTronWeb`.
- **Solana only:** provide `accounts`, `getSolanaSigner`, and `rpcs.solana`.
- **Tron only (for deposit/direct-deposit flows):** provide `accounts` (including a TRON account) and `getTronWeb`.
- **EVM + Solana:** combine EVM and Solana adapters in the same `wallet` object.
- **EVM + Tron:** add `getTronWeb` and TRON accounts for Tron-supported routes.

## EVM-Only Example

This is the smallest recommended setup if your app already uses an EVM wallet library such as wagmi.

Your connect modal or connect button still belongs to your app.

```tsx
import { Bungee, type BungeeConfig } from "@socket.tech/bungee";
import { getWalletClient } from "@wagmi/core";
import { useMemo } from "react";
import { useAccount, useConfig, useSwitchChain } from "wagmi";

export function BungeeWidget() {
  const { address, chain, isConnected } = useAccount();
  const { switchChainAsync } = useSwitchChain();
  const wagmiConfig = useConfig();

  const accounts = useMemo(
    () =>
      address && isConnected
        ? [
            {
              address,
              chainType: "EVM" as const,
              chainId: chain?.id,
              isConnected: true,
            },
          ]
        : [],
    [address, chain?.id, isConnected],
  );

  const config: BungeeConfig = {
    // apiKey is optional when using the default public backend; required for dedicated backend
    apiKey: "your-api-key",
    wallet: {
      accounts,
      getEVMWalletClient: async (chainId) => {
        return getWalletClient(wagmiConfig, { chainId });
      },
      switchChain: async (chainId) => {
        await switchChainAsync({ chainId });
        return true;
      },
    },
    eventHandlers: {
      onOpenWalletConnect: () => {
        // Open your app's wallet modal or navigate to your connect screen.
        openYourWalletModal();
      },
    },
  };

  return <Bungee config={config} />;
}
```

## Solana-Only Example

This example assumes Solana wallet-adapter providers are already mounted higher in your app.

```tsx
import { Bungee, type BungeeConfig } from "@socket.tech/bungee";
import { useMemo } from "react";
import { useWallet } from "@solana/wallet-adapter-react";
import { useWalletModal } from "@solana/wallet-adapter-react-ui";

export function BungeeSolanaWidget() {
  const wallet = useWallet();
  const { setVisible } = useWalletModal();

  const accounts = useMemo(
    () =>
      wallet.publicKey
        ? [
            {
              address: wallet.publicKey.toBase58(),
              chainType: "SVM" as const,
              isConnected: wallet.connected,
            },
          ]
        : [],
    [wallet.connected, wallet.publicKey],
  );

  const config: BungeeConfig = {
    // apiKey is optional when using the default public backend; required for dedicated backend
    apiKey: "your-api-key",
    rpcs: {
      solana: "https://api.mainnet-beta.solana.com",
    },
    wallet: {
      accounts,
      getSolanaSigner: async () => {
        if (!wallet.connected) return null;
        return {
          signAndSendTransaction: wallet.sendTransaction ?? undefined,
          signTransaction: wallet.signTransaction ?? undefined,
        };
      },
    },
    eventHandlers: {
      onOpenWalletConnect: () => {
        setVisible(true);
      },
    },
  };

  return <Bungee config={config} />;
}
```

To support both EVM and Solana, combine this signer with the EVM adapter shown above in the same `wallet` object.

## Both EVM and Solana

If your app supports both networks:

- pass both account types in `wallet.accounts`
- provide both `getEVMWalletClient` and `getSolanaSigner`
- keep `switchChain` for EVM
- route `eventHandlers.onOpenWalletConnect(network)` to the correct host wallet modal

## Adding Tron (optional)

To support Tron deposit or direct-deposit flows:

- include TRON accounts in `wallet.accounts` (with `chainType: "TRON"`)
- provide `getTronWeb` returning your app's TronWeb instance (must satisfy the `TronWebLike` interface)
- route `eventHandlers.onOpenWalletConnect("tron")` to open your Tron wallet/connect UI

If you do not pass `getTronWeb`, the widget works as before; Tron routes simply won't be available for sending.

## Configuration Reference

### BungeeConfig Interface

#### Required Properties

- `wallet` (WalletAdapter): Minimal wallet adapter for connected-wallet flows. **Only this property is mandatory.** See [Wallet Adapter](#wallet-adapter).

#### Optional Properties (required by backend type)

- `apiKey` (string): Your Bungee API key. Optional when using the default **public** backend. **Required** when using the **dedicated** backend URL.
- `baseUrl` (string): API base URL. If not passed, the widget uses the **public** backend: `https://public-backend.bungee.exchange/api/v1`. See [Base URL and API key / affiliate requirements](#base-url-and-api-key--affiliate-requirements).
- `affiliateId` (string): For integrator tracking. Optional for the **public** backend. **Required** for the **backend** and **dedicated** URLs.
- `theme` (Theme): Theming configuration. See [Theme Configuration](#theme-configuration).
- `widgetTitle` (string): Optional title shown in the widget header. Default is `"Swap"`. Only set if you need to customize the header label.
- `rpcs` (object): Custom RPC URLs (all optional)
  - `solana` (string): Solana RPC URL. Only needed if you want Solana support and pass `getSolanaSigner`.
  - `eip155` (string): Custom Ethereum Mainnet RPC URL for transactions.
- `initialValues` (object): Default values
  - `fromChain` (number): Default source chain ID
  - `toChain` (number): Default destination chain ID
  - `inputTokens` (string[]): Default input token addresses
  - `outputTokens` (string[]): Default output token addresses
  - `amount` (string[]): Default amounts
  - `outputRatio` (number[]): Output ratios
  - `locale` (SupportedLocale): UI locale (currently only `"en-US"`)
  - `sortPreference` (`"fast" | "best"`): Route sorting preference
  - `swapSlippage` (number): Default slippage tolerance
- `supportedTokens` (object): Restrict available tokens. See [Token Configuration Details](#token-configuration-details).
  - `from` (object): Tokens by chain ID for source. Structure: `{ [chainId: string]: Token[] }`
  - `to` (object): Tokens by chain ID for destination. Structure: `{ [chainId: string]: Token[] }`
- `supportedChains` (object): Restrict available chains
  - `from` (number[]): Available source chains
  - `to` (number[]): Available destination chains
- `eventHandlers` (EventHandlers): Event callbacks. See [Event Handlers](#event-handlers).
- `feeParams` (object): Integrator fee configuration
  - `feeTakerAddress` (string): Wallet address that receives the fee
  - `feeBps` (number): Fee in basis points (for example `10 = 0.1%`)
- `features` (object): Feature flags
  - `internalToasts` (boolean): Show or hide internal toasts (default: `true`)
  - `internalTxHistory` (boolean): Show or hide transaction history (default: `true`)
  - `internalInflight` (boolean): Show or hide the inflight screen as a non-blocking toast (default: `true`)
  - `autoRouting` (boolean): Auto-routing feature (not implemented)
  - `refuel` (boolean): Refuel feature (not implemented)
- `postSwapAction` (object): Customize the post-swap screen
  - `ctaLabel` (string): Label for the call-to-action button
  - `ctaAction` ((hash: string) => Promise\<void\>): Async action triggered when the CTA button is clicked
  - `bannerAddon` (ReactNode): Custom React element to render in the post-swap banner
  - `bannerAddonClassNames` (string): CSS class names for the banner addon container
  - `onSwapCompleted` ((hash: string) => void): Callback fired when a swap completes successfully
  - `isPending` (boolean): Whether the CTA action is in progress

### Base URL and API key / affiliate requirements

The widget supports three backend URLs. Requirements for `apiKey` and `affiliateId` depend on which URL you use (or the default when you omit `baseUrl`):

| Base URL | apiKey | affiliateId |
|----------|--------|-------------|
| **Public** (default) — `https://public-backend.bungee.exchange/api/v1` | Optional | Optional |
| **Backend** — `https://backend.bungee.exchange/api/v1` | Optional | **Required** |
| **Dedicated** — `https://dedicated-backend.bungee.exchange/api/v1` | **Required** | **Required** |

- If you do not pass `baseUrl`, the widget uses the **public** backend. No API key or affiliate ID is required.
- For the **backend** URL, you must provide `affiliateId` in config; the widget will show a validation error otherwise.
- For the **dedicated** URL, you must provide both `apiKey` and `affiliateId`; the widget will show a validation error if either is missing.
- Using an unsupported or invalid `baseUrl` may result in runtime validation errors or API failures.

## Wallet Adapter

The `wallet` object is the only host integration point the widget needs. You provide connected accounts and the network-specific wallet hooks you support; the widget handles the rest internally.

| Property | Type | Required | Description |
|---|---|---|---|
| `accounts` | `AccountInfo[]` | Yes | Connected wallet accounts |
| `getEVMWalletClient` | `(chainId: number) => Promise<WalletClient \| null>` | EVM only | Returns a viem WalletClient for the requested chain |
| `getSolanaSigner` | `() => Promise<SolanaSigner \| null>` | Solana only | Returns a Solana signer for signing transactions |
| `getTronWeb` | `() => TronWebLike \| undefined` | Tron only | Returns the wallet-connected TronWeb instance for Tron transactions and deposit flows |
| `switchChain` | `(chainId: number, account?) => Promise<boolean>` | Recommended for EVM | Switches the wallet's active EVM chain |

### What the widget handles internally

When you provide `getEVMWalletClient`, the widget automatically implements:

- `sendTransaction` (including EIP-5792 batched calls)
- `signTypedData`
- `writeContract`
- `readContract` (via viem public clients)
- `getBalance` (native + ERC-20)
- `getTransactionReceipt`
- `getBytecode`
- `getEnsAddress`
- `getWalletCapabilities` (EIP-5792)
- `getCallsStatus` (EIP-5792)

When you provide `getSolanaSigner` and `config.rpcs.solana`, the widget additionally handles:

- Solana transaction building and sending (VersionedTransaction with lookup tables)
- SOL and SPL token balance fetching (including Token-2022)
- Solana transaction receipt polling

When you provide `getTronWeb`, the widget additionally handles:

- Tron transaction building, signing, and broadcasting (for deposit and direct-deposit flows)
- TRX and TRC-20 balance and transaction receipt via the host-supplied TronWeb instance

### accounts

An array of currently connected wallet accounts:

```typescript
accounts: Array<{
  address: string;
  chainType: "EVM" | "SVM" | "TRON";
  chainId?: number;
  isConnected: boolean;
  connector?: {
    id?: string;
    name?: string;
    icon?: string;
  };
}>
```

### getEVMWalletClient

Returns a [viem WalletClient](https://viem.sh/docs/clients/wallet) for the requested chain. The widget uses this to send transactions, sign typed data, write contracts, and query EIP-5792 capabilities.

```typescript
getEVMWalletClient: (chainId: number) => Promise<WalletClient | null>
```

The return type is `any` in the adapter interface so hosts are free to use any viem version; the widget casts internally.

### getSolanaSigner

Returns a Solana signer object. The widget builds the transaction internally (instructions, lookup tables, blockhash) and passes it to the signer. If the signer provides `signAndSendTransaction`, the widget prefers it; otherwise it falls back to `signTransaction` + `sendRawTransaction` via its internal Connection.

```typescript
getSolanaSigner: () => Promise<{
  signAndSendTransaction?: (...args: any[]) => Promise<any>;
  signTransaction?: (...args: any[]) => Promise<any>;
} | null>
```

### getTronWeb

Returns the wallet-connected TronWeb instance from your app. The widget uses it to build, sign, and broadcast Tron transactions for deposit and direct-deposit flows. The instance must implement the minimal `TronWebLike` interface (see type export). If you do not support Tron, omit this property.

```typescript
getTronWeb: () => TronWebLike | undefined
```

### switchChain

Switches the wallet's active EVM chain. The optional `account` parameter targets a specific wallet when multiple are connected.

Treat this as a success-or-throw contract in practice:

- resolve when the chain switch succeeds
- reject or throw if the switch is cancelled or fails

```typescript
switchChain: (
  chainId: number,
  account?: { address?: string; chainType: "EVM" | "SVM" | "TRON" }
) => Promise<boolean>
```

## Token Configuration Details

### Token Interface

When using `supportedTokens`, tokens must follow this structure:

```typescript
interface Token {
  address: string;
  chainId: number;
  decimals: number;
  logoURI: string;
  name: string;
  symbol: string;
  isVerified?: boolean; // Must be true to avoid warnings
}
```

### Using supportedTokens

Important notes:

- When `supportedTokens` are provided, the widget will not automatically set a default token.
- To show a default source token, you must pass `initialValues.fromChain` and `initialValues.inputTokens`.
- Set `isVerified: true` for tokens in `supportedTokens` to avoid warnings.
- `inputTokens` addresses must match tokens from your `supportedTokens.from[chainId]` list.

### Fetching Token Data

When using `supportedTokens`, you can fetch token data from the Bungee API endpoints:

- `/tokens/list`: Returns a curated list of trending tokens (verified) or the complete token list
- `/tokens/search`: Search for tokens by address, name, or symbol

Tokens fetched from these endpoints include the `isVerified` field. For detailed request and response formats, see the [Bungee API token endpoints documentation](https://docs.bungee.exchange/bungee-api/integration-guides/additional-guides/bungee-token-endpoints).

## Theme Configuration

Customize the widget appearance with the `theme` property:

```tsx
const theme = {
  width: 420, // or "full" for full width
  borderRadius: "base", // "none" | "sm" | "base" | "md" | "lg"
  fonts: {
    primary: "Inter, sans-serif",
    secondary: "Roboto, sans-serif",
  },
  colors: {
    text: {
      primary: "#FFFFFF",
      secondary: "#A0A0A0",
      button: "#000000",
      theme: "#3B82F6",
      accent1: "#10B981",
      accent2: "#F59E0B",
      accent3: "#EF4444",
      accent4: "#8B5CF6",
    },
    bg: {
      layer1: "#1F2937",
      layer2: "#374151",
      layer3: "#4B5563",
      accent1: "#10B981",
      accent3: "#EF4444",
      accent4: "#8B5CF6",
      main: "#111827",
      theme: "#3B82F6",
    },
    border: {
      strong: "#6B7280",
      theme: "#3B82F6",
    },
    icon: {
      primary: "#FFFFFF",
      secondary: "#A0A0A0",
      theme: "#3B82F6",
    },
  },
};
```

Colors support both hex (`#FFFFFF`) and RGB (`rgb(255, 255, 255)`) formats.

## Event Handlers

Handle user interactions and widget events:

```tsx
const eventHandlers = {
  onFromTokenChange: (token: Token) => {
    console.log("Source token changed:", token);
  },
  onToTokenChange: (token: Token) => {
    console.log("Destination token changed:", token);
  },
  onFromChainChange: (chain: Chain) => {
    console.log("Source chain changed:", chain);
  },
  onToChainChange: (chain: Chain) => {
    console.log("Destination chain changed:", chain);
  },
  onEvent: (eventType: "success" | "error" | "warning", eventData: EventData) => {
    console.log("Widget event:", eventType, eventData);
  },
  onOpenWalletConnect: (network: "solana" | "eip155" | "tron") => {
    openWalletModal(network);
  },
  onSwapInitiated: (data: { chainId: number; hash: string; type: OrderFlowType }) => {
    console.log("Swap initiated:", data);
  },
  onTransactionStatusChange: (data: { requestHash: string; statusCode: number }) => {
    console.log("Transaction status:", data);
  },
  logEvent: (log: { event: Event; error: unknown; context?: Record<string, unknown> }) => {
    console.log("Analytics event:", log);
  },
  toggleExternalHistorySection: () => {
    toggleHistorySidebar();
  },
  showToast: (title, message, type, duration) => {
    // Display a custom toast notification
  },
};
```

## Imperative API

The imperative API allows parent components to control widget state programmatically.

Use case example: when a user clicks a history item in a sidebar, the parent can navigate the widget to the inflight screen for that transaction.

```tsx
import { useRef } from "react";
import { Bungee, type BungeeImperativeAPIType } from "@socket.tech/bungee";

export default function App() {
  const bungeeRef = useRef<BungeeImperativeAPIType>(null);

  const handleSetInflightData = (data: OrderData) => {
    bungeeRef.current?.setInflightData(data);
  };

  return <Bungee config={config} ref={bungeeRef} />;
}
```

Available method:

- `setInflightData(data: OrderData)`: Track pending transactions and navigate to inflight screen

## Type Exports

```tsx
import type {
  BungeeConfig,
  BungeeImperativeAPIType,
  WalletAdapter,
  TronWebLike,
} from "@socket.tech/bungee";
```

## Migration from v0.25.x

### What Changed

In v0.25.x (up to v0.25.4), the widget bundled its own wallet provider (wagmi + Reown/WalletConnect). You did not need to pass wallet configuration; the widget managed connections, chain switching, transactions, and contract reads internally.

In v1.0.0, the widget is wallet-provider-agnostic. Your app owns wallet connection state and UI, and Bungee only consumes the minimal adapter you pass through `config.wallet`.

| | v0.25.x | v1.0.0 |
|---|---|---|
| Wallet provider | Built-in (wagmi + Reown) | Your choice |
| Config | `apiKey` only | `wallet` required; `apiKey` optional for default public backend |
| Wallet connections | Widget handled internally | Host app handles via `onOpenWalletConnect` |
| Blockchain reads | Widget handled internally | Widget handles internally via viem public clients |
| Blockchain writes | Widget handled internally | Widget handles internally via `getEVMWalletClient` or `getSolanaSigner` |

### Migration Checklist

1. Remove old assumptions that the widget will open or manage wallets for you.
2. Add `QueryClientProvider` above `<Bungee />`.
3. Build `wallet.accounts` from your host wallet provider.
4. Provide `getEVMWalletClient` for EVM support.
5. Provide `switchChain` for EVM chain switching. Reject or throw if switching fails.
6. Provide `getSolanaSigner` and `rpcs.solana` for Solana support.
7. Implement `eventHandlers.onOpenWalletConnect` so the widget can ask your app to open the correct wallet modal.

## Troubleshooting

- React Query error or `No QueryClient set`
  Mount `QueryClientProvider` above `<Bungee />`.

- Clicking connect does nothing
  Implement `eventHandlers.onOpenWalletConnect`.

- Transactions fail because no wallet client is available for the selected chain
  Make sure `getEVMWalletClient(chainId)` returns a client for the requested chain, not only the currently active chain.

- Chain switch handling feels inconsistent
  Make `switchChain` reject or throw on cancellation or failure instead of silently resolving unsuccessfully.

- Solana actions fail immediately
  Mount Solana wallet-adapter providers higher in the app and pass both `getSolanaSigner` and `rpcs.solana`.

- Tron deposit/direct-deposit fails or is unavailable
  Pass `getTronWeb` returning a TronWeb instance that implements `TronWebLike`, and include TRON accounts in `wallet.accounts`. If you do not need Tron, you can omit `getTronWeb`.

- Widget has no styling
  Import `@socket.tech/bungee/styles.css` and `@socket.tech/bungee/fonts.css`.

## Links

- Web App: [bungee.exchange](https://bungee.exchange/)
- Bungee Lite Package: [@socket.tech/bungee](https://www.npmjs.com/package/@socket.tech/bungee)
- Bungee Protocol Docs: [docs.bungee.exchange/](https://docs.bungee.exchange)
