> For the complete documentation index, see [llms.txt](https://docs.orda.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.orda.network/developers/widget.md).

# Widget

The orda Widget is a comprehensive, embeddable React component that enables\
multi-chain cryptocurrency payments in your application — swap, on-ramp, and\
off-ramp flows with built-in wallet connection, all wrapped around the orda\
TypeScript SDK.

{% hint style="info" %}
**Replaces the old `/developers/widget-beta` page.** The widget moved out of `@ordanetwork/sdk/react` (deleted in the SDK v2 release) into its own package **`@ordanetwork/widget@1.0.0`**. If you were on the beta, see Migrating from `@ordanetwork/sdk/react` at the bottom.Try the [widget →](https://widget.orda.network/)
{% endhint %}

### What it does

* **Multi-chain support** — EVM (Ethereum, Polygon, Arbitrum, Base, Optimism, plus plasma / stable / megaeth) and Solana
* **Three transaction flows** — Swap, Off-ramp (crypto → BRL via PIX), On-ramp (BRL → crypto via PIX)
* **Wallet integration** — WalletConnect / Reown AppKit modal mounted for you
* **Lifecycle callbacks** — 10 typed events for quote / transaction / wallet / error, plus a firehose for analytics
* **Recipient management** — save and manage payment recipients
* **Token prices & balances** — real-time USD prices and multi-chain balance fetching
* **Responsive UI** — desktop and mobile
* **Customizable theming** — CSS variables

### Prerequisites

1. WalletConnect / Reown project id — [cloud.reown.com](https://cloud.reown.com/)
2. orda API credentials — `clientId` and `clientSecret`
3. React 18 or React 19
4. Node.js 18+

### Installation

```bash
npm install @ordanetwork/widget @ordanetwork/sdk
# peers (you almost certainly have most of these already)
npm install react react-dom @tanstack/react-query wagmi viem
```

#### Version compatibility

| Package                 | Version                |
| ----------------------- | ---------------------- |
| `@ordanetwork/widget`   | `^1.0.0`               |
| `@ordanetwork/sdk`      | `^1.3.0 \|\| ^2.0.0`   |
| `react`                 | `^18.0.0 \|\| ^19.0.0` |
| `react-dom`             | `^18.0.0 \|\| ^19.0.0` |
| `@tanstack/react-query` | `^5.0.0`               |
| `wagmi`                 | `^2.0.0`               |
| `viem`                  | `^2.0.0`               |

`@solana/web3.js`, `@reown/appkit*`, and the radix-ui primitives are direct dependencies of `@ordanetwork/widget` — you don't need to install them yourself.

### Quick start

#### Step 1 — Set environment variables (`.env.local`)

```bash
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=your_walletconnect_project_id
ORDA_CLIENT_ID=prj_your_client_id
ORDA_CLIENT_SECRET=sec_your_client_secret
```

#### Step 2 — Create the JWT auth route (`app/api/auth/jwt/route.ts`)

The widget calls `config.getToken()` to obtain a JWT; you handle credential exchange server-side so the secret never reaches the browser.

```ts
import { NextResponse } from 'next/server';
import { UniversalHttpClient, JWT } from '@ordanetwork/sdk';

const CLIENT_ID = process.env.ORDA_CLIENT_ID!;
const CLIENT_SECRET = process.env.ORDA_CLIENT_SECRET!;
const API_URL = process.env.NEXT_PUBLIC_ORDA_API_BASE_URL || 'https://api.orda.network/v1';

export async function POST() {
  try {
    if (!CLIENT_ID || !CLIENT_SECRET) {
      return NextResponse.json(
        { error: 'Server configuration error: missing API credentials' },
        { status: 500 },
      );
    }

    const http = new UniversalHttpClient(API_URL, 30_000);
    const jwt = new JWT(http);

    const { token, expiresAt } = await jwt.generate({
      clientId: CLIENT_ID,
      clientSecret: CLIENT_SECRET,
      permissions: [
        'quotes:read',
        'offramp:read',
        'onramp:read',
        'transactions:read',
        'recipients:read',
        'recipients:write',
        'balances:read',
        'prices:read',
      ],
    });

    return NextResponse.json({
      jwt: token,
      expiresAt: Math.floor(new Date(expiresAt).getTime() / 1000),
    });
  } catch (err: any) {
    return NextResponse.json(
      { error: err?.message ?? 'Failed to generate token' },
      { status: err?.statusCode ?? 500 },
    );
  }
}
```

#### Step 3 — Create the provider (`app/providers.tsx`)

```tsx
'use client';

import { OrdaProvider, createAppKitConfig } from '@ordanetwork/widget';

// Module scope (SSR-safe).
const appKitConfig = createAppKitConfig({
  projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID!,
  metadata: {
    name: 'Your App',
    description: 'Your app description',
    url: 'https://yourapp.com',
    icons: ['https://yourapp.com/icon.png'],
  },
});

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <OrdaProvider
      config={{
        getToken: async () => {
          const res = await fetch('/api/auth/jwt', { method: 'POST' });
          if (!res.ok) {
            const { error } = await res.json();
            throw new Error(error || 'Failed to get JWT');
          }
          return res.json(); // { jwt: string, expiresAt: number }
        },
        appKitConfig,
        debug: process.env.NODE_ENV === 'development',
      }}
    >
      {children}
    </OrdaProvider>
  );
}
```

#### Step 4 — Mount the provider + styles in your root layout (`app/layout.tsx`)

```tsx
import './globals.css';
import '@ordanetwork/widget/styles.css';
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

#### Step 5 — Drop in the Widget (`app/page.tsx`)

```tsx
'use client';

import { Widget } from '@ordanetwork/widget';

export default function Home() {
  return (
    <main className="min-h-screen flex items-center justify-center p-4 bg-zinc-100">
      <div className="w-full max-w-md">
        <h1 className="text-2xl font-bold text-center mb-6">Crypto Payments</h1>
        <Widget
          onTransactionSettled={(e) => console.log('settled', e.payload)}
          onError={(e) => console.error(e.payload.message)}
        />
      </div>
    </main>
  );
}
```

#### Step 6 — Run

```bash
npm run dev
```

### Lifecycle callbacks

All optional props on `<Widget />`. Every event fires alongside `onAnalyticsEvent` — dedupe by `event.id` if you wire both (see Analytics firehose below).

| Callback                 | Fires when                                                                                  |
| ------------------------ | ------------------------------------------------------------------------------------------- |
| `onQuoteRequested`       | The widget asked the SDK for a quote (before network round-trip).                           |
| `onQuoteAccepted`        | A quote returned successfully (or was served from the 30 s cache — `payload.cached: true`). |
| `onQuoteFailed`          | Quote request failed (network / validation / SDK error).                                    |
| `onTransactionInitiated` | User confirmed; the wallet was asked to sign / submit.                                      |
| `onTransactionSettled`   | Transaction confirmed on-chain **or** settled by the backend.                               |
| `onTransactionFailed`    | Transaction reverted, was rejected, or failed before settling.                              |
| `onWalletConnected`      | An EVM or Solana wallet just connected.                                                     |
| `onWalletDisconnected`   | A wallet just disconnected.                                                                 |
| `onError`                | Any user-visible error (fires alongside the specific `*_failed` callback when applicable).  |
| `onAnalyticsEvent`       | **Firehose** — every event in order.                                                        |

Every event payload shares the same envelope:

```ts
{
  id: string;          // stable nanoid per event
  timestamp: number;   // Date.now() at emit
  flow?: 'swap' | 'onramp' | 'offramp';
  type: WidgetEventType;
  payload: { ... };    // discriminated by type
}
```

#### Analytics firehose

`onAnalyticsEvent` fires for **every** event, alongside the specific callback. Dedupe by `event.id` when you wire both:

```tsx
const seen = new Set<string>();

<Widget
  onTransactionSettled={(e) => {
    if (seen.has(e.id)) return;
    seen.add(e.id);
    yourTxHistoryStore.add(e.payload);
  }}
  onAnalyticsEvent={(e) => {
    if (seen.has(e.id)) return;
    seen.add(e.id);
    analytics.track(e.type, e.payload); // safe to forward verbatim — see below
  }}
/>
```

#### Payload redaction (PII)

{% hint style="success" %}
Event payloads are deliberately redacted so they are safe to forward verbatim to a third-party analytics sink (Segment / GA / PostHog / Mixpanel).
{% endhint %}

* **`quote_accepted`** carries only a serializable summary — `transactionId`, `provider`, `fromSymbol`/`toSymbol`, `fromAmount`/`toAmount`, `fromChainId`/`toChainId`, `estimatedDuration`, and `cached`. It does **not** include PIX key / QR code, deposit address, reference id, raw `transactionRequest` / `approvalTxParams` (signed calldata), or wallet addresses.
* **`quote_failed`, `transaction_failed`, and `error`** expose a human-readable `message` plus an optional `causeMessage` string. The underlying error object is **never** forwarded — viem / RPC errors embed tx params and request payloads, so only a message string crosses the callback boundary.

If you need the full, unredacted quote (e.g. to render PIX deposit instructions yourself), read it from the hook return values (`useOnRamp().quote`, etc.) rather than from events.

### Configuration flags

All optional on the `config` object passed to `<OrdaProvider>`:

| Field                 | Default      | Effect                                                                                                                                                                                 |
| --------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `getToken`            | — (required) | Returns `{ jwt, expiresAt }`. Called on mount and \~5 min before expiry.                                                                                                               |
| `appKitConfig`        | — (required) | From `createAppKitConfig({ projectId, metadata })`.                                                                                                                                    |
| `debug`               | `false`      | Verbose console logs for SDK init / refresh.                                                                                                                                           |
| `notifications`       | `true`       | When `true`, mounts a Sonner `<Toaster />`. When `false`, in-progress states render as inline `<Alert />` banners and success / error states surface only through lifecycle callbacks. |
| `disableOnRamp`       | `false`      | Hide on-ramp (fiat → crypto) flows.                                                                                                                                                    |
| `disableOffRamp`      | `false`      | Hide off-ramp (crypto → fiat) flows.                                                                                                                                                   |
| `enableOfframpSolana` | `false`      | Allow Solana as a source chain for off-ramp.                                                                                                                                           |

Apps controlling these via env (e.g. `NEXT_PUBLIC_ON_RAMP_DISABLED`) should map the env value to the config field when constructing the provider — the widget does not read `process.env.*` directly.

### SSR / RSC

`@ordanetwork/widget` is a client component. The bundle starts with `'use client';` so importing it from a React Server Component is safe — you get the `'use client'` boundary automatically. You do **not** need to wrap it.

`createAppKitConfig()` must be called at module scope (outside components) for SSR safety — see Step 3 above.

### Wallet mode

`@ordanetwork/widget@1.0.0` ships one wallet mode: **`appkit`** (the default). The widget mounts Wagmi + the Reown AppKit modal internally; you supply a `projectId` via `createAppKitConfig`.

{% hint style="info" %}
**BYOW (`wallet: 'external'`) is coming in 1.1.0** — for hosts that already own a `<WagmiProvider>` and want the widget to consume it. The plumbing exists internally; a few downstream call sites are still being migrated. Tracked as **COR-963**.
{% endhint %}

### TypeScript

All types ship with the package; no `@types/...` install needed.

```ts
import type {
  WidgetEvent,
  WidgetCallbacks,
  QuoteAcceptedPayload,
  TransactionSettledPayload,
  TransactionFailedPayload,
  OrdaProviderConfig,
} from '@ordanetwork/widget';
```

The full event union is discriminated by `type`, so narrowing in a callback works without casts.

### Security note

{% hint style="danger" %}
Never expose `ORDA_CLIENT_SECRET` to the browser. Always exchange credentials for a JWT on a server-side route (Step 2) and let the widget call that route via `config.getToken`.
{% endhint %}

### Migrating from `@ordanetwork/sdk/react`

If you integrated against the beta (when the widget lived inside the core SDK), here's the migration:

| Before (`@ordanetwork/sdk/react`)                                                   | After (`@ordanetwork/widget`)                                                    |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `npm install @ordanetwork/sdk`                                                      | `npm install @ordanetwork/widget @ordanetwork/sdk`                               |
| `import { OrdaProvider, Widget, createAppKitConfig } from '@ordanetwork/sdk/react'` | `import { OrdaProvider, Widget, createAppKitConfig } from '@ordanetwork/widget'` |
| `import '@ordanetwork/sdk/react/styles.css'`                                        | `import '@ordanetwork/widget/styles.css'`                                        |

No runtime behavior changes — the same components, the same `OrdaProvider` config shape, the same `Widget` props. The split is purely about packaging: the React UI now ships independently of the core SDK so it can version on its own cadence.

The `@ordanetwork/sdk` itself remains a peer dependency (the widget uses it internally for quote / transaction / recipient API calls).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.orda.network/developers/widget.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
