# Swap on NEAR Intents

Build a cross-chain swap experience by pairing Openfort's embedded wallet with the [NEAR Intents](https://docs.near-intents.org) 1Click API. The user picks an asset to send and an asset to receive, the wallet signs a single deposit on the origin chain, and a network of solvers settles the swap on the destination chain — which can be any chain NEAR Intents supports, including non-EVM chains like Solana, Bitcoin, and Dogecoin.

:::tip
You'll build a full-stack Next.js app where users sign in with an Openfort embedded wallet (email or external wallet) and swap from any EVM chain to 30+ destination chains. No NEAR account or NEAR-native signature is required — the only on-chain action is a standard EVM transfer.
:::

<HoverCardLink
  title="View Sample Code"
  subtitle="GitHub Repository"
  description="Complete source code for the NEAR Intents cross-chain swap application with a Next.js frontend and 1Click API integration."
  href="https://github.com/openfort-xyz/recipes-hub/tree/main/near-intents"
  img={{
  src: "/img/icons/github-icon.svg",
  alt: "GitHub Icon",
  className: "rounded-none",
}}
  color="#333"
  external
/>

## Getting started

::::steps
### Set up your project

Clone the recipe and install dependencies:

```bash
pnpx gitpick openfort-xyz/recipes-hub/tree/main/near-intents openfort-near-intents
cd openfort-near-intents
pnpm install
```

### Get your credentials

