# Widget

### Key Features

* **Quick Setup** — Add swaps to your app in under 5 minutes
* **Three Display Modes** — Integrated (inline), Widget (floating button), or Modal (popup)
* **Fully Customizable** — Theme colors, default tokens, locale, slippage, and more
* **Multi-Language** — Supports 11 locales out of the box
* **Wallet Support** — Built-in wallet connection UI, or bring your own

### Display Modes

| Mode         | Description                                                                |
| ------------ | -------------------------------------------------------------------------- |
| `INTEGRATED` | Renders inline inside a target `<div>` you provide                         |
| `WIDGET`     | Renders a floating button (configurable corner). Click to open the swap UI |
| `MODAL`      | Opens a centered popup dialog with a backdrop overlay                      |

### Supported Frameworks

* Plain HTML / Vanilla JS
* React (Vite, CRA)
* Next.js (App Router & Pages Router)
* Any framework that can run JavaScript

### Getting an API Key

A default public API key is available for development and typical usage:

```
a4^KV_EaTf4MW#ZdvgGKX#HUD^3IFEAOV_kzpIE^3BQGA8pDnrkT7JcIy#HNlLGi
```

For production or high-traffic use cases, open a ticket on [Discord](https://discord.gg/panora) to request a dedicated key.

### Installation

**npm**

```bash
npm install @panoraexch/widget-sdk
```

**yarn**

```bash
yarn add @panoraexch/widget-sdk
```

**pnpm**

```bash
pnpm add @panoraexch/widget-sdk
```

**CDN (no bundler needed)**

```bash
<script src="https://assets.panora.exchange/widget-v1.js" defer></script>
```

<figure><img src="https://1242588619-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyERsnod2kLzIe1dirWBH%2Fuploads%2FHdm5DEVBhV2EM4nWuqpB%2Fimage.png?alt=media&#x26;token=f3545627-a078-4a04-8da6-6e96177ae1bc" alt=""><figcaption></figcaption></figure>

### Quick Start

#### Option A: NPM (React / Next.js)

```typescript
import { init, close } from "@panoraexch/widget-sdk";

// Open the widget
await init({
  displayMode: "INTEGRATED",
  integratedTargetId: "my-swap-container",
  panoraApiKey: "YOUR_API_KEY",
});

// Later, tear it down
close();

```

#### Option B: CDN (plain HTML)

```html
<script src="https://assets.panora.exchange/widget-v1.js" defer></script>
<div id="my-swap-container"></div>

<script>
  window.addEventListener("load", function () {
    PanoraWidget.render("#my-swap-container", {
      displayMode: "INTEGRATED",
      panoraApiKey: "YOUR_API_KEY",
    });
  });
</script>

```

## React App Example

A complete guide to integrating Panora Widget SDK into a React + Vite application.

### 1. Create a New Project

```bash
npm create vite@latest my-app -- --template react-ts
cd my-app
```

### 2. Install the SDK

```bash
npm install @panoraexch/widget-sdk
```

### 3. Add the Widget

Replace `src/App.tsx` with:

```tsx
import { useEffect, useState } from "react";
import { init, close } from "@panoraexch/widget-sdk";

const API_KEY = import.meta.env.VITE_PANORA_API_KEY || "";
const DEFAULT_FROM = "0xa" as const;
const DEFAULT_TO =
  "0x..." as const;

type Tab = "INTEGRATED" | "WIDGET" | "MODAL";

export default function App() {
  const [activeTab, setActiveTab] = useState<Tab>("INTEGRATED");

  useEffect(() => {
    // Clean up any previous instance
    close();

    // MODAL is opened on-demand, not on mount
    if (activeTab === "MODAL") return;

    const timeout = setTimeout(() => {
      if (activeTab === "INTEGRATED") {
        init({
          panoraApiKey: API_KEY,
          displayMode: "INTEGRATED",
          integratedTargetId: "panora-widget-container",
          defaultFromTokenAddress: DEFAULT_FROM,
          defaultToTokenAddress: DEFAULT_TO,
          locale: "en",
          showNotifications: true,
        });
      } else if (activeTab === "WIDGET") {
        init({
          panoraApiKey: API_KEY,
          displayMode: "WIDGET",
          defaultFromTokenAddress: DEFAULT_FROM,
          defaultToTokenAddress: DEFAULT_TO,
          locale: "en",
          widgetBtnDirection: "right-bottom",
        });
      }
    }, 100);

    return () => {
      clearTimeout(timeout);
      close();
    };
  }, [activeTab]);

  return (
    <div>
      <h1>My App</h1>

      {/* ── Integrated Mode ── */}
      {activeTab === "INTEGRATED" && (
        <div id="panora-widget-container" />
      )}

      {/* ── Modal Mode — open / close on demand ── */}
      {activeTab === "MODAL" && (
        <div>
          <button
            onClick={() =>
              init({
                panoraApiKey: API_KEY,
                displayMode: "MODAL",
                defaultFromTokenAddress: DEFAULT_FROM,
                defaultToTokenAddress: DEFAULT_TO,
                locale: "en",
              })
            }
          >
            Open Swap Modal
          </button>
          <button onClick={() => close()}>Close</button>
        </div>
      )}
    </div>
  );
}
```

### 4. Run

```bash
npm run dev
```

### Using the React Component

If you prefer a declarative approach instead of the imperative `init()` / `close()` API, the SDK also exports a `<PanoraWidget />` React component:

```tsx
import { PanoraWidget } from "@panoraexch/widget-sdk";

export default function App() {
  return (
    <PanoraWidget
      widgetConfig={{
        panoraApiKey: "YOUR_API_KEY",
        defaultFromTokenAddress: "0xa",
        defaultToTokenAddress:
          "0x...",
        locale: "en",
        showNotifications: true,
      }}
    />
  );
}
```

> **Note:** `<PanoraWidget />` always renders in `INTEGRATED` mode. For `WIDGET` or `MODAL` modes, use the imperative `init()` / `close()` API.

### Key Points

* Always call `close()` before re-initializing to avoid duplicate instances
* The `100ms` timeout gives the DOM time to settle before the widget mounts
* Store your API key in `.env` as `VITE_PANORA_API_KEY`

***

## Next.js Example (App Router)

A complete guide to integrating Panora Widget SDK into a Next.js App Router application.

### 1. Create a New Project

```bash
npx create-next-app@latest my-app --typescript
cd my-app
```

### 2. Install the SDK

```bash
npm install @panoraexch/widget-sdk
```

### 3. Add the Widget

Create or replace `src/app/page.tsx`:

```tsx
"use client";

import { useEffect, useState } from "react";
import { init, close } from "@panoraexch/widget-sdk";

const API_KEY = process.env.NEXT_PUBLIC_PANORA_API_KEY || "";
const DEFAULT_FROM = "0xa" as const;
const DEFAULT_TO =
  "0x..." as const;

export default function Home() {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    setReady(true);
  }, []);

  useEffect(() => {
    if (!ready) return;

    init({
      panoraApiKey: API_KEY,
      displayMode: "INTEGRATED",
      integratedTargetId: "panora-widget-container",
      defaultFromTokenAddress: DEFAULT_FROM,
      defaultToTokenAddress: DEFAULT_TO,
      locale: "en",
      showNotifications: true,
    });

    return () => {
      close();
    };
  }, [ready]);

  return (
    <main>
      <h1>My App</h1>
      <div id="panora-widget-container" />
    </main>
  );
}
```

> **Important:** The `"use client"` directive is required. The widget uses browser APIs (`document`, `window`) and cannot run during server-side rendering.

### Widget Mode Example

```tsx
"use client";

import { useEffect } from "react";
import { init, close } from "@panoraexch/widget-sdk";

export default function WidgetPage() {
  useEffect(() => {
    init({
      panoraApiKey: process.env.NEXT_PUBLIC_PANORA_API_KEY || "",
      displayMode: "WIDGET",
      widgetBtnDirection: "right-bottom",
      locale: "en",
    });

    return () => close();
  }, []);

  return (
    <main>
      <h1>Widget floating in the corner</h1>
    </main>
  );
}
```

### Modal Mode Example

```tsx
"use client";

import { init, close } from "@panoraexch/widget-sdk";

export default function ModalPage() {
  return (
    <main>
      <button
        onClick={() =>
          init({
            panoraApiKey: process.env.NEXT_PUBLIC_PANORA_API_KEY || "",
            displayMode: "MODAL",
            locale: "en",
          })
        }
      >
        Open Swap Modal
      </button>
      <button onClick={() => close()}>Close</button>
    </main>
  );
}
```

### Next.js Pages Router

If you use the Pages Router instead of App Router, the code is nearly identical — just drop the `"use client"` directive and place your file in `src/pages/index.tsx`. No other changes needed.

### Key Points

* Store your API key in `.env.local` as `NEXT_PUBLIC_PANORA_API_KEY`
* Always call `close()` in the `useEffect` cleanup to prevent memory leaks
* The widget loads its UI from the CDN at runtime, so your Next.js bundle size is unaffected

***

## HTML App Example

The simplest integration — no bundler, no framework, just a single `<script>` tag.

### 1. Create an HTML File

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Panora Swap</title>
    <script src="https://assets.panora.exchange/widget-v1.js" defer></script>
  </head>
  <body>
    <h1>My App</h1>
    <div id="panora-widget-container"></div>

    <script>
      window.addEventListener("load", function () {
        PanoraWidget.render("#panora-widget-container", {
          panoraApiKey: "YOUR_API_KEY",
          displayMode: "INTEGRATED",
          defaultFromTokenAddress: "0xa",
          defaultToTokenAddress:
            "0x...",
          locale: "en",
          showNotifications: true,
        });
      });
    </script>
  </body>
</html>
```

### 2. Serve It

Use any static file server:

```bash
npx http-server .
```

### Widget Mode (Floating Button)

```html
<script>
  window.addEventListener("load", function () {
    PanoraWidget.render(document.body, {
      panoraApiKey: "YOUR_API_KEY",
      displayMode: "WIDGET",
      widgetBtnDirection: "right-bottom",
      locale: "en",
    });
  });
</script>
```

### Modal Mode (Popup)

```html
<button id="open-btn">Open Swap</button>

<script>
  var instance = null;

  document.getElementById("open-btn").addEventListener("click", function () {
    var root = document.getElementById("panora-widget-root");
    if (!root) {
      root = document.createElement("div");
      root.id = "panora-widget-root";
      document.body.appendChild(root);
    }

    instance = PanoraWidget.render(root, {
      panoraApiKey: "YOUR_API_KEY",
      displayMode: "MODAL",
      locale: "en",
    });
  });
</script>
```

### API Reference (CDN)

When using the CDN script, the global `PanoraWidget` object is available on `window`:

| Method                                    | Description                                                                 |
| ----------------------------------------- | --------------------------------------------------------------------------- |
| `PanoraWidget.render(container, config?)` | Render the widget into a DOM element or CSS selector. Returns `{ unmount }` |

```javascript
// container can be a DOM element or a CSS selector string
var instance = PanoraWidget.render("#my-div", { displayMode: "INTEGRATED" });

// To remove the widget:
instance.unmount();
```

### Key Points

* The `defer` attribute ensures the script loads after HTML parsing
* `PanoraWidget.render()` accepts either a DOM element or a CSS selector (`#id`, `.class`)
* The CDN bundle is fully self-contained (includes React) — no other dependencies needed

***

## Configuration

Complete reference for all `WidgetConfig` options.

### API & RPC

| Property       | Type     | Default        | Description                                                  |
| -------------- | -------- | -------------- | ------------------------------------------------------------ |
| `panoraApiKey` | `string` | Public key     | Your Panora API key                                          |
| `rpcUrl`       | `string` | Panora default | Custom Aptos RPC URL. Must use `https://` (except localhost) |

### Display

| Property             | Type                                  | Default           | Description                                                    |
| -------------------- | ------------------------------------- | ----------------- | -------------------------------------------------------------- |
| `displayMode`        | `"INTEGRATED" \| "WIDGET" \| "MODAL"` | `"WIDGET"`        | How the widget is presented                                    |
| `integratedTargetId` | `string`                              | `"panora-widget"` | DOM element ID for `INTEGRATED` mode (used with `init()` only) |
| `width`              | CSS value                             | `450px` (max)     | Widget width in `INTEGRATED` mode                              |
| `height`             | CSS value                             | `630px`           | Widget height in `INTEGRATED` mode                             |

### Widget Button (WIDGET mode only)

| Property             | Type                                                           | Default          | Description                  |
| -------------------- | -------------------------------------------------------------- | ---------------- | ---------------------------- |
| `widgetBtnSize`      | `"SMALL" \| "DEFAULT"`                                         | `"DEFAULT"`      | Floating button size         |
| `widgetBtnDirection` | `"left-top" \| "left-bottom" \| "right-top" \| "right-bottom"` | `"right-bottom"` | Corner position              |
| `widgetBtnOffset`    | `{ x: number; y: number }`                                     | —                | Pixel offset from the corner |

### Tokens

| Property                  | Type                  | Default | Description                                     |
| ------------------------- | --------------------- | ------- | ----------------------------------------------- |
| `defaultFromTokenAddress` | `` `0x${string}` ``   | —       | Pre-selected "from" token                       |
| `defaultToTokenAddress`   | `` `0x${string}` ``   | —       | Pre-selected "to" token                         |
| `defaultFavoriteTokens`   | `` `0x${string}`[] `` | —       | Tokens pinned as favorites in the token picker  |
| `customTokens`            | `` `0x${string}`[] `` | —       | Additional token addresses shown in the picker  |
| `allowedTokens`           | `` `0x${string}`[] `` | —       | Restrict the token list to only these addresses |
| `showTokenCategorization` | `boolean`             | —       | Show/hide token category tabs in the picker     |

### Swap Settings

| Property                    | Type      | Default | Description                                                 |
| --------------------------- | --------- | ------- | ----------------------------------------------------------- |
| `defaultSlippagePercentage` | `string`  | —       | Default slippage tolerance (0–50). Must be a numeric string |
| `showPercentageSlider`      | `boolean` | —       | Show the percentage slider on the input field               |

### Wallet

| Property                    | Type       | Default     | Description                                                                                                                                                                                                                                                                                                         |
| --------------------------- | ---------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| account                     | Account    | undefined   | Pre-configured Aptos account for direct transaction signing without wallet popup. Accepts any `Account` subclass from `@aptos-labs/ts-sdk` (`Ed25519Account`, `SingleKeyAccount`, `KeylessAccount`, etc.). When set, the widget signs transactions directly — no wallet connection needed.                          |
| independentWalletConnection | boolean    | true        | When `false`, hides the widget's built-in wallet connect button. Use when the host app manages wallet connection externally.                                                                                                                                                                                        |
| useExternalWallet           | boolean    | false       | When `true`, the widget skips creating its own `AptosWalletAdapterProvider` and uses `useWallet()` from the nearest parent provider. The integrator must wrap the widget in their own `<AptosWalletAdapterProvider>`. Ideal for wallet extensions/apps embedding the widget. Only works with npm package (not CDN). |
| `disableWalletButton`       | `boolean`  | `false`     | Hide the "Connect Wallet" button                                                                                                                                                                                                                                                                                    |
| `enabledWallets`            | `string[]` | All wallets | Restrict which wallets appear in the connection dialog                                                                                                                                                                                                                                                              |

### Integrator Fees

| Property                  | Type                | Default | Description                                  |
| ------------------------- | ------------------- | ------- | -------------------------------------------- |
| `integratorWalletAddress` | `` `0x${string}` `` | —       | Wallet address that receives integrator fees |
| `integratorFeePercentage` | `number`            | —       | Fee percentage per transaction (0–5)         |

### Transaction Options

| Property                          | Type     | Default | Description                                            |
| --------------------------------- | -------- | ------- | ------------------------------------------------------ |
| `transactionOptions.maxGasAmount` | `number` | —       | Maximum gas units. Must be a positive integer          |
| `transactionOptions.gasUnitPrice` | `number` | —       | Price per gas unit (octas). Must be a positive integer |

### Localization

| Property | Type     | Default | Description |
| -------- | -------- | ------- | ----------- |
| `locale` | `string` | `"en"`  | UI language |

**Supported locales:**

| Code    | Language              |
| ------- | --------------------- |
| `en`    | English               |
| `es`    | Spanish               |
| `hi`    | Hindi                 |
| `id`    | Indonesian            |
| `ja`    | Japanese              |
| `ko`    | Korean                |
| `ru`    | Russian               |
| `tr`    | Turkish               |
| `vi`    | Vietnamese            |
| `zh`    | Chinese (Simplified)  |
| `zh-HK` | Chinese (Traditional) |

### Notifications

| Property            | Type      | Default | Description                                       |
| ------------------- | --------- | ------- | ------------------------------------------------- |
| `showNotifications` | `boolean` | `false` | Show toast notifications for swap success/failure |

### Validation Rules

The widget validates your config at runtime. Invalid values are silently ignored with a console warning:

| Property                          | Rule                                                          |
| --------------------------------- | ------------------------------------------------------------- |
| `defaultSlippagePercentage`       | Must be a numeric string between 0 and 50                     |
| `integratorFeePercentage`         | Must be a number between 0 and 5                              |
| `rpcUrl`                          | Must start with `https://` (except `localhost` / `127.0.0.1`) |
| `transactionOptions.maxGasAmount` | Must be a positive integer                                    |
| `transactionOptions.gasUnitPrice` | Must be a positive integer                                    |

***

## Theming

Customize the widget's appearance to match your app's design system.

Pass a partial `theme` object in your config — any property you omit will use the default Panora theme.

### Usage

```typescript
init({
  panoraApiKey: "YOUR_API_KEY",
  displayMode: "INTEGRATED",
  integratedTargetId: "panora-widget",
  theme: {
    primaryColor: "#6366f1",
    primaryButtonTextColor: "#ffffff",
    basicBgColor: "#1e1e2e",
    secondaryBgColor: "#181825",
    tertiaryBgColor: "#11111b",
    primaryTextColor: "#cdd6f4",
    secondaryTextColor: "#6c7086",
    borderColor: "#313244",
  },
});
```

### Theme Properties

| Property                 | Default                    | Description                             |
| ------------------------ | -------------------------- | --------------------------------------- |
| `basicBgColor`           | `rgba(255, 255, 255, .05)` | Primary background                      |
| `secondaryBgColor`       | `#010D09`                  | Secondary background (outer areas)      |
| `tertiaryBgColor`        | `#000704`                  | Tertiary background (cards, dropdowns)  |
| `primaryColor`           | `#5fdfac`                  | Main accent color (buttons, highlights) |
| `subtleColor`            | `rgba(255, 255, 255, .05)` | Subtle UI elements                      |
| `primaryButtonTextColor` | `#000000`                  | Text color on primary buttons           |
| `borderColor`            | `rgba(255, 255, 255, .05)` | Border color                            |
| `inputColor`             | `#0d110d`                  | Input field background                  |
| `inputBorderColor`       | `#2a3a2a`                  | Input field border                      |
| `primaryTextColor`       | `#FFFFFF`                  | Main text color                         |
| `secondaryTextColor`     | `#6B7280`                  | Muted/secondary text                    |
| `placeholderTextColor`   | `#3a4a3a`                  | Input placeholder text                  |
| `textUpColor`            | `#5fdfac`                  | Positive values (gains)                 |
| `textDownColor`          | `#F6465D`                  | Negative values (losses)                |
| `successColor`           | `#5fdfac`                  | Success states                          |
| `errorColor`             | `#F6465D`                  | Error states                            |
| `warningColor`           | `#EF8E19`                  | Warning states                          |

### Default Theme

```json
{
  "id": "panora",
  "name": "Panora",
  "basicBgColor": "rgba(255, 255, 255, .05)",
  "secondaryBgColor": "#010D09",
  "tertiaryBgColor": "#000704",
  "primaryColor": "#5fdfac",
  "subtleColor": "rgba(255, 255, 255, .05)",
  "primaryButtonTextColor": "#000000",
  "borderColor": "rgba(255, 255, 255, .05)",
  "inputColor": "#0d110d",
  "inputBorderColor": "#2a3a2a",
  "primaryTextColor": "#FFFFFF",
  "secondaryTextColor": "#6B7280",
  "placeholderTextColor": "#3a4a3a",
  "textUpColor": "#5fdfac",
  "textDownColor": "#F6465D",
  "successColor": "#5fdfac",
  "errorColor": "#F6465D",
  "warningColor": "#EF8E19"
}
```

> **Tip:** You can import the default theme object in code:
>
> ```typescript
> import { DEFAULT_WIDGET_THEME } from "@panoraexch/widget-sdk";
> ```

***

## API Reference

### NPM Package Exports

```typescript
// Functions
import { init, render, close, resume } from "@panoraexch/widget-sdk";

// React component
import { PanoraWidget } from "@panoraexch/widget-sdk";

// Constants
import { DEFAULT_WIDGET_THEME } from "@panoraexch/widget-sdk";

// Types
import type {
  WidgetConfig,
  PanoraWidgetConfig, // backward-compatible alias for WidgetConfig
  AppTheme,
  WidgetFeature,
  WidgetTheme,
  PanoraWidgetProps,
} from "@panoraexch/widget-sdk";
```

### `init(config?)`

Initialize and render the widget.

```typescript
function init(
  config?: WidgetConfig & { integratedTargetId?: string }
): Promise<{ unmount: () => void }>
```

| Parameter                   | Type           | Description                                                         |
| --------------------------- | -------------- | ------------------------------------------------------------------- |
| `config`                    | `WidgetConfig` | Widget configuration (see Configuration)                            |
| `config.integratedTargetId` | `string`       | DOM element ID for `INTEGRATED` mode. Defaults to `"panora-widget"` |

**Returns:** `Promise<{ unmount: () => void }>` — call `unmount()` to remove the widget.

**Behavior by display mode:**

| Mode         | What happens                                                                                              |
| ------------ | --------------------------------------------------------------------------------------------------------- |
| `INTEGRATED` | Renders into the element matching `integratedTargetId`. Throws if element not found.                      |
| `WIDGET`     | Creates a floating button. A `panora-widget-root` container is auto-created in `document.body` if needed. |
| `MODAL`      | Opens a centered dialog with backdrop. Container auto-created like `WIDGET`.                              |

### `render(container, config?)`

Lower-level alternative to `init()` for full control over where the widget mounts.

```typescript
function render(
  container: HTMLElement | string,
  config?: WidgetConfig
): Promise<{ unmount: () => void }>
```

| Parameter   | Type                    | Description                                                |
| ----------- | ----------------------- | ---------------------------------------------------------- |
| `container` | `HTMLElement \| string` | A DOM element or CSS selector (`"#my-div"`, `".my-class"`) |
| `config`    | `WidgetConfig`          | Widget configuration                                       |

> **Note:** `render()` is primarily used with the CDN bundle (`PanoraWidget.render()`). When using the npm package, prefer `init()`.

### `close()`

Unmount and clean up the widget.

```typescript
function close(): void
```

Always call `close()` before re-initializing, or in your component's cleanup function to prevent duplicate instances.

### `resume()`

Re-show the widget with the last configuration passed to `init()`.

```typescript
function resume(): Promise<void>
```

If a widget instance is already mounted, this is a no-op.

### `<PanoraWidget />`

React component for declarative rendering. Always uses `INTEGRATED` mode.

```typescript
interface PanoraWidgetProps {
  widgetConfig?: WidgetConfig;
}
```

```tsx
<PanoraWidget
  widgetConfig={{
    panoraApiKey: "YOUR_API_KEY",
    locale: "en",
  }}
/>
```

* Automatically creates a container `<div>` and calls `init()` on mount
* Automatically calls `close()` on unmount
* Re-initializes when `widgetConfig` changes

### CDN Global: `PanoraWidget`

When using the CDN `<script>` tag, a global `PanoraWidget` object is available on `window`.

```typescript
window.PanoraWidget.render(
  container: HTMLElement | string,
  config?: WidgetConfig
): { unmount: () => void }
```

| Parameter   | Type                    | Description                 |
| ----------- | ----------------------- | --------------------------- |
| `container` | `HTMLElement \| string` | DOM element or CSS selector |
| `config`    | `WidgetConfig`          | Widget configuration        |

The CDN bundle is fully self-contained — it includes its own copy of React and all dependencies.

***

### Attribution

Include **"Powered by Panora"** when using this SDK in your application. The widget displays this by default.