1. Sign in to [dashboard.openfort.io](https://dashboard.openfort.io) and create a project
2. Go to **API Keys** and copy your **publishable key**
3. Go to **Shield** settings and copy your **Shield publishable key**
4. (Optional) Create a **Policy** for gas sponsorship and copy the policy ID
5. (Optional) Create a free **WalletConnect** project at [cloud.reown.com](https://cloud.reown.com) to enable external-wallet sign-in
6. (Optional) Request a **1Click** JWT at [partners.near-intents.org](https://partners.near-intents.org) to waive the 0.2% fee
7. (Optional) For **confidential swaps**, ask the NEAR team to enable Confidential Intents on your JWT — it's invite-only

:::tip
The project must be on **Auth v2**. The 1Click JWT is read server-side only and is never exposed to the browser.
:::

### Configure your environment

Copy the template and fill in your credentials:

```bash
cp .env.example .env.local
```

```bash
NEXT_PUBLIC_OPENFORT_PUBLISHABLE_KEY=pk_...
NEXT_PUBLIC_OPENFORT_SHIELD_PUBLISHABLE_KEY=...
NEXT_PUBLIC_OPENFORT_FEE_SPONSORSHIP_ID=pol_...     # Optional — for gas sponsorship
NEXT_PUBLIC_OPENFORT_DEFAULT_CHAIN_ID=8453          # Base
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=...            # Optional — enables wallet sign-in

ONECLICK_JWT=...                                    # Optional, server-side only (required for confidential swaps)
NEXT_PUBLIC_CONFIDENTIAL_ENABLED=true               # Optional — shows the Public/Private toggle
```

:::warning
NEAR Intents settles on **mainnet** — there is no testnet. Test with small amounts.
:::

### Run the application

```bash
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000), sign in with email or your wallet, and try a cross-chain swap.
::::

## How it works

The app keeps the 1Click API behind server routes (so the JWT stays private) and lets the Openfort embedded wallet sign a single deposit. Solvers handle the routing and settlement.

### The swap flow

The `useSwapController` hook in `src/features/near-intents/hooks/use-swap-controller.ts` manages the full lifecycle:

1. **Quote** — `POST /api/quote` proxies the 1Click `/v0/quote` endpoint (injecting the JWT server-side) and returns a deposit address plus the expected output. A debounced dry-run quote keeps the estimate live as the user types.
2. **Deposit** — the Openfort wallet signs one transfer of the input asset to the deposit address (native send or ERC-20 `transfer`), switching chains first if needed.
3. **Track** — `GET /api/status` polls `/v0/status` until the swap reaches `SUCCESS`, `REFUNDED`, or `FAILED`, with origin and destination explorer links.

### Server-side 1Click proxy

The JWT lives only in the server routes. The browser talks to `/api/*`, which forwards to 1Click:

```typescript
// src/features/near-intents/services/oneclick-server.ts
const BASE_URL = "https://1click.chaindefuser.com";

const authHeaders = () => ({
  "Content-Type": "application/json",
  ...(process.env.ONECLICK_JWT && {
    Authorization: `Bearer ${process.env.ONECLICK_JWT}`,
  }),
});

export const requestQuote = (params) =>
  fetch(`${BASE_URL}/v0/quote`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify({
      dry: params.dry,
      swapType: "EXACT_INPUT",
      depositType: "ORIGIN_CHAIN",
      recipientType: "DESTINATION_CHAIN",
      originAsset: params.originAsset,
      destinationAsset: params.destinationAsset,
      amount: params.amount,
      recipient: params.recipient,
      refundTo: params.refundTo,
      // ...slippage, deadline
    }),
  });
```

### Signing the deposit

The deposit is a normal EVM transfer — no NEAR signature. The wallet sends native value or an ERC-20 `transfer` to the deposit address returned by the quote:

```typescript
// src/features/near-intents/hooks/use-swap-controller.ts
if (fromAsset.isNative) {
  txHash = await sendTransactionAsync({
    chainId: fromAsset.chainId,
    to: depositAddress,
    value: amountIn,
  });
} else {
  txHash = await writeContractAsync({
    chainId: fromAsset.chainId,
    address: fromAsset.contractAddress,
    abi: erc20Abi,
    functionName: "transfer",
    args: [depositAddress, amountIn],
  });
}
```

### EVM origin, any-chain destination

Because the Openfort wallet signs the deposit on an EVM chain, the **origin** asset is restricted to the EVM chains in the wagmi config (Ethereum, Base, Arbitrum, Optimism, Polygon, Avalanche). The **destination** can be any chain NEAR Intents supports — including non-EVM ones. For non-EVM destinations the user supplies a recipient address on that chain (a Solana, Bitcoin, or other address), since their `0x` address only works for EVM destinations.

### Confidential swaps

Set `NEXT_PUBLIC_CONFIDENTIAL_ENABLED=true` to add a **Public / Private** toggle to the swap form. Choosing *Private* routes the swap through [NEAR Confidential Intents](https://www.near.org/blog/confidential-intents) so the trade isn't broadcast on the public chain — useful for reducing trade signaling, MEV, and front-running.

For this recipe it's a one-field change. It's the "foreign-to-foreign" confidential path: the deposit stays a normal `ORIGIN_CHAIN` → `DESTINATION_CHAIN` transfer signed by the Openfort wallet — no signed intents and no NEAR account. Only the quote body gains a `confidentiality` field:

```typescript
// src/features/near-intents/services/oneclick-server.ts
const body = {
  swapType: "EXACT_INPUT",
  depositType: "ORIGIN_CHAIN",
  recipientType: "DESTINATION_CHAIN",
  // "public" (default), "basic", or "advanced".
  confidentiality: params.confidentiality ?? "public",
  originAsset: params.originAsset,
  destinationAsset: params.destinationAsset,
  amount: params.amount,
  recipient: params.recipient,
  refundTo: params.refundTo,
  // ...slippage, deadline
};
```

The toggle sets `confidentiality` to `"basic"` (the API also accepts `"advanced"` for a higher privacy tier), and `useSwapController` threads it into both the live estimate and the real quote.

:::warning
Confidential swaps are **invite-only** and require an **authenticated JWT** — the public 1Click endpoint returns `401` for `basic`/`advanced` quotes. Request access via the [Partner Portal](https://partners.near-intents.org). Until your integration is enabled, the API returns an invite-only message, which the recipe surfaces as a prompt to request access or switch back to a public swap.
:::

## Next steps

* [Embedded Wallet Guide](/docs/products/embedded-wallet) — learn more about embedded wallet security
* [Gas sponsorship with policies](/docs/configuration/policies) — sponsor user transactions
* [NEAR Intents docs](https://docs.near-intents.org) — explore the 1Click API and supported chains
