# Welcome to Aurora Intents

**Aurora Intents** is the **cross-chain execution layer** for on-chain **applications**.

It enables **users** and **systems** to move assets, execute actions, and access liquidity across chains without friction.

***

### Our Products

<table data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th><th data-type="content-ref"></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td><a href="/pages/ETiitsNKbnDcUtk53eBi"><strong>Intents Connect</strong></a><br><br>The core infrastructure for <strong>cross-chain execution</strong>.</td><td><a href="/pages/cxglsDCrD9JbVi1jvrNG">/pages/cxglsDCrD9JbVi1jvrNG</a></td><td><a href="/pages/IEeJG5eB7GF5DU33CT9i">/pages/IEeJG5eB7GF5DU33CT9i</a></td><td><a href="/pages/cxglsDCrD9JbVi1jvrNG">/pages/cxglsDCrD9JbVi1jvrNG</a></td><td><a href="/files/t7nvl2WDn298g6yPAYEr">/files/t7nvl2WDn298g6yPAYEr</a></td></tr><tr><td><a href="/pages/ltTn81ZbsMSwwhqPZybz"><strong>Intents Deposits</strong></a><br><br>The simplest way to enable <strong>cross-chain deposits</strong> into your app or protocol.</td><td><a href="/pages/mXHWRJWdmbxCwKpCf43Y">/pages/mXHWRJWdmbxCwKpCf43Y</a></td><td><a href="/pages/nXedcGwIWPg3vmSiTYdk">/pages/nXedcGwIWPg3vmSiTYdk</a></td><td><a href="/pages/ltTn81ZbsMSwwhqPZybz">/pages/ltTn81ZbsMSwwhqPZybz</a></td><td><a href="/files/LWOX1ll7GtgyfOTEOyP7">/files/LWOX1ll7GtgyfOTEOyP7</a></td></tr><tr><td><a href="/pages/qc0T1RdEvA8HxEcaOX4y"><strong>Swap Widget</strong></a><br><br>A plug-and-play component to enable <strong>cross-chain swaps</strong> inside <strong>your app</strong>.</td><td><a href="/pages/AbxaqB9XkGlpz29USfkC">/pages/AbxaqB9XkGlpz29USfkC</a></td><td><a href="/pages/SsBEp6o4Ln2lEihmfnC1">/pages/SsBEp6o4Ln2lEihmfnC1</a></td><td><a href="/pages/qc0T1RdEvA8HxEcaOX4y">/pages/qc0T1RdEvA8HxEcaOX4y</a></td><td><a href="/files/ZpEJKS7kRgSmldjREzKd">/files/ZpEJKS7kRgSmldjREzKd</a></td></tr></tbody></table>

***

### 🚀 Intents Connect

Intents Connect allows applications to execute cross-chain actions without managing bridges, liquidity, or complex routing logic. Powered by Near Intents.

#### Use it to:

* Enable cross-chain swaps and on-chain actions
* Route transactions across chains automatically
* Chain on chain actions

#### Explore:

* [What is Intents Connect?](/intents-connect/what-is-intents-connect)
* [Supported chains](/intents-connect/supported-chains)
* [Examples](/intents-connect/examples)

***

### 💸 Intents Deposits

The simplest way to enable **cross-chain deposits** into your app or protocol.

Users can deposit from any chain and asset, while your app receives funds on the target chain, with optional execution on arrival.

#### Use it to:

* Accept deposits from any chain
* Simplify onboarding for non-native users
* Route funds directly into contracts (vaults, RWAs, etc.)

#### Explore:

* [What are Intents Deposits?](/intents-deposits/what-are-intents-deposits)
* [Quickstart](/intents-deposits/quickstart)
* [Supported chains and assets](/intents-deposits/supported-chains)

***

### 🔄 Swap Widget

A plug-and-play component to enable **cross-chain swaps inside your app**.

The easiest way to integrate cross-chain functionality; no backend, no liquidity management, no infrastructure required.

#### Use it to:

* Add cross-chain swaps in minutes
* Let users swap across chains without switching networks
* Create additional revenue stream for your app

#### Explore:

* [What is Swap Widget?](/intents-swap/what-is-swap-widget)
* [API Keys & Fees](/intents-swap/api-keys-and-fees)
* [Widget integration](/intents-swap/widget-integration)

***

### 🧭 How to get started

Not sure where to begin?

* Building a **DeFi app or wallet** → Start with **Swap Widget**
* Need **cross-chain deposits / funding flows** → Start with **Intents Deposits**
* Want full control over **cross-chain execution logic** → Explore **Intents Connect**

***

### 💬 Need help?

* Join the community on [Telegram](https://t.me/auroraisnear/377725)
* Reach out to the team on Telegram
* Explore [Use Cases](/use-cases) in the docs


# Use cases

Aurora Intents enables a wide range of cross-chain interactions, from simple swaps to fully automated on-chain flows.

This page highlights common use cases and how to implement them.

***

### 🔄 Cross-chain swaps in your app

Let users swap assets across chains directly inside your application.

No bridging, no network switching — just a seamless experience.

#### Typical use cases

* Wallets enabling cross-chain trading
* DeFi apps expanding liquidity access
* Consumer apps simplifying token access

👉 **See a live implementation:**

[**https://swap.nearmobile.app/**](https://swap.nearmobile.app/)<br>

👉 **Best fit:** [Swap Widget](/intents-swap/what-is-swap-widget)

***

### 💸 Cross-chain deposits

Let users deposit from any chain and asset, while your application receives funds on the target chain.

This removes friction in onboarding and funding flows.

#### Typical use cases

* Prediction markets accepting deposits from any chain
* Neobanks enabling easy top-ups
* RWA platforms onboarding non-crypto-native users

👉 **Try the demo:**\
<https://intents-connect-landing.vercel.app/demo/deposits.html>

👉 **Best fit:** [Intents Deposits](/intents-deposits/what-are-intents-deposits)

***

### 🏦 Direct deposit into protocols

Go beyond simple deposits, route funds directly into contracts.

Users can deposit from any chain and immediately interact with your protocol.

#### Examples

* Deposit into a lending protocol (e.g. Aave)
* Mint tokenized assets (RWAs)
* Enter yield strategies or vaults

👉 No intermediate steps required: funding and execution happen in one flow.

👉 **Try the demo:**\
<https://intents-connect-landing.vercel.app/demo-tonstakers.html>

👉 **Best fit:** [Intents Deposits](/intents-deposits/what-are-intents-deposits) + [Intents Connect](/intents-connect/what-is-intents-connect)

***

### 📈 Cross-chain yield access

Let users access yield opportunities across chains without moving assets manually.

#### Examples

* Stake assets on another chain
* Access vault strategies across ecosystems
* Rebalance positions across chains

👉 Users don’t need to:

* bridge assets
* switch networks
* understand where yield lives

👉 **Best fit:** [Intents Connect](/intents-connect/what-is-intents-connect)

***

### ⚙️ Custom cross-chain actions

Build fully custom flows where users perform actions across chains in one step.&#x20;

Multiple actions can be bundled and executed in sequence.

#### Examples

* Swap → stake → borrow
* Bridge → then deposit into a vault&#x20;
* Convert → then mint an asset

👉 **Best fit:** [Intents Connect (API)](/intents-connect/what-is-intents-connect)

***

### 🧭 Choosing the right approach

<table><thead><tr><th width="478.625">Use case</th><th>Recommended product</th></tr></thead><tbody><tr><td>Add cross-chain swaps frontend widget to your app</td><td><a href="/pages/qc0T1RdEvA8HxEcaOX4y">Swap Widget</a></td></tr><tr><td>Accept cross-chain deposits via a frontend widget</td><td><a href="/pages/ltTn81ZbsMSwwhqPZybz">Intents Deposits</a></td></tr><tr><td>Execute contract actions via a frontend widget in your dApp</td><td><a href="/pages/ETiitsNKbnDcUtk53eBi">Intents Connect</a></td></tr><tr><td>Build tailored cross-chain flows using any interface</td><td><a href="/pages/ETiitsNKbnDcUtk53eBi">Intents Connect API</a></td></tr></tbody></table>


# Confidential Intents

### Confidential Intents

Every cross-chain swap normally leaves a trail. The origin wallet, the route, the size. That is your user's strategy, out in the open for anyone to read.

Confidential Intents hides it. It is live now on the Swap API.

#### Overview

A normal cross-chain swap is fully traceable. The origin wallet, the amount, and the full routing path are visible on-chain. Anyone observing can link the source wallet to the destination, map a treasury, or track a strategy as it executes.

Confidential Intents removes that link. The swap is routed through a private shard of NEAR, so the origin wallet and the routing trail are not visible to the outside world. The swap still settles on the destination chain as a standard public transaction. What is hidden is how the funds got there, not that they arrived.

#### What is hidden and what stays public

Confidentiality applies to the origin and the route only. Destination settlement remains fully public and verifiable.

| Hidden                              | Public                             |
| ----------------------------------- | ---------------------------------- |
| Origin wallet address               | Destination transaction            |
| Routing path across chains          | Destination asset and amount       |
| Link between source and destination | Recipient on the destination chain |

This scope is exact. Confidential Intents is not private DeFi and does not obscure the destination. Settlement stays auditable, so the receiving side can verify the transaction as normal.

#### How it works

<figure><img src="/files/ggGUFJLBkyLZTvQa0WfK" alt=""><figcaption></figcaption></figure>

The swap is routed through a private shard of NEAR running the NEAR Intents engine. Any swap or transfer that happens inside the private shard is not visible to the outside world.

Funds enter from the origin chain, execute within the private shard, and settle on the destination chain. Because the routing happens inside the shard, the origin chain and the destination chain are not linkable on-chain. The origin wallet is not exposed to the destination, and the intermediate routing cannot be traced back to the source.

Destination settlement is a standard on-chain transaction. No special handling is required on the receiving side. From the destination's perspective, a confidential swap and a normal swap look the same once settled.

#### What it enables

<figure><img src="/files/RCHPzC7GblG7kUYSfVBK" alt=""><figcaption></figcaption></figure>

Confidential Intents is built for flows where exposing the origin and route creates risk.

**Position privacy**. Funds can move into a position without broadcasting the originating wallet, so the strategy behind the trade is not readable on-chain.

**Treasury protection**. Institutions can execute cross-chain without telegraphing treasury movements to anyone watching the source wallet.

#### Availability

Confidential Intents is live on the **Swap API** now. The best part is that it is a single flag on the quote endpoint you already call. No new integration. No separate API. Flip one parameter and you are live.

This is rolling out across the full suite:&#x20;

* **confidential swaps**
* **confidential deposits**
* **confidential DeFi actions.**&#x20;

Confidentiality is becoming a standard capability across Aurora Intents, available as an upgrade to the products you already integrate.

#### Enabling it

Confidential mode is a flag on the quote endpoint you already use. See the [Confidential Swaps](/intents-swap/confidential-swaps) to get started.


# What is Intents Connect?

Intents Connect lets a user with a wallet on one chain perform actions on a completely different chain - without manual bridging, switching wallets or managing gas on the destination chain.

<figure><img src="/files/R9IAxzCLjK6GaFQd0t5R" alt=""><figcaption></figcaption></figure>

The classic example: a user with a Solana wallet depositing USDC into Aave on Ethereum. Today, that requires bridging funds, switching to an Ethereum wallet, acquiring ETH for gas, and manually completing each step. With Intents Connect, the user signs a single intent from their existing wallet. Everything else is handled.

### **What makes it non-custodial**

The user's signature authorises one specific, bounded outcome. Intents Connect never holds funds or acts beyond what the user has explicitly approved.&#x20;

An intermediary account on the destination chain executes the action, but it is fully owned by the origin wallet and controlled via Chain Signature, an [MPC technology](https://docs.near.org/chain-abstraction/chain-signatures/getting-started) based on Near protocol.

### **Who it's for**

Intents Connect is built for dApps that want to open their protocols to users on any chain, without requiring those users to manage the complexity of cross-chain execution.

It can also be used as an API by apps, backend systems and AI agents who need a straightforward solution to perform any type of operations on the chain.


# Supported Chains

Below is a list of blockchains for which Intents Connect can integrate with:

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="59.625">‎</th><th>CHAIN</th><th>SOURCE</th><th>DESTINATION</th></tr></thead><tbody><tr><td><img src="/files/3TMJHPLDv9jPeaKoBCpq" alt="‎ " data-size="line"></td><td>ADI</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/5MdN9CaOPOc5gL65WowM" alt="" data-size="line"></td><td>Aleo</td><td>✅ Supported</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/Y4jp8lJt3VAmAhcZXlvS" alt="" data-size="line"></td><td>Arbitrum</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/PgR51HT4CoiG3hxGD1eE" alt="" data-size="line"></td><td>Aurora</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/dbRLOxgWBb5TKyHNfoRk" alt="" data-size="line"></td><td>Avalanche</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/eQCNu5AT5BDgRfnxLPof" alt="" data-size="line"></td><td>Base</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/SG75T9nLhPXLWVNHG7tA" alt="" data-size="line"></td><td>Bera</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/eV6tbERf5hoiw3OnclnS" alt="" data-size="line"></td><td>Binance Smart Chain</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/KyvtFMxJVw7U4iOtZS3v" alt="" data-size="line"></td><td>Bitcoin</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/xHrIedtsjNmnd2bSxdrn" alt="" data-size="line"></td><td>Bitcoin Cash</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/YSLVZFeIEeHfvCaiZJjO" alt="" data-size="line"></td><td>Cardano</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/uivorID6vldc3OnzIqiF" alt="" data-size="line"></td><td>Dash</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/MI0F9k03AHjvfu7k0CJ0" alt="" data-size="line"></td><td>Dogecoin</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/9GMNeb7aSeyjAl4BZAlm" alt="" data-size="line"></td><td>Ethereum</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/9To32s13yHKCpV4ocoQP" alt="" data-size="line"></td><td>Gnosis</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/KIz44F0jBmAIO57BpbRH" alt="" data-size="line"></td><td>Hyperliquid</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/FVO5fls0pdWU9xz4eEVB" alt="" data-size="line"></td><td>Litecoin</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/C5flpvND0b50ZVPXXoo5" alt="" data-size="line"></td><td>Monad</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/R1KCSwtvWIdUog0usGKv" alt="" data-size="line"></td><td>NEAR</td><td>✅ Supported</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/hGMywzPPsG8DtLEpyY2b" alt="" data-size="line"></td><td>Optimism</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/EIAQtyX84IdcqBFNlRHr" alt="" data-size="line"></td><td>Plasma</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/qqth9R8ovyroAS1uKLj1" alt="" data-size="line"></td><td>Polygon</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/dpPqAgrtn7jhyvGsy50n" alt="" data-size="line"></td><td>Scroll</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/ga9kAWhqZXqPZU93WOC1" alt="" data-size="line"></td><td>Solana</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/kTiQWNy7oEyVHCLYT3kd" alt="" data-size="line"></td><td>Starknet</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/shBCBJE9ub6pvsJgC4hm" alt="" data-size="line"></td><td>Stellar</td><td>✅ Supported</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/18ZAvVYV1TGltUgv2qMG" alt="" data-size="line"></td><td>Sui</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/scqD9LQbaRLO4ks8574N" alt="" data-size="line"></td><td>TON</td><td>✅ Supported</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/cJsy0zs1wZ0h49sVZQRS" alt="" data-size="line"></td><td>Tron</td><td>✅ Supported</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/JyErasHvx1dxp16FMg65" alt="" data-size="line"></td><td>XLayer</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/AZj68Bm65FieD7mV7NPl" alt="" data-size="line"></td><td>XRP</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr><tr><td><img src="/files/FKIbuoo07NQdpVADeUwF" alt="" data-size="line"></td><td>Zcash</td><td>🔜 Coming soon</td><td>🔜 Coming soon</td></tr></tbody></table>


# Deep dive


# How It Works

{% hint style="warning" %}
**Pre-release** — Details are subject to change as the design is finalised.
{% endhint %}

## User flow

When a user initiates an action through Intents Connect, the following happens:

{% stepper %}
{% step %}

### **The user initiates an action**&#x20;

The user connects to your dApp frontend and selects what they want to do — for example, deposit 100 USDC into Aave on Ethereum — from their Solana wallet.
{% endstep %}

{% step %}

### **Intents Connect constructs the intent**&#x20;

The backend prepares a structured, typed message describing the exact action: the source chain, destination chain, asset, amount, target protocol, fees, expiry, and a nonce to prevent replay. Nothing is ambiguous or open-ended.
{% endstep %}

{% step %}

### **The user signs and deposits**&#x20;

The user signs the intent message from their source wallet and deposits the source asset to the designated deposit address. This signature is the only action required from the user.
{% endstep %}

{% step %}

### **Cross-chain execution**&#x20;

Intents Connect handles the cross-chain swap using [Near Intents](https://docs.near-intents.org/) — converting the source asset into the required destination asset — and funds the intermediary account on the target chain.
{% endstep %}

{% step %}

### **The intermediary account acts**

The intermediary account, authorised by the user's signature and remotely controlled by [Chain Signature](https://docs.near.org/chain-abstraction/chain-signatures/getting-started), executes the target-chain transaction — in this example, depositing USDC into Aave. The user's intent is fulfilled.

The user sees the result in the UI: transaction hashes, status updates, and a clear record of exactly what was executed.
{% endstep %}
{% endstepper %}

## Core concepts

### **Intent**&#x20;

A signed, structured message from the user that authorises a specific cross-chain action. An intent defines exactly what will happen: the chains involved, the asset, the amount, the target protocol, the fees, and an expiry. One intent authorises one bounded outcome — never a general right to move funds.

### **Deposit account**

A deposit account on the source chain is used for operating the cross-chain swap on top of NEAR Intents by temporarily transferring assets to a trusted swapping agent.

### **Intermediary account**

An execution account on the destination chain, derived from the user's identity on the source chain. It holds the destination asset temporarily and executes the target protocol interaction on the user's behalf. Its authorisation is scoped to the specific signed intent — it cannot act outside of it.

### **Transaction planner**&#x20;

The internal component that converts a high-level intent into the concrete sequence of transactions needed on the destination chain. This includes token approvals, permit flows, protocol-specific calldata, and multi-call bundling where supported.

### **Execution lifecycle**&#x20;

Each intent moves through a defined set of states:&#x20;

`CREATED → DEPOSIT_PENDING → DEPOSIT_PROCESSING → OPERATION_PENDING → OPERATION_PROCESSING → SUCCESS`

If execution fails at any point, the system follows a defined failure policy — no funds are silently lost, and recovery paths are documented.

Read more about [Execution Lifecycle](/intents-connect/deep-dive/execution-lifecycle).

### **Gas and fees**&#x20;

Intents Connect handles destination-chain gas on the user's behalf. Fees are quoted upfront before the user signs, and the breakdown is always visible: gas estimate, service fee, and the final amount the user can expect to land in the target protocol.


# Security & Trust Model

{% hint style="warning" %}
**Pre-release** — Details are subject to change as the design is finalised.
{% endhint %}

### What the user signs

Every action through Intents Connect requires an explicit user signature. The signed message contains the complete specification of the action:

* Source wallet and source chain
* Destination chain and target protocol
* Asset, amount, and receiver
* Maximum fee
* Allowed execution path
* Deadline and nonce

Nothing outside this authorisation can be executed. The message is structured so it can be displayed clearly to the user before signing — the intent is human-readable, not an opaque hash.

### Replay protection

Each intent includes a nonce and an expiry deadline. A signed intent cannot be replayed after it expires, and each nonce can only be used once per wallet.

### Scope of the intermediary account

The intermediary account on the destination chain is fully controlled by the origin wallet that signs the intent. It:

* Cannot act outside the scope of the specific signed intent
* Cannot be triggered by any party outside the authorised execution path
* Cannot execute any transaction without the explicit authorisation from the origin wallet

### Failure handling

If execution cannot be completed due to gas spikes, protocol pauses, or other unexpected conditions, the system doesn't control the intermediary account, so explicit user authorisation is required.

Unspent or stranded funds are not silently abandoned. The failure reason is surfaced to the UI, and recovery paths are available for every failure state.

Read more about failure handling under [Execution Lifecycle](/intents-connect/deep-dive/execution-lifecycle).


# API Usage

{% hint style="warning" %}
**Pre-release** — The Intents Connect API is currently in development. If you're interested in early access, get in touch with the team.
{% endhint %}

The Intents Connect API gives developers full programmatic control over cross-chain protocol interactions. Rather than embedding a UI component, you call the API directly — constructing intents, receiving transaction plans, and managing execution from your own backend or application logic.

#### When to use the API

The API is the right integration path if you want to:

* Enable AI agents to perform any operation on the chain
* Build a custom interface around Intents Connect
* Orchestrate cross-chain actions programmatically as part of a larger workflow
* Integrate into a backend system where an embeddable widget is not applicable

If you want a frontend-first integration, the [Intents Connect Widget](/intents-connect/intents-connect-widget)[ ](https://docs.intents.aurora.dev/intents-connect-widget/introduction)may be a better starting point.

#### What the API handles

You provide the intent parameters — source chain and wallet, destination chain, target protocol, asset, and amount. The API then:

1. Validates the intent and checks for replay safety
2. Prepares the full transaction plan for the destination chain
3. Estimates gas and quotes fees upfront, before execution
4. Executes on the destination chain via the intermediary account
5. Returns status updates and transaction hashes throughout

#### Get early access

The API is moving toward public availability. To register interest or request early access, contact the team. [Talk to the team](mailto:contact@aurora.dev)


# Execution Lifecycle

An **Execution** represents a state machine that coordinates two independent phases:

1. **Deposit phase** (funds arrival and confirmation)
2. **Operation phase** (transaction execution on the destination chain)

Depending on the usage scenario, these phases may occur in reverse order.

### Core Concepts

* **Execution**: A single user intent being processed.
* **Deposit**: Movement of funds into the system (source → destination).
* **Operation**: Action performed using those funds (e.g., deposit, withdraw).
* **Intermediary account**: Account on the destination chain derived from the source account, controlled by the source private key.

Each execution progresses through a deterministic set of states. Execution state can be [requested](/api-reference/intents-connect-api-reference/request-an-execution) and [monitored](/api-reference/intents-connect-api-reference/fetch-executions) through an API:

* status is polled
* state is authoritative
* transitions are eventual

## States

### Active States

After creating the execution via the API, to continue the flow, instruct the user to make a deposit, authorise the operation, or both, as explained in the [Execution Scenarios](#execution-scenarios).

| Status                 | Description                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------------ |
| `CREATED`              | Execution requested by integrator. Awaiting next step (deposit or operation, depending on flow). |
| `DEPOSIT_PENDING`      | Deposit transaction detected or expected but not yet confirmed.                                  |
| `DEPOSIT_PROCESSING`   | Deposit is being verified and finalised (e.g., confirmations, indexing).                         |
| `OPERATION_PENDING`    | Deposit completed. Operation is queued but not yet submitted.                                    |
| `OPERATION_PROCESSING` | Operation submitted and awaiting confirmation on the destination chain.                          |
| `SUCCESS`              | Execution completed successfully.                                                                |

* `DEPOSIT_PENDING → DEPOSIT_PROCESSING` occurs when the deposit transaction is detected on-chain.
* `DEPOSIT_PROCESSING → OPERATION_PENDING` occurs after the deposit is completed.
* `OPERATION_PENDING → OPERATION_PROCESSING` occurs when the deposit transaction is detected on-chain.
* `OPERATION_PROCESSING → SUCCESS` occurs after the on-chain transaction is successful.

### Failure States

All failure states are terminal. Recovery actions (e.g., withdrawal or retry via a new execution) must be handled explicitly by the client, because assets end up in intermediary accounts, requiring a retry or withdrawal.

It is recommended to let the user decide on the exact outcome, which may require creating a new execution.

|                    |                                                                                                                       |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `EXPIRED`          | The deposit was not completed within the allowed time window specified by the execution metadata returned by the API. |
| `DEPOSIT_FAILED`   | Deposit failed or was reverted/refunded.                                                                              |
| `OPERATION_FAILED` | Operation failed after deposit completion. Recovery actions may be required (e.g. rety or withdrawal).                |

* `DEPOSIT_PENDING → EXPIRED` occurs once the deposit wasn't made in the time frame returned by the API. Transferred funds will be refunded.
* `DEPOSIT_PROCESSING → DEPOSIT_FAILED` occurs on deposit failure, such as an invalid deposit amount. Transferred funds will be refunded.
* `OPERATION_PROCESSING → OPERATION_FAILED` occurs when the destination chain transaction failed and wasn't included on the chain.

### State Machine Invariant

* Execution is **strictly linear within each phase** (deposit → operation or operation → deposit).
* Failure states are **terminal**.
* Only one phase is active at a time.
* Transitions are **event-driven** (deposit detected, on-chain confirmations, backend validations).

## Execution Scenarios

There are different scenarios in which you can use the [API](/api-reference/intents-connect-api-reference), each covering a different user flow. Below is a list of the most common scenarios.

State transitions are strictly sequential within each phase; no states are skipped.

### 1. Inbound Execution (Deposit → Operation)

User funds originate from a source chain and are used on the destination chain.

This scenario involves two user actions:

1. Transfer funds into the deposit account created by execution.
2. Sign the operation authorisation message and submit it.

<figure><img src="/files/DB1y5APmMmTI7oLcuH2Z" alt=""><figcaption></figcaption></figure>

### 2. Outbound Execution (Operation → Withdraw)

The user authorises the operation first, then receives the funds back to the source.

In outbound flows, the “deposit” phase represents settlement (funds returning to the user), not inbound funding.

This scenario involves a single user's actions:

1. Sign the operation authorisation message and submit it.

Note: The transfer into the deposit account used for withdrawal must be handled in the signed operation.

<figure><img src="/files/LkdcoDZd4pzogzC5ahoY" alt=""><figcaption></figcaption></figure>

### 3. Destination-Only Execution (Operation Only)

No cross-chain movement. Execution happens entirely on the destination chain.

This scenario involves a single user's actions:

1. Sign the operation authorisation message and submit it.

<figure><img src="/files/3lvXlJc90dWO4dj1HXRV" alt=""><figcaption></figcaption></figure>


# Intents Connect Widget


# Introduction

{% hint style="warning" %}
**Pre-release** — The Intents Connect Widget is currently in development. If you're interested in early access, get in touch.
{% endhint %}

The Intents Connect Widget is an embeddable UI component that lets your users interact with your dApp from any other chain or using any asset, without leaving your interface or managing any cross-chain complexity themselves.

<figure><img src="https://387686700-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLp9IVb8Xkubcbzzk80Br%2Fuploads%2FDWhjQoQp33t1HdJ7yQ8z%2Fimage%2019.png?alt=media&#x26;token=2632ab3d-4c17-4085-a5a4-7dd14ad18950" alt=""><figcaption></figcaption></figure>

#### How it differs from the Swap Widget

The **Swap Widget** handles asset swaps: a user selects a source token and a destination token, and the widget handles the exchange. It is self-contained and designed to be dropped into any application quickly.

The **Intents Connect Widget** handles cross-chain protocol interactions. Rather than just swapping assets, it enables actions like depositing into a lending protocol, taking a position, or interacting with a smart contract on a chain the user does not have assets on. The user stays in your dApp, signs once, and the widget handles execution end-to-end.

#### When to use it

The Intents Connect Widget is the right integration path if you want to:

* Accept users from any chain into a protocol that lives on a specific chain
* Offer one-click access to cross-chain actions without requiring users to bridge, switch wallets, or manage gas
* Embed a ready-made UI rather than building a custom interface via the Intents Connect API

#### Get early access

To register interest or request early access, contact the team.


# Coming Soon

{% hint style="warning" %}
**Pre-release** — The Intents Connect Widget is not yet publicly available.
{% endhint %}

Full integration documentation — including installation, configuration, theming, and wallet connection — will be published ahead of launch.

In the meantime, you can:

* Read about [How Intents Connect Works](/intents-connect/deep-dive/how-it-works) to understand the execution model
* Explore the [Intents Connect AP](/api-reference/intents-connect-api-reference)I if you prefer a programmatic integration
* Get in touch to register interest or request early access


# Examples


# Deposit into Aave from Solana

{% hint style="warning" %}
**Pre-release** — The Intents Connect API is currently in development. If you're interested in early access, get in touch with the team.
{% endhint %}

Below is an example flow depositing into Aave using a Solana wallet using the Intents Connect API.

You'll need to reference supported assets by their IDs, then run a dry execution to get the estimated output amount, and estimate costs for the destination chain action, signing the intent (deposit to Aave), and the transfer transaction to move SOL from the Solana wallet. As a result, you'll have deposited Aave into an intermediary account associated with your Solana wallet.

Make sure to understand [Execution Lifecycle](/intents-connect/deep-dive/execution-lifecycle) before you proceed with the implementation.

{% stepper %}
{% step %}

### List supported tokens

Use [List supported tokens](/api-reference/intents-connect-api-reference/list-supported-tokens) to find the `assetId` values you will need. You'll need to use the Intents API for that.

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/supported_tokens`);
const tokens = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Response explanation</summary>

The response includes tokens with their `assetId` in this format:

* SOL tokens: `nep141:sol.omft.near`
* USDC on Base: `nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near`

```json
[
  {
    "assetId": "nep141:sol.omft.near",
    "decimals": 9,
    "blockchain": "sol",
    "symbol": "SOL",
    "price": 84.79,
    "priceUpdatedAt": "2026-04-23T17:44:00.452Z"
  },
  {
    "assetId": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "decimals": 6,
    "blockchain": "base",
    "symbol": "USDC",
    "price": 0.999706,
    "priceUpdatedAt": "2026-04-23T17:44:00.452Z",
    "contractAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
  }
]
```

</details>
{% endstep %}

{% step %}

### Request an execution

Use [Request an execution](/api-reference/intents-connect-api-reference/request-an-execution) endpoint.

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/executions/${solanaWalletAccount}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    // Generate your API key at https://studio.aurora.dev
    'x-api-key': 'API_KEY',
  },
  body: JSON.stringify({
    "dry": false,
    "metadata": {
      "intent": "aave_supply",
      "title": "Supply to Aave"
    },
    "quote": {
      "amount": "1270000",
      "destinationAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
      "originAsset": "nep141:sol.omft.near",
      "slippageTolerance": 100
    },
    "steps": [
      {
        "functionSignature": "approve(address,uint256)",
        "parameters": [
          "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
          "{MIN_AMOUNT_OUT}"
        ],
        "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
        "value": "0"
      },
      {
        "functionSignature": "supply(address,uint256,address,uint16)",
        "parameters": [
          "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
          "{MIN_AMOUNT_OUT}",
          "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
          "0"
        ],
        "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
        "value": "0"
      }
    ],
    "type": "evm"
  })
});
const execution = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Request data explanation</summary>

* `dry` is false, unless we want to get just the estimates
* `metadata` contains information displayed in the UI - it's optional
* `quote`
  * `amount` contains the input amount used for the action
  * `destinationAsset` assetId used for the action on the destination chain
  * `originChain` assetId deposited into the deposit account
  * `slippageTolerance` value is basis points, so 100 is 1%
* `steps` not used in the initial dry run
* `type` type of the action
* `steps` are now used, and this is how we defined an action on the destination chain
  * `functionSignature` [function signature](https://docs.soliditylang.org/en/latest/contracts.html#function-signatures-and-selectors-in-libraries) to be called on the EVM chain
  * `parameters` arguments to be passed to the function
  * `to` contact to interact with
  * `value` native value - in case a native token needs to be used

</details>

<details>

<summary>Example response</summary>

| Field                          | Description                                                                                                                                  |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `details.payload.payload_json` | Message to be signed by the wallet                                                                                                           |
| `id`                           | Identifier of the execution, needed for tracking                                                                                             |
| `quote.depositAddress`         | Deposit account to which the transfer needs to be done                                                                                       |
| `status`                       | Status of the transaction, used for understanding the lifecycle of the execution                                                             |
| `steps`                        | Updated steps were built based on the user's signed input steps. The additional transfer added to the array is used to reimburse the gas fee |

```json
{
    "result": {
        "createdAt": "2026-04-28T15:21:41Z",
        "details": {
            "estimatedTime": "22",
            "intermediaryAddress": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
            "messageSigned": false,
            "messageToSign": "...",
            "networkFee": "7150",
            "payload": {
                "payload_bytes_base64": "..",
                "payload_json": "...",
                "standard": "raw_ed25519"
            },
            "serviceFee": "0",
            "signingStandard": "raw_ed25519"
        },
        "id": "33ca3807-0e1b-455f-a6dd-1a482ec9b385",
        "metadata": {
            "title": "Supply to Aave (single-round)",
            "url": "https://last-mile-fe-check.vercel.app/actions_min_amount",
            "intent": "aave_supply"
        },
        "quote": {
            "amount": "1270000",
            "amountIn": "1270000",
            "amountInUsd": "0.1059",
            "amountOut": "95300",
            "amountOutUsd": "0.1024",
            "deadline": "2026-04-28T15:31:37Z",
            "depositAddress": "81VBKaGXxy6chA9KjsiuLiAqiaNnsTvjRLe2DX1jYq9M",
            "destinationAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
            "minAmountOut": "94275",
            "originAsset": "nep141:sol.omft.near",
            "recipient": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
        },
        "status": "CREATED",
        "steps": [
            {
                "functionSignature": "approve(address,uint256)",
                "parameters": [
                    "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
                    "94275"
                ],
                "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
                "value": "0"
            },
            {
                "functionSignature": "supply(address,uint256,address,uint16)",
                "parameters": [
                    "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
                    "94275",
                    "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
                    "0"
                ],
                "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
                "value": "0"
            },
            {
                "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                "functionSignature": "transfer(address,uint256)",
                "parameters": [
                    "0x546252c9a0E974f75892b4c54b7a67B69a0aFf45",
                    "7150"
                ],
                "value": "0",
                "metadata": {
                    "name": "Fee Transfer",
                    "description": "Gas fee reimbursement"
                }
            }
        ],
        "type": "evm",
        "version": "1.0"
    }
}
```

</details>
{% endstep %}

{% step %}

### Sign the message

`details.payload.payload_json` needs to be signed by the Solana wallet so it can be submitted in the next step.
{% endstep %}

{% step %}

### Submit the digest

Use [Submit digest](/api-reference/intents-connect-api-reference/submit-digest) to submit the signed message. This will allow executing batched transactions on the destination chain.

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/executions/${solanaWalletAccount}/submit`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "executionId": "92a10832-d36b-46c6-807d-7f28469c2a94",
    "publicKey": "ed25519:BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4",
    "signature": "ed25519:5VUXRtVgS6bq3Wn64YdCt5NSPfA1Ni5zqiLFsSScyq6Dj53pbNEcwKcp7t1aRqw8zWCoN7coMLBUmatmwLAEvndP"
  })
});
const execution = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Request data explanation</summary>

* `executionId` ID of the execution
* `publicKey` public key of the wallet
* `signature` signed message signature

</details>

<details>

<summary>Example response</summary>

```json
{
  "result": {
    "status": "SIGNED_PENDING_DEPOSIT"
  }
}
```

</details>
{% endstep %}

{% step %}

### Deposit into the deposit account

Now, deposit exactly `quote.amount` into `quote.depositAddress` so the swap can execute.
{% endstep %}

{% step %}

### Monitor the status of the execution

Use [Fetch executions](/api-reference/intents-connect-api-reference/fetch-executions) using `id` of the execution

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/executions/BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4?id=92a10832-d36b-46c6-807d-7f28469c2a94`);
const execution = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Response explanation</summary>

The response will contain the entire execution object, but look out for `status` the field that indicates the lifecycle of the execution. `SUCCESS` means the execution is fully processed.

</details>
{% endstep %}
{% endstepper %}


# Withdraw from Aave to Solana

{% hint style="warning" %}
**Pre-release** — The Intents Connect API is currently in development. If you're interested in early access, get in touch with the team.
{% endhint %}

Below is an example flow withdrawing from Aave using a Solana wallet using the Intents Connect API.

You'll need to run an execution to get the estimated output amount, estimate costs for the destination chain action, and sign the intent (withdraw from Aave). As a result, you'll withdraw from Aave into your Solana wallet.

Make sure to understand [Execution Lifecycle](/intents-connect/deep-dive/execution-lifecycle) before you proceed with the implementation.

{% stepper %}
{% step %}

### List supported tokens

Use [List supported tokens](/api-reference/intents-connect-api-reference/list-supported-tokens) to find the `assetId` values you will need. You'll need to use the Intents API for that.

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/supported_tokens`);
const tokens = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Response explanation</summary>

The response includes tokens with their `assetId` in this format:

* SOL tokens: `nep141:sol.omft.near`
* USDC on Base: `nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near`

```json
[
  {
    "assetId": "nep141:sol.omft.near",
    "decimals": 9,
    "blockchain": "sol",
    "symbol": "SOL",
    "price": 84.79,
    "priceUpdatedAt": "2026-04-23T17:44:00.452Z"
  },
  {
    "assetId": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "decimals": 6,
    "blockchain": "base",
    "symbol": "USDC",
    "price": 0.999706,
    "priceUpdatedAt": "2026-04-23T17:44:00.452Z",
    "contractAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
  }
]
```

</details>
{% endstep %}

{% step %}

### Request an execution

Use [Request an execution](/api-reference/intents-connect-api-reference/request-an-execution) endpoint.

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/executions/${solanaWalletAccount}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    // Generate your API key at https://studio.aurora.dev
    'x-api-key': 'API_KEY',
  },
  body: JSON.stringify({
    "dry": false,
    "metadata": {
      "intent": "aave_withdraw",
      "title": "Withdraw from Aave"
    },
    "outOperation": true,
    "quote": {
      "amount": "197373",
      "destinationAsset": "nep141:sol.omft.near",
      "originAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
      "slippageTolerance": 100
    },
    "steps": [
      {
        "functionSignature": "withdraw(address,uint256,address)",
        "parameters": [
          "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "197373",
          "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
        ],
        "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
        "value": "0"
      },
      {
        "functionSignature": "transfer(address,uint256)",
        "parameters": [
          "{DEPOSIT_ADDRESS}",
          "197373"
        ],
        "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "value": "0"
      }
    ],
    "type": "evm"
  })
});
const execution = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Request data explanation</summary>

* `dry` is false, unless we want to get just the estimates
* `metadata` contains information displayed in the UI - it's optional
* `outOperation` means that it's outbound operations and does not require a deposit
* `quote`
  * `amount` contains the input amount used for the action
  * `destinationAsset` assetId used for the action on the destination chain
  * `originChain` assetId deposited into the deposit account
  * `slippageTolerance` value is basis points, so 100 is 1%
* `steps` is used to define an operation on the destination chain
  * `functionSignature` [function signature](https://docs.soliditylang.org/en/latest/contracts.html#function-signatures-and-selectors-in-libraries) to be called on the EVM chain
  * `parameters` arguments to be passed to the function
    * Notice `{DEPOSIT_ADDRESS}`, which is a placeholder for the deposit account generated during quoting
  * `to` contact to interact with
  * `value` native value - in case a native token needs to be used
* `type` type of the action

</details>

<details>

<summary>Example response</summary>

| Field                          | Description                                                                                                                                  |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `details.payload.payload_json` | Message to be signed by the wallet                                                                                                           |
| `id`                           | Identifier of the execution, needed for tracking                                                                                             |
| `status`                       | Status of the transaction, used for understanding the lifecycle of the execution                                                             |
| `steps`                        | Updated steps were built based on the user's signed input steps. The additional transfer added to the array is used to reimburse the gas fee |

```json
{
    "result": {
        "createdAt": "2026-04-27T17:53:45Z",
        "details": {
            "estimatedTime": "32",
            "intermediaryAddress": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
            "messageSigned": false,
            "messageToSign": "<REDACTED>",
            "networkFee": "6952",
            "payload": {
                "payload_bytes_base64": "<REDACTED>",
                "payload_json": "<REDACTED>",
                "standard": "raw_ed25519"
            },
            "serviceFee": "0",
            "signingStandard": "raw_ed25519"
        },
        "id": "eb4c80e8-e491-42da-87d9-f5879d91b7f6",
        "metadata": {
            "title": "Withdraw from Aave",
            "url": "https://last-mile-fe-check.vercel.app/withdraw",
            "intent": "aave_withdraw"
        },
        "quote": {
            "amount": "197373",
            "amountIn": "190421",
            "amountInUsd": "0.1904",
            "amountOut": "2214445",
            "amountOutUsd": "0.1871",
            "deadline": "2026-04-27T18:03:38Z",
            "depositAddress": "0x52dF3dE8e121332635ef319e4af9cCb98fd74a6f",
            "destinationAsset": "nep141:sol.omft.near",
            "minAmountOut": "2192300",
            "originAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
            "recipient": "BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4"
        },
        "status": "OPERATION_PENDING",
        "steps": [
            {
                "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
                "functionSignature": "withdraw(address,uint256,address)",
                "parameters": [
                    "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                    "197373",
                    "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
                ],
                "value": "0"
            },
            {
                "functionSignature": "transfer(address,uint256)",
                "parameters": [
                    "0x52dF3dE8e121332635ef319e4af9cCb98fd74a6f",
                    "190421"
                ],
                "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                "value": "0"
            },
            {
                "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                "functionSignature": "transfer(address,uint256)",
                "parameters": [
                    "0x546252c9a0E974f75892b4c54b7a67B69a0aFf45",
                    "6952"
                ],
                "value": "0",
                "metadata": {
                    "name": "Fee Transfer",
                    "description": "Gas fee reimbursement"
                }
            }
        ],
        "type": "evm",
        "version": "1.0"
    }
}
```

</details>
{% endstep %}

{% step %}

### Submit the digest

Use [Submit digest](/api-reference/intents-connect-api-reference/submit-digest) to submit the signed message. This will allow executing batched transactions on the destination chain.

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/executions/${solanaWalletAccount}/submit`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "executionId": "74e0cbfe-def3-47fb-8f1c-469778c6acbc",
    "publicKey": "ed25519:BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4",
    "signature": "ed25519:466cjq2diW62zHhHik8hQQGLjLTg2drnZCEjBxDnUDZCoN4HmcXv4F4WTe2LqDgJk8Ccaq1rjusA47DeUWKegNy1"
  })
});
const execution = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Request data explanation</summary>

* `executionId` ID of the execution
* `publicKey` public key of the wallet
* `signature` signed message signature

</details>

<details>

<summary>Example response</summary>

```json
{
  "result": {
    "status": "SIGNING"
  }
}
```

</details>
{% endstep %}

{% step %}

### Monitor the status of the execution

Use [Fetch executions](/api-reference/intents-connect-api-reference/fetch-executions) using `id` of the execution

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-connect-alpha-api.aurora.dev/api/v1/executions/BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4?id=74e0cbfe-def3-47fb-8f1c-469778c6acbc`);
const execution = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Response explanation</summary>

The response will contain the entire execution object, but look out for `status` the field that indicates the lifecycle of the execution. `SUCCESS` means the execution is fully processed.

</details>
{% endstep %}
{% endstepper %}


# Developer Guides


# EVM


# EVM steps - Aave supply

A worked, self-contained example of bridging a token in from any origin chain and supplying it to an **Aave V3** pool on the destination EVM chain to earn yield.

Supplying is two steps in one execution:

1. **`approve`** — let the Aave Pool pull the bridged token from the intermediary.
2. **`supply`** — move the token into the pool and mint back **aTokens** (the pool's interest-bearing receipt) to the intermediary.

The service then appends its **own** fee step that debits the intermediary in the **destination token**, so the amount you supply and the fee together must not exceed what the bridge actually delivers. This doc covers the **amount recalculation** flow — three rounds, where you compute that amount yourself and can show the user an exact figure before they sign.

### Three rounds vs one

There are two ways to size the supply amount. They produce the same on-chain result; they differ in how many round trips you make and in who computes the number.

|                                   | **single-round** (`{MIN_AMOUNT_OUT}`)      | **three-round** (this doc)                                              |
| --------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------- |
| Create requests                   | 1 (`dry: false`)                           | 3 (`dry: true`, `dry: true`, `dry: false`)                              |
| Supply amount in your steps       | the literal `{MIN_AMOUNT_OUT}` placeholder | a concrete `uint256`                                                    |
| Who carves the fee                | the service, inside the same call          | the service too, one round earlier — you copy the carved figure forward |
| Exact figure known before signing | no                                         | yes                                                                     |

`{MIN_AMOUNT_OUT}` exists precisely to collapse this three-round protocol into one call, and it is the better default. Reach for the three-round flow when the UI must display the exact supplied amount and fee **before** the user signs, or when you want a fee preview you can abort on.

#### Why three rounds and not two

The number you need — the post-fee bridged amount — depends on the gas fee, and the gas fee depends on the steps, which depend on the number. The rounds break that cycle:

| Round | `dry`   | `steps`              | What the service does                                                           | What you learn                                                       |
| ----- | ------- | -------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| 1     | `true`  | `[]` (empty)         | fetches a 1Click quote, **skips** gas estimation entirely                       | the **gross** `quote.minAmountOut` — enough to build realistic steps |
| 2     | `true`  | built from round 1   | estimates gas against your real steps, then carves the fee out of the quote     | `details.networkFee` and the **post-fee** `quote.minAmountOut`       |
| 3     | `false` | rebuilt from round 2 | same as round 2, plus it creates the execution and prepares the signing payload | `id`, `quote.depositAddress`, `details.payload`                      |

Round 1 has to be step-less: gas estimation only runs when `steps` is non-empty, so an empty array is what gets you a quote with no fee deducted. Round 1 must therefore be `dry: true` — a `dry: false` create without steps is rejected with `steps are required for non-dry executions`.

### Addresses (Aave V3 on Base, USDC)

The example uses **USDC on Base**. A different chain or reserve changes the pool and token addresses but not the shape of the steps.

| Account                   | Address                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------ |
| Aave V3 Pool (Base)       | `0xA238Dd80C259a72e81d7e4664a9801593F98d1c5`                                               |
| USDC (Base)               | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`                                               |
| aBasUSDC (supply receipt) | `0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB`                                               |
| Fee collector             | per-deployment config — read it back from the appended fee step rather than hard-coding it |

**Only `base`, `eth` and `arb` are enabled as EVM destinations.** Anything else is rejected with `400 blockchain <chain> is not supported as a destination` before the rest of this flow applies. The pools on the other two:

| Chain | Pool                                         |
| ----- | -------------------------------------------- |
| `eth` | `0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2` |
| `arb` | `0x794a61358D6845594F94dc1DB02A252b5b4814aD` |

The one address you supply yourself is the **intermediary** — the deterministic EVM account where the bridged tokens land, where the steps execute, and which receives the aTokens:

```http
GET /api/v1/executions/{wallet}/intermediary
```

```json
{ "result": { "evm": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E" } }
```

Fetch it once per wallet, before you build any steps. (For a TON origin, pass `?publicKey=ed25519:<base58>` — a TON address does not expose its public key.)

### The steps

```jsonc
"steps": [
  {
    "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",          // USDC
    "functionSignature": "approve(address,uint256)",
    "parameters": [
      "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",              // Aave Pool
      "87125"                                                     // amount to supply
    ],
    "value": "0"
  },
  {
    "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",          // Aave Pool
    "functionSignature": "supply(address,uint256,address,uint16)",
    "parameters": [
      "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",              // asset = USDC
      "87125",                                                    // same amount
      "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",              // onBehalfOf = intermediary
      "0"                                                         // referralCode
    ],
    "value": "0"
  }
]
```

Both amounts are USDC base units (6 decimals). `onBehalfOf` is the intermediary, so the aTokens are minted to the account that will later withdraw them. The `referralCode` is `0` unless Aave issued you one.

***

## Round 1 — gross quote (`dry: true`, no steps)

```jsonc
POST /api/v1/executions/BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "evm",
  "quote": {
    "originAsset": "nep141:sol.omft.near",
    "amount": "1270000",                        // 0.00127 SOL (9 decimals)
    "destinationAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "slippageTolerance": 100,                   // basis points (1%)
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-30T12:00:00Z"
  },
  "steps": [],
  "metadata": { "title": "Supply to Aave", "intent": "aave_supply" },
  "dry": true
}
```

```jsonc
// response — no details.networkFee, amounts are gross
{
  "result": {
    "status": "CREATED",
    "details": { "intermediaryAddress": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E", "estimatedTime": "22" },
    "quote": {
      "amountIn": "1270000",
      "amountOut": "95300",
      "minAmountOut": "94275",                  // <- build round-2 steps with this
      "deadline": "2026-07-30T12:00:00Z"
    }
  }
}
```

Take **`quote.minAmountOut`** (`94275`) — the worst-case delivered amount, before the fee. Nothing is created and nothing is reserved by this call.

## Round 2 — measured fee (`dry: true`, real steps)

Same envelope, `dry` still `true`, now with the steps built at `94275`:

```jsonc
POST /api/v1/executions/BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "evm",
  "quote": {
    "originAsset": "nep141:sol.omft.near",
    "amount": "1270000",
    "destinationAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "slippageTolerance": 100,
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-30T12:00:00Z"
  },
  "steps": [
    {
      "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "functionSignature": "approve(address,uint256)",
      "parameters": ["0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "94275"],
      "value": "0"
    },
    {
      "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
      "functionSignature": "supply(address,uint256,address,uint16)",
      "parameters": [
        "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "94275",
        "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
        "0"
      ],
      "value": "0"
    }
  ],
  "metadata": { "title": "Supply to Aave", "intent": "aave_supply" },
  "dry": true
}
```

```jsonc
// response — fee measured, amounts carved, fee step echoed
{
  "result": {
    "status": "CREATED",
    "details": {
      "intermediaryAddress": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
      "estimatedTime": "22",
      "networkFee": "7150"                      // 0.00715 USDC
    },
    "quote": {
      "amountIn": "1270000",
      "amountOut": "88150",                     // 95300 − 7150
      "minAmountOut": "87125"                   // 94275 − 7150  <- supply this
    },
    "steps": [
      { "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "functionSignature": "approve(address,uint256)",  "parameters": ["0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "94275"], "value": "0" },
      { "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "functionSignature": "supply(address,uint256,address,uint16)", "parameters": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "94275", "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E", "0"], "value": "0" },
      {
        "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "functionSignature": "transfer(address,uint256)",
        "parameters": ["0x546252c9a0E974f75892b4c54b7a67B69a0aFf45", "7150"],
        "value": "0",
        "metadata": { "name": "Fee Transfer", "description": "Gas fee reimbursement" }
      }
    ]
  }
}
```

Two things to read out of this response:

* **`details.networkFee`** — the gas fee, denominated in the **destination token**, that the appended step will debit.
* **`quote.minAmountOut`** — already `94275 − 7150 = 87125`. The response amounts are always post-fee once a fee was estimated, so you do **not** subtract again. This is the amount to supply.

**If `details.networkFee` is absent, stop.** It means gas estimation failed, so nothing was carved and `quote.minAmountOut` is still the gross figure. Copying it into round 3 bakes an amount the batch cannot cover once the fee is added. Retry the round instead.

The third step in the echo is the service's own fee transfer. You never send it yourself — it is appended to every response that carries a `networkFee`, dry or not, and it is part of the batch the user signs.

The arithmetic that has to hold at execution time:

```
supplied (87125) + fee (7150) = 94275 = the bridge's guaranteed delivery
```

Anything the bridge delivers above `minAmountOut` simply stays in the intermediary.

## Round 3 — create (`dry: false`, steps rebuilt at the post-fee amount)

Identical body with the steps rebuilt at `87125` and `dry: false`:

```jsonc
POST /api/v1/executions/BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "evm",
  "quote": {
    "originAsset": "nep141:sol.omft.near",
    "amount": "1270000",
    "destinationAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "slippageTolerance": 100,
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-30T12:00:00Z"
  },
  "steps": [
    {
      "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "functionSignature": "approve(address,uint256)",
      "parameters": ["0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", "87125"],
      "value": "0"
    },
    {
      "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
      "functionSignature": "supply(address,uint256,address,uint16)",
      "parameters": [
        "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "87125",
        "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
        "0"
      ],
      "value": "0"
    }
  ],
  "metadata": { "title": "Supply to Aave", "intent": "aave_supply" },
  "dry": false
}
```

```jsonc
// 201 response
{
  "result": {
    "id": "33ca3807-0e1b-455f-a6dd-1a482ec9b385",
    "createdAt": "2026-07-30T11:51:41Z",
    "status": "CREATED",
    "details": {
      "intermediaryAddress": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
      "estimatedTime": "22",
      "networkFee": "7150",
      "messageSigned": false,
      "messageToSign": "…",
      "signingStandard": "raw_ed25519",
      "payload": {
        "standard": "raw_ed25519",
        "payload_json": "…",
        "payload_bytes_base64": "…"
      }
    },
    "quote": {
      "amount": "1270000",
      "amountIn": "1270000",
      "amountOut": "88150",
      "minAmountOut": "87125",
      "depositAddress": "81VBKaGXxy6chA9KjsiuLiAqiaNnsTvjRLe2DX1jYq9M",
      "depositMemo": null,
      "recipient": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
    },
    "steps": [ /* your two steps at 87125, plus the fee transfer */ ],
    "type": "evm",
    "version": "1.0"
  }
}
```

**Before signing, compare `result.quote.minAmountOut` with the amount you baked into the steps.** They should be equal. If the returned value is *lower*, the quote moved between rounds 2 and 3 and the batch will not have enough token to cover `supply + fee`

> A `dry: false` create takes an **in-flight lock** per (intermediary, chain). A second create before the first reaches a terminal state returns `409` with `an execution for this wallet is already in progress. Wait for it to complete or fail before creating a new one`. So you get one shot per attempt: to retry with different numbers, cancel the execution first with `DELETE /api/v1/executions/{wallet}/{executionId}`. That call is not a bare DELETE — it needs a body carrying the wallet's signature over the literal string `delete_execution:{executionId}`.

### Sign and submit

`result.details.payload` is signed with the **origin wallet**, using `result.details.signingStandard` — your origin chain's standard, not the destination's. You never handle EVM calldata.

```http
POST /api/v1/executions/{wallet}/submit
```

```json
{
  "executionId": "33ca3807-0e1b-455f-a6dd-1a482ec9b385",
  "publicKey": "ed25519:BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4",
  "signature": "ed25519:5VUXRtVgS6bq3Wn64YdCt5NSPfA1Ni5zqiLFsSScyq6Dj53pbNEcwKcp7t1aRqw8zWCoN7coMLBUmatmwLAEvndP"
}
```

The body is `{ signature, executionId }` for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, Stellar) adds `publicKey`. A TON origin adds both `publicKey` and a `tonConnect` envelope. No `x-api-key` is needed here.

```json
{ "result": { "status": "SIGNED_PENDING_DEPOSIT" } }
```

Signing before the deposit is deliberate: the service holds the pre-signed batch and fires it the moment the bridge settles.

### Deposit and settle

1. **Transfer** to `result.quote.depositAddress`:
   * `EXACT_INPUT` — exactly `quote.amount` (`1270000` lamports here).
   * `EXACT_OUTPUT` — `result.quote.amountIn`, the origin commitment the service computed. Do not recompute it.
   * A Stellar origin must also attach `quote.depositMemo` or the bridge will not settle.
2. **Record it** so 1Click is notified without waiting for a watcher:

   ```http
   POST /api/v1/executions/deposit/submit
   ```

   ```json
   { "txHash": "<origin tx hash>", "depositAddress": "81VBKaGXxy6chA9KjsiuLiAqiaNnsTvjRLe2DX1jYq9M" }
   ```

   MEMO-mode origins (Stellar) must include `"memo"` — the address alone does not identify the execution and the call answers `404` without it.
3. **Poll** `GET /api/v1/executions/{wallet}?id={executionId}` until terminal. `result` is an **array**, so read `result[0]` even when filtering by a single id:

   ```
   CREATED → DEPOSIT_PENDING → DEPOSIT_PROCESSING →
   OPERATION_PENDING → OPERATION_PROCESSING → SUCCESS
   ```

   `DEPOSIT_FAILED` and `OPERATION_FAILED` are terminal. `EXPIRED` looks terminal but is not always: a deposit that settles late can revive the execution straight to `OPERATION_PROCESSING`.

After `SUCCESS` the intermediary holds aBasUSDC representing the supplied USDC plus accrued interest, redeemable later through an out-operation execution.

### EXACT\_INPUT vs EXACT\_OUTPUT

The steps are identical across swap types. What changes is what `quote.amount` means, how the fee is handled, and how much you deposit.

|                               | **EXACT\_INPUT**                                                                      | **EXACT\_OUTPUT**                                                                                    |
| ----------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `quote.amount` denominated in | the **origin** token (what you send)                                                  | the **destination** token (what you want supplied)                                                   |
| What's fixed                  | the input you bridge                                                                  | the amount that ends up supplied (`≈ Y`)                                                             |
| Fee handling                  | the service carves the fee out of the quote's output, so you supply `delivered − fee` | the service **grosses the 1Click quote up** by the fee first, so after the carve you still net `≈ Y` |
| How much you deposit          | `quote.amount`, verbatim                                                              | `result.quote.amountIn` from the create response                                                     |
| Round-2 `quote.minAmountOut`  | gross output minus the fee                                                            | `≈ Y`                                                                                                |

* **EXACT\_INPUT** — "I'm sending 0.00127 SOL. Supply whatever survives fees."
* **EXACT\_OUTPUT** — "I want 87125 USDC supplied. Charge me whatever that costs." Read `result.quote.amountIn` from round 3 and deposit **that**.

Note that on `EXACT_OUTPUT` the gross-up happens only once a fee exists, so round 1 (no steps, no fee) reports a lower `amountIn` than round 3 does. Use the round-3 value for the deposit.

### Rules

* **`x-api-key` is required** on `POST /api/v1/executions/{wallet}` — every round, dry or not. It fetches a 1Click quote and prices per-key fees server-side. `POST …/submit` and `POST …/deposit/submit` do not take it.
* **`dry: false` requires steps.** An empty `steps` array is only valid on a dry round.
* **Do not add the fee step yourself.** The service appends it; a duplicate would double-charge the intermediary.
* **Step objects accept only `to`, `functionSignature`, `parameters`, `value` and `metadata`.** An unknown key, a differently cased spelling, or a duplicate is a `400`. Anything else you need to carry goes in `metadata`, which is passed through untouched.
* **`quote.recipient` is out-operation only.** On a bridge-in the recipient is always the intermediary; sending it is a `400`.
* **`{MIN_AMOUNT_OUT}` and the three-round flow are mutually exclusive in practice** — the placeholder is what you use *instead* of computing the amount. Mixing them (a placeholder in one step, a literal in another) means the two steps operate on different amounts.

### Errors you will actually hit

| Response                                                                                                             | Cause                                                                                                                                                              |
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400 steps are required for non-dry executions`                                                                      | `dry: false` with an empty `steps` array.                                                                                                                          |
| `400 estimated gas fee exceeds minimum output amount`                                                                | the fee is larger than the quote's `minAmountOut` — the bridged amount is too small to pay for the batch. Send more.                                               |
| `400 estimated gas fee exceeds expected output amount`                                                               | same, against `amountOut`.                                                                                                                                         |
| `400 invalid steps JSON: …`                                                                                          | a malformed parameter, an out-of-range integer, a tuple arity mismatch, or a bad step key. The detail is appended, and per-step problems are prefixed `step <i>:`. |
| `409 an execution for this wallet is already in progress. Wait for it to complete or fail before creating a new one` | a previous `dry: false` create is still live. Wait for it, or delete it.                                                                                           |
| `401 missing or invalid api token`                                                                                   | absent or rejected `x-api-key`.                                                                                                                                    |


# EVM steps - Aave withdraw

A worked, self-contained example of redeeming an **Aave V3** supply position on an EVM chain and bridging the proceeds out to another chain — burning the aTokens you received when you supplied, and getting the underlying token back plus accrued interest.

This is an **out-operation** (`outOperation: true`): the intermediary already holds the position, the steps run on the **origin** chain, and a final transfer pushes the result to the 1Click deposit address that bridges it out. There is no user deposit — nothing to fund, nothing to wait for on the origin side.

Withdrawing is the mirror of supplying:

1. **`withdraw`** — burn aTokens and return the underlying token (plus interest) to the intermediary.
2. **`transfer`** — a **producer** step that moves the proceeds to the 1Click deposit address, which bridges them to the destination chain.

The service then appends its own fee step. This doc covers the **amount recalculation** flow — two rounds against the create endpoint: a `dry: true` preview, then a `dry: false` create that carries the amount the preview computed.

### The two rounds

| Round | `dry`   | What it is for                                                                                                                                                            |
| ----- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1     | `true`  | preview: the gas fee, the amount that will actually be bridged, and the destination estimate. Creates nothing.                                                            |
| 2     | `false` | same body, with the producer transfer's amount replaced by round 1's `quote.amountIn`. Creates the execution, resolves `{DEPOSIT_ADDRESS}`, prepares the signing payload. |

Both rounds are `POST /api/v1/executions/{wallet}`. Signing and `POST …/submit` follow as on any execution — that call carries a signature, not an amount, so it is not part of the recalculation.

### Recomputing the amount between the rounds

The producer transfer cannot forward the whole withdrawal: the appended fee step has to be paid out of the same proceeds. So the amount in that step is `amount − networkFee`, and `networkFee` is not known until something has quoted. That is what round 1 is for.

```
round 1  → details.networkFee = 6952,  quote.amountIn = 190421
           (190421 = 197373 − 6952)

round 2  → producer step amount: 197373 → 190421
           withdraw step amount: 197373 (unchanged)
           quote.amount:         197373 (unchanged)
```

**Only the producer step changes.** The `withdraw` still pulls the full amount out of Aave, and `quote.amount` is still the full amount — that is the origin exposure the user signs for. The carve splits it: `190421` bridges out, `6952` pays the fee.

`quote.amountIn` from round 1 is the number to copy. It is already `amount − networkFee` — no arithmetic needed on your side.

> **The service also computes this itself.** It re-estimates the fee on round 2 and overwrites the producer step's amount with its own `amount − networkFee`, both in the `steps` it returns and in the calls the signature covers. So a figure that has moved since round 1 cannot break the batch, and sending the full amount in both steps works too — that is the single-round variant. Carve it anyway when the UI has to show the exact split before the user signs, and read the returned `steps` to see what the service settled on.

#### What not to carry forward

Four things go wrong if you treat the dry response as a template for round 2.

* **Do not echo the response's `steps` array.** It already carries the appended fee transfer. Send it back and the service appends a *second* one — nothing rejects it, and the batch then tries to pay the fee twice, overdrawing what the withdraw produced. Rebuild the steps from your own source, or drop the appended step (the one whose `metadata.name` is `Fee Transfer`).
* **Do not bake in the deposit address.** Every quote mints a fresh one. Keep `{DEPOSIT_ADDRESS}` literal in round 2 — the create asserts the steps contain a transfer to the address *it* just allocated.
* **Do not replace `{AMOUNT_IN}` with the number you saw.** On EXACT\_OUTPUT the service rewrites only the producer step, to the *fresh* `quote.amountIn`; a stale figure left in the `withdraw` step is not touched. The withdraw then pulls less than `producer + fee` and the batch reverts on the fee transfer.
* **Do not subtract the fee from `quote.amount`.** The service deducts it from the input itself before quoting. Pre-subtracting deducts it twice and quotes a smaller bridge than the user asked for. `quote.amount` is identical in both rounds.

#### Chain roles flip on an out-operation

This trips people up, so it is worth stating plainly:

| Field                    | On a bridge-in (supply)                    | On an out-operation (withdraw)                                        |
| ------------------------ | ------------------------------------------ | --------------------------------------------------------------------- |
| `quote.originAsset`      | the token the user sends from their wallet | the token the **steps produce** on the execution chain (USDC on Base) |
| `quote.destinationAsset` | the token the steps consume                | where the proceeds are bridged **to** (SOL)                           |
| Where steps run          | destination chain                          | **origin** chain                                                      |
| Fee denominated in       | the destination token                      | the **origin** token (`originAsset`)                                  |
| Initial status           | `CREATED`                                  | `OPERATION_PENDING`                                                   |
| User deposit             | required                                   | none                                                                  |

`originAsset` is the **underlying** token, not the aToken. The intermediary holds aBasUSDC, but the batch converts it to USDC before the producer transfer, so USDC is what the quote, the fee, and the deposit-address guard are all denominated in.

### Addresses (Aave V3 on Base, USDC)

| Account                 | Address                                                                                    |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| Aave V3 Pool (Base)     | `0xA238Dd80C259a72e81d7e4664a9801593F98d1c5`                                               |
| USDC (Base)             | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`                                               |
| aBasUSDC (the position) | `0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB`                                               |
| Fee collector           | per-deployment config — read it back from the appended fee step rather than hard-coding it |

**Only `base`, `eth` and `arb` are enabled as EVM execution chains.** Anything else is rejected with `400 blockchain <chain> is not supported as a destination`. The pools on the other two:

| Chain | Pool                                         |
| ----- | -------------------------------------------- |
| `eth` | `0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2` |
| `arb` | `0x794a61358D6845594F94dc1DB02A252b5b4814aD` |

Fetch the intermediary — the account that holds the position and executes the steps — before building anything:

```http
GET /api/v1/executions/{wallet}/intermediary
```

```json
{ "result": { "evm": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E" } }
```

Then read the position size straight off the aToken:

```
aBasUSDC.balanceOf(0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E)
```

That balance is the maximum you can withdraw. It grows with accrued interest, so read it immediately before you build the request.

### The steps

```jsonc
"steps": [
  {
    "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",          // Aave Pool
    "functionSignature": "withdraw(address,uint256,address)",
    "parameters": [
      "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",              // asset = USDC
      "197373",                                                   // amount to withdraw
      "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"               // to = intermediary
    ],
    "value": "0"
  },
  {
    "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",          // USDC
    "functionSignature": "transfer(address,uint256)",
    "parameters": [
      "{DEPOSIT_ADDRESS}",                                        // resolved server-side
      "197373"                                                    // rewritten to amount − fee
    ],
    "value": "0"
  }
]
```

Three things about this step pair:

* **`{DEPOSIT_ADDRESS}` is not optional.** You cannot know the deposit address before the service fetches the quote, and hard-coding one from an earlier response will not survive: the create path substitutes the *final* quote's address and then asserts the steps contain a `transfer` of `originAsset` to **that** address. A stale address fails the assertion with `outOperation requires a transfer(tokenAddress, {DEPOSIT_ADDRESS}, amount) step`.
* **The producer amount changes between the two rounds.** The pair above is the round-1 shape — the full `197373` in both steps. On round 2 the producer transfer carries `197373 − 6952 = 190421`, which is round 1's `quote.amountIn`, while the withdraw still pulls the whole amount and the appended fee step takes the remaining `6952`. The step is matched on its target token, signature and recipient — never on the amount — and the service redoes the subtraction itself, so a figure that has gone stale is corrected rather than rejected.
* **`withdraw`'s third parameter is the intermediary**, not the deposit address. The proceeds must land where the next step can spend them.

***

## Round 1 — preview (`dry: true`)

```jsonc
POST /api/v1/executions/BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "evm",
  "outOperation": true,
  "quote": {
    "originAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "amount": "197373",                         // USDC base units (6 decimals)
    "destinationAsset": "nep141:sol.omft.near",
    "slippageTolerance": 100,                   // basis points (1%)
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-30T12:00:00Z"
  },
  "steps": [
    {
      "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
      "functionSignature": "withdraw(address,uint256,address)",
      "parameters": [
        "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "197373",
        "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
      ],
      "value": "0"
    },
    {
      "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "functionSignature": "transfer(address,uint256)",
      "parameters": ["{DEPOSIT_ADDRESS}", "197373"],
      "value": "0"
    }
  ],
  "metadata": { "title": "Withdraw from Aave", "intent": "aave_withdraw" },
  "dry": true
}
```

```jsonc
// response
{
  "result": {
    "status": "OPERATION_PENDING",
    "details": {
      "intermediaryAddress": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
      "estimatedTime": "32",
      "networkFee": "6952"                      // 0.006952 USDC, taken in the origin token
    },
    "quote": {
      "amount": "197373",
      "amountIn": "190421",                     // 197373 − 6952: what actually gets bridged
      "amountOut": "2214445",                   // lamports
      "minAmountOut": "2192300"                 // worst-case SOL the user receives
    },
    "steps": [ /* your steps, plus the appended fee transfer */ ]
  }
}
```

What to show the user: **`details.networkFee`** (what the operation costs, in USDC) and **`quote.minAmountOut`** (the worst-case amount that reaches their wallet on the destination chain).

The dry round resolves the steps exactly like the create does — the substitution and the producer-amount rewrite are not gated on `dry` — so when 1Click allocates a deposit address for the dry quote you get back fully resolved, already-carved steps. Three caveats make the preview indicative rather than binding:

* **1Click need not allocate a deposit address for a dry quote,** and in practice does not. When there is no address there is nothing to splice in: the returned steps keep `{DEPOSIT_ADDRESS}` and the producer amount is left as you sent it.
* **Gas simulation can fall back.** A transfer to the zero address reverts, so if the address is missing at estimation time the fee comes from a fixed **500000** gas units rather than a measurement. The dry `networkFee` is then conservative, and it can be **absent** entirely if estimation fails another way — the create turns that same failure into a `500`.
* **The producer step is not asserted here.** The check that the steps contain a transfer to the deposit address lives behind the same address-is-present condition, so a body missing it returns `200` on the dry round and `400` on the create. The dry round validates step *encodability*, not out-op shape.

Either way the deposit address on the dry quote is not the one you will use. Read the authoritative fee and address from the `dry: false` response.

The `steps` in that response are the resolved-and-augmented array, fee step included — the same shape the create returns, not an echo of what you sent.

## Round 2 — create (`dry: false`)

The same body with `dry: false`, and with the producer transfer's amount replaced by round 1's `quote.amountIn` — `190421` where round 1 sent `197373`:

```jsonc
POST /api/v1/executions/BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "evm",
  "outOperation": true,
  "quote": {
    "originAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "amount": "197373",
    "destinationAsset": "nep141:sol.omft.near",
    "slippageTolerance": 100,
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-30T12:00:00Z"
  },
  "steps": [
    {
      "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
      "functionSignature": "withdraw(address,uint256,address)",
      "parameters": [
        "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "197373",                                     // unchanged: the full position
        "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
      ],
      "value": "0"
    },
    {
      "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "functionSignature": "transfer(address,uint256)",
      "parameters": ["{DEPOSIT_ADDRESS}", "190421"],  // ← was 197373: quote.amountIn
      "value": "0"
    }
  ],
  "metadata": { "title": "Withdraw from Aave", "intent": "aave_withdraw" },
  "dry": false
}
```

That one parameter is the only difference from round 1. `quote.amount` is still `197373`, the `withdraw` step still pulls the full `197373`, and `{DEPOSIT_ADDRESS}` is still a placeholder.

If the fee the service measures here differs from the one round 1 previewed — and it usually does, since a dry quote gets no deposit address and its gas estimate falls back — it rewrites the producer step to the new figure. Read the returned amount rather than assuming yours survived.

```jsonc
// 201 response
{
  "result": {
    "id": "eb4c80e8-e491-42da-87d9-f5879d91b7f6",
    "createdAt": "2026-07-30T11:53:45Z",
    "status": "OPERATION_PENDING",
    "details": {
      "intermediaryAddress": "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E",
      "estimatedTime": "32",
      "networkFee": "6952",
      "messageSigned": false,
      "messageToSign": "…",
      "signingStandard": "raw_ed25519",
      "payload": {
        "standard": "raw_ed25519",
        "payload_json": "…",
        "payload_bytes_base64": "…"
      }
    },
    "quote": {
      "amount": "197373",
      "amountIn": "190421",
      "amountOut": "2214445",
      "minAmountOut": "2192300",
      "depositAddress": "0x52dF3dE8e121332635ef319e4af9cCb98fd74a6f",
      "recipient": "BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4"
    },
    "steps": [
      {
        "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
        "functionSignature": "withdraw(address,uint256,address)",
        "parameters": [
          "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "197373",                                        // unchanged: full amount out of Aave
          "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
        ],
        "value": "0"
      },
      {
        "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "functionSignature": "transfer(address,uint256)",
        "parameters": [
          "0x52dF3dE8e121332635ef319e4af9cCb98fd74a6f",     // {DEPOSIT_ADDRESS} resolved
          "190421"                                          // 197373 − 6952: matches what you sent
        ],
        "value": "0"
      },
      {
        "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "functionSignature": "transfer(address,uint256)",
        "parameters": ["0x546252c9a0E974f75892b4c54b7a67B69a0aFf45", "6952"],
        "value": "0",
        "metadata": { "name": "Fee Transfer", "description": "Gas fee reimbursement" }
      }
    ],
    "type": "evm",
    "version": "1.0"
  }
}
```

The returned `steps` array is the exact batch the signature covers. Read it back and check the arithmetic — and that the producer amount is still the one you computed from round 1:

```
producer (190421) + fee (6952) = 197373 = what the withdraw pulled out of Aave
```

The user signs the full `197373` of origin exposure; the split between the bridge and the fee is fixed inside the signed batch, so the fee cannot be changed afterwards.

> A `dry: false` create takes an **in-flight lock** for this intermediary. The `dry: true` round does not — it writes no row and takes no lock, so the preview can be re-run freely and never blocks the create that follows it. A second create before the first reaches a terminal state returns `409` `an execution for this wallet is already in progress. Wait for it to complete or fail before creating a new one`. To abandon an attempt, delete it with `DELETE /api/v1/executions/{wallet}/{executionId}` — not a bare DELETE, it needs a body carrying the wallet's signature over the literal string `delete_execution:{executionId}`.

### Sign and submit

`result.details.payload` is signed with the **user's wallet**, using `result.details.signingStandard`. On an out-operation that wallet does not need to be on the execution chain at all — it only signs; the intermediary holds the funds and the service sponsors the gas.

```http
POST /api/v1/executions/{wallet}/submit
```

```json
{
  "executionId": "eb4c80e8-e491-42da-87d9-f5879d91b7f6",
  "publicKey": "ed25519:BTKcXNp1wSzs9Mp2ejsPrHLr59z5UkEDJgqcWyXGhGc4",
  "signature": "ed25519:466cjq2diW62zHhHik8hQQGLjLTg2drnZCEjBxDnUDZCoN4HmcXv4F4WTe2LqDgJk8Ccaq1rjusA47DeUWKegNy1"
}
```

The body is `{ signature, executionId }` for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, Stellar) adds `publicKey`. A TON origin adds both `publicKey` and a `tonConnect` envelope. No `x-api-key` here.

```json
{ "result": { "status": "SIGNING" } }
```

`SIGNING` — rather than the bridge-in flow's `SIGNED_PENDING_DEPOSIT` — is how you know the out-operation went straight to execution: the execution was already `OPERATION_PENDING`, so submitting the signature moves it to `OPERATION_PROCESSING` immediately. There is no deposit phase and no `deposit/submit` call.

### Monitor

```http
GET /api/v1/executions/{wallet}?id={executionId}
```

```
OPERATION_PENDING → OPERATION_PROCESSING → SUCCESS
```

`result` is an **array**, so read `result[0]` even when filtering by a single id.

`OPERATION_FAILED` is terminal. `SUCCESS` means the batch executed on the origin chain **and** the bridge settled — the batch landing on-chain only moves the execution as far as `OPERATION_PROCESSING`, so a long stay there is normal while the bridge leg completes. The API does not currently expose the sponsored batch's transaction hash.

### EXACT\_INPUT vs EXACT\_OUTPUT

|                                | **EXACT\_INPUT**                         | **EXACT\_OUTPUT**                                   |
| ------------------------------ | ---------------------------------------- | --------------------------------------------------- |
| `quote.amount` denominated in  | the **origin** token withdrawn from Aave | the **destination** token the user wants to receive |
| What's fixed                   | how much leaves the position             | how much arrives (`≈ Y`)                            |
| Amount in your steps           | concrete numbers you chose               | the `{AMOUNT_IN}` placeholder                       |
| Producer amount on round 2     | round 1's `quote.amountIn`               | still `{AMOUNT_IN}`                                 |
| Producer transfer, server-side | rewritten to `amount − fee`              | rewritten to `quote.amountIn`                       |
| The user signs                 | `quote.amount`                           | `quote.amountIn + networkFee`                       |

* **EXACT\_INPUT** — "Take 0.197373 USDC out of Aave and bridge whatever survives fees." You know the origin amount up front, so no placeholder is needed, and the producer amount is the one you recompute between the rounds.
* **EXACT\_OUTPUT** — "I want 0.0022 SOL. Take whatever that costs out of Aave." You cannot know the origin amount until the service has quoted, so put **`{AMOUNT_IN}`** wherever the amount appears. The service substitutes `quote.amountIn + networkFee` (the full origin commitment) into every occurrence, then rewrites the producer transfer down to `quote.amountIn`:

```jsonc
"quote": {
  "originAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
  "amount": "2200000",                          // target SOL, in lamports
  "destinationAsset": "nep141:sol.omft.near",
  "swapType": "EXACT_OUTPUT",
  "slippageTolerance": 100
},
"steps": [
  {
    "to": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
    "functionSignature": "withdraw(address,uint256,address)",
    "parameters": [
      "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "{AMOUNT_IN}",                            // → amountIn + fee, e.g. 202952
      "0xFe6EF968D2F7B2e9CCCF92150d96c930C3CC4a4E"
    ],
    "value": "0"
  },
  {
    "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "functionSignature": "transfer(address,uint256)",
    "parameters": ["{DEPOSIT_ADDRESS}", "{AMOUNT_IN}"],   // → rewritten to amountIn, e.g. 196000
    "value": "0"
  }
]
```

`{AMOUNT_IN}` is **out-operation + EXACT\_OUTPUT only**. Anywhere else it is a `400`: `{AMOUNT_IN} placeholder requires outOperation=true and quote.swapType=EXACT_OUTPUT`. Because `quote.amountIn` is the slippage-baked upper bound, 1Click refunds unused slippage to the intermediary (`refundTo` on an out-operation), so the position may end up slightly larger than the arithmetic suggests.

### Where the proceeds land

By default the bridged funds go to the **user's own wallet** on the destination chain. To send them somewhere else, set `quote.recipient`:

```jsonc
"quote": {
  "originAsset": "…",
  "destinationAsset": "nep141:sol.omft.near",
  "amount": "197373",
  "recipient": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
}
```

`quote.recipient` is **out-operation only** — on a bridge-in it is a `400`. Refunds always go back to the intermediary, never to the recipient.

### Rules

* **`x-api-key` is required** on `POST /api/v1/executions/{wallet}`, dry or not. `POST …/submit` does not take it.
* **`outOperation: true` must be set**, and `type` must be `evm` (the default) — with an out-operation the `type` is checked against `originAsset`'s chain, not `destinationAsset`'s.
* **Exactly one producer step is honoured.** The service rewrites the **first** step matching "transfer of `originAsset` to the deposit address" and stops. Any further transfer to that address keeps the amount you sent, un-carved — so the batch would try to move more than the withdraw produced. Send one.
* **`{MIN_AMOUNT_OUT}` is rejected** on an out-operation (`{MIN_AMOUNT_OUT} placeholder is not supported with outOperation=true`) — it substitutes a *destination* bridge output, and out-op steps run before the bridge.
* **Do not add the fee step yourself.** The service appends it — on a `dry: true` round too, so the `steps` array it hands back is not a valid request body.
* **Step objects accept only `to`, `functionSignature`, `parameters`, `value` and `metadata`.** Anything else is a `400`; free-form data goes in `metadata`.

### Errors you will actually hit

| Response                                                                                                             | Cause                                                                                                               |
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `400 outOperation requires a transfer(tokenAddress, {DEPOSIT_ADDRESS}, amount) step`                                 | no producer step, the transfer targets the wrong token, or you hard-coded a stale deposit address.                  |
| `400 estimated gas fee exceeds input amount`                                                                         | EXACT\_INPUT only: the fee is ≥ `quote.amount`, so withdrawing this little leaves nothing to bridge. Withdraw more. |
| `400 {AMOUNT_IN} placeholder requires outOperation=true and quote.swapType=EXACT_OUTPUT`                             | `{AMOUNT_IN}` on an EXACT\_INPUT or bridge-in request.                                                              |
| `400 {MIN_AMOUNT_OUT} placeholder is not supported with outOperation=true`                                           | wrong placeholder for this direction.                                                                               |
| `400 quote.recipient is only valid when outOperation=true`                                                           | `recipient` sent on a bridge-in.                                                                                    |
| `400 steps are required for non-dry executions`                                                                      | `dry: false` with an empty `steps` array.                                                                           |
| `409 an execution for this wallet is already in progress. Wait for it to complete or fail before creating a new one` | a previous `dry: false` create is still live.                                                                       |
| `401 missing or invalid api token`                                                                                   | absent or rejected `x-api-key`.                                                                                     |


# Asynchronous operations

Some on-chain operations don't settle in the transaction that triggers them. You submit a request now, and the funds it produces arrive at the intermediary account *later* — minutes, hours, or even days afterwards. There is no way to say "do the request, wait for settlement, then bridge the result out" inside a single execution, so model it as **two separate executions**:

1. Trigger the async operation with `POST /api/v1/executions/{wallet}/steps`.
2. Once the settled funds (a 1Click-supported token) land at the intermediary, move them to another network with `POST /api/v1/executions/{wallet}`.

### TL;DR

1. **Trigger** — call `POST /api/v1/executions/{wallet}/steps` with the steps that submit the request (e.g. an `approve` + a request-redeem). No `x-api-key` is needed for this endpoint.
2. **Wait** — the operation settles off the critical path. When it does, the payout token (e.g. USDC — anything 1Click supports) arrives at the **intermediary account** on the destination chain.
3. **Move** — call `POST /api/v1/executions/{wallet}` with a quote and `outOperation: true` to bridge the settled funds to another network. This endpoint **requires** an `x-api-key` header.

### Why two executions

Request-style flows (request-then-redeem, queued withdrawals, epoch-based unstaking, …) only *enqueue* the operation in the triggering transaction. The payout is produced asynchronously, so it cannot be bridged in the same transaction — at trigger time the funds don't exist yet.

The intermediary account is the constant across both executions: the trigger runs there, the settled funds arrive there, and the second execution spends from there. So you split the work along the settlement boundary — one execution to start the operation, a second one to move the result once it's real.

### The intermediary account

The intermediary is a deterministic EVM address derived from the wallet. It is where steps execute and where bridged or settled funds land on the destination chain. Fetch it before building steps:

```http
GET /api/v1/executions/{wallet}/intermediary
```

```json
{ "result": { "evm": "0x3eb032bca9a6ceeb8ce69fdf4ec79187fdddd25e" } }
```

Two things to keep in mind:

1. **It must already hold the destination token to pay gas on step 1.** The `/steps` execution collects its network fee in the destination token, so the intermediary needs a balance there *before* you trigger
2. **The settled funds arrive here, not at the user's wallet.** That is exactly why step 2 exists: to forward them on.

### Step 1 — trigger the async operation (`/steps`)

`POST /api/v1/executions/{wallet}/steps` runs your steps against the funds the intermediary already holds — no bridge, no quote. Submit the steps that enqueue the operation:

```json
{
  "version": "1.0",
  "type": "evm",
  "destinationAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
  "steps": [
    {
      "to": "0x1111111111111111111111111111111111111111",
      "value": "0",
      "functionSignature": "approve(address,uint256)",
      "parameters": ["0x2222222222222222222222222222222222222222", "1000000000000000000"]
    },
    {
      "to": "0x2222222222222222222222222222222222222222",
      "value": "0",
      "functionSignature": "requestRedeem(address,uint256)",
      "parameters": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "1000000000000000000"]
    }
  ],
  "metadata": { "title": "Request redeem", "intent": "async_redeem" },
  "dry": false
}
```

Notes:

* `destinationAsset` is the token the operation eventually pays out (here, USDC on Base). It is **required**.
* Neither step above calls the USDC contract — they only touch the vault. The `/steps` endpoint rejects step sets that never call the destination token, so you'll need to append a zero-value transfer to satisfy that guard
* This endpoint does **not** require `x-api-key`.

The execution returns a payload to sign and submit (see Signing & status). Once submitted, the request is enqueued on-chain and you wait for settlement.

### Step 2 — move settled funds to another network

When the operation settles and the payout token is at the intermediary, bridge it out with the quote-backed endpoint:

```http
POST /api/v1/executions/{wallet}
x-api-key: <your-api-key>
Content-Type: application/json
```

```json
{
  "version": "1.0",
  "type": "evm",
  "quote": {
    "originAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
    "destinationAsset": "<target-network-asset>",
    "amount": "1000000",
    "swapType": "EXACT_INPUT"
  },
  "outOperation": true,
  "metadata": { "title": "Withdraw to target network", "intent": "async_withdraw" },
  "dry": false
}
```

* `originAsset` is the settled token now held by the intermediary; `destinationAsset` is where the user wants it.
* `outOperation: true` executes on the origin chain and bridges the output to the destination via 1Click.
* This endpoint **requires** `x-api-key` — it fetches a 1Click quote and prices per-key fees server-side.


# Steps destination token requirement

`POST /api/v1/executions/{wallet}/steps` requires that **at least one step calls the `destinationAsset` token contract**. If none of your steps touch it, the request is rejected. This guide explains the rule, why it exists, and the zero-value-transfer workaround for steps that legitimately never touch the payout token.

### The rule

For an ERC-20 `destinationAsset`, the `to` field of at least one step must equal the destination token's contract address. Otherwise the request fails:

```json
{ "error": "steps must include at least one call to destination token 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" }
```

**Native** destination assets (the gas token of the chain) are exempt — the fee is taken in native value, so no token call is required.

### Why

The network/gas fee for a steps execution is collected **in the destination token**. The backend appends a fee-transfer step that moves the gas fee from the intermediary's destination-token balance to the fee collector. The guard ensures the step set actually engages that token. Two consequences follow:

1. **At least one step must call the destination token** — otherwise the request is rejected before it runs.
2. **The intermediary must hold enough destination token to cover the gas fee** — the appended fee-transfer step spends from its balance. A step set that passes the guard but leaves the intermediary with no destination-token balance will fail at execution time.

### Workaround — append a zero-value transfer

When your real steps don't touch the payout token (for example, an async request that only calls a vault), satisfy the guard by adding a final step that transfers `0` of the destination token to the intermediary account:

```json
{
  "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "value": "0",
  "functionSignature": "transfer(address,uint256)",
  "parameters": ["<intermediaryAddress>", "0"]
}
```

* `to` is the destination token contract (here, USDC on Base).
* The recipient is the intermediary account — fetch it from `GET /api/v1/executions/{wallet}/intermediary` → `{ "evm": "0x…" }`.
* The amount is `0`, so this moves no funds; it exists only to reference the destination token and clear the guard.

### Full example

A complete `/steps` body whose real work (an `approve` + a request call) never touches the payout token, with the zero-value transfer appended last:

```json
{
  "version": "1.0",
  "type": "evm",
  "destinationAsset": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
  "steps": [
    {
      "to": "0x1111111111111111111111111111111111111111",
      "value": "0",
      "functionSignature": "approve(address,uint256)",
      "parameters": ["0x2222222222222222222222222222222222222222", "1000000000000000000"]
    },
    {
      "to": "0x2222222222222222222222222222222222222222",
      "value": "0",
      "functionSignature": "requestRedeem(address,uint256)",
      "parameters": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "1000000000000000000"]
    },
    {
      "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "value": "0",
      "functionSignature": "transfer(address,uint256)",
      "parameters": ["0x3eb032bca9a6ceeb8ce69fdf4ec79187fdddd25e", "0"]
    }
  ],
  "metadata": { "title": "Request redeem", "intent": "async_redeem" },
  "dry": false
}
```

The first two steps do the actual work; the third (a `0`-amount transfer of USDC to the intermediary) only exists to satisfy the destination-token guard.

### Prerequisite

The zero-value transfer clears the guard, but it does **not** fund the gas fee. The intermediary must separately hold a balance of the destination token large enough to cover the appended fee-transfer step. This is especially relevant for asynchronous flows, where the payout token only arrives after settlement — the intermediary needs a pre-existing balance to pay gas at trigger time


# Solana


# Getting your Solana intermediary address

A Solana action does not run from your own wallet. It runs from a dedicated **intermediary account** that the service controls on your behalf. On Solana this is an ed25519 account **deterministically derived from your origin wallet** — it is stable for a given origin wallet, but you cannot compute it yourself, so you fetch it from the API.

This document covers how to obtain that address from an origin wallet (an EVM `0x…` address is the worked example), what the response looks like for every origin type, and how to derive the intermediary's token accounts from it.

### From an EVM address to a Solana address

Call the intermediary endpoint with your origin wallet in the path:

```
GET /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5/intermediary
```

The response returns **both** intermediaries — the EVM one and the Solana one — regardless of which origin you used:

```jsonc
{
  "result": {
    "originAccount": "0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5",  // the wallet you passed
    "originType":    "evm",                                          // detected from the path
    "evm":           "0x9c8B7a6F5e4D3c2B1a09F8e7D6c5B4a3928170615",  // your EVM intermediary
    "solana":        "2fjhr2fzcoHYvdKkYpxBsUnE5QDg2hhb2mnxfwrL7RTY"   // your SOLANA intermediary
  }
}
```

Read `result.solana` — that base58 string is **your** Solana intermediary account. It is the account that will hold your tokens on Solana and act as the authority/signer for your steps.

Field by field:

| Field           | Meaning                                                                                               |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| `originAccount` | the wallet address you passed in the path, echoed back                                                |
| `originType`    | the detected origin type — `evm`, `solana`, `near`, `stellar`, `tron`, or `ton`                       |
| `evm`           | your EVM intermediary (secp256k1 account)                                                             |
| `solana`        | your Solana intermediary (ed25519 account), base58 — or `null` if Solana destinations are not enabled |

Notes:

* The address is **deterministic and stable**: the same origin wallet always maps to the same Solana intermediary.
* `solana` is `null` when Solana destinations are disabled for the deployment (or if its derivation is temporarily unavailable). The `evm` intermediary is still returned in that case.

### Other origin types

The same endpoint accepts any supported origin wallet in the path — an EVM `0x…` address, a Solana base58 public key, a NEAR account (named or implicit), a Stellar `G…` address, a Tron `T…` address, or a TON user-friendly address. `originType` in the response tells you which was detected (a Solana origin is reported as `"solana"`).

#### TON origins need a public key

A TON address does not contain its public key, so for a TON origin you must pass the wallet's ed25519 public key as a query parameter, and it must be the key that owns the address:

```
GET /api/v1/executions/{tonWallet}/intermediary?publicKey=ed25519:7XSf…
```

Missing key → `400 {"error": "publicKey query parameter is required for TON wallets"}`. A malformed key (bad prefix, bad base58, or wrong length) → `400 {"error": "publicKey must be a valid ed25519:<base58> key"}`. A well-formed key that does not own the wallet → `400 {"error": "public key does not match wallet"}`. Use the **same** key you use when creating executions — a different key derives a different intermediary.

### Deriving the intermediary's token accounts

Token balances are held in **associated token accounts (ATAs)** owned by the intermediary. Your steps reference these ATAs by their concrete base58 address — there is no placeholder for an ATA, because the ATA address depends on the resolved intermediary. So:

1. Fetch the resolved Solana intermediary address (above).
2. Derive its associated token account for the relevant mint using the standard associated-token-account derivation — a program-derived address of `[ owner, tokenProgram, mint ]` under the associated-token-account program, where:
   * `owner` = the resolved Solana intermediary
   * `tokenProgram` = `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`
   * `mint` = the token mint
   * associated-token-account program = `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`
3. Put that concrete base58 ATA address into the step's `accounts`.

The intermediary itself still goes into steps as the `{INTERMEDIARY}` placeholder (as owner / authority / signer). Only the **derived ATA** goes in as a resolved address.

#### When the ATA does not exist yet

If an intermediary-owned ATA your steps reference does not exist yet, the service creates it for you inside the same signed transaction — you do not add a create step, and you are billed its one-time rent only if it was actually missing. (On a bridge-in, the destination token's ATA is provisioned by the bridge as it settles your deposit.)

If a step pays out to **someone else's** token account that might not exist, you must include the account-create yourself. Name `{INTERMEDIARY}` as its funder (the only signer you may name) and the service reassigns the funder to the fee payer at signing time.

### Using the address

In almost all cases you do **not** paste the resolved intermediary into your steps. Instead you use the placeholder `{INTERMEDIARY}` wherever the intermediary must appear, and the service substitutes the real address before it encodes and signs the transaction. Fetch the resolved address only when you need to **derive another account from it** — most commonly one of its token accounts, as above.


# Kamino stake scenario

A worked, self-contained example of supplying USDC to a **Kamino Lending** reserve to earn yield. It covers **both** execution modes: **steps-only** (your Solana intermediary already holds the USDC) and **quote-with-steps** (bridge USDC in from another chain and supply it in one signed request).

Supplying is two instructions in one execution:

1. **`refresh_reserve`** — refresh the reserve's price so the deposit values your USDC correctly.
2. **`deposit_reserve_liquidity`** — move your USDC into the reserve and mint back **cUSDC** (the reserve's collateral receipt token) to your intermediary.

No borrowing and no obligation account are involved — this is the plain earn flow. You simply hold cUSDC afterwards and redeem it later to get your USDC back plus accrued interest (see Kamino withdraw).

### The two execution modes

The instructions above are identical in both modes. What differs is the endpoint, whether a `quote` and an API key are involved, and — crucially — **how the deposit amount is chosen**.

|                    | **steps-only**                                       | **quote-with-steps** (bridge-in)                                                   |
| ------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Endpoint           | `POST /api/v1/executions/{wallet}/steps`             | `POST /api/v1/executions/{wallet}`                                                 |
| `execution_mode`   | `steps_only`                                         | `quote_with_steps`                                                                 |
| Carries a `quote`  | no                                                   | yes                                                                                |
| `x-api-key` header | **not** required                                     | **required**                                                                       |
| Precondition       | intermediary already holds USDC                      | USDC is bridged in from the origin chain first                                     |
| Deposit amount     | a concrete `u64` you compute (see fee carving below) | the `{MIN_AMOUNT_OUT}` placeholder — the service fills the post-fee bridged amount |
| Swap type          | n/a (no quote)                                       | `EXACT_INPUT` or `EXACT_OUTPUT`                                                    |

Everything else — the request envelope, signing with your origin wallet, and the submit call — is the same as for any destination. For the conceptual overview see Using a Solana destination.

### Addresses (Kamino Main Market USDC reserve, Solana mainnet)

These are specific to the Kamino **Main Market USDC reserve**. A different token or market has a different account set.

| Account                               | Address                                        |
| ------------------------------------- | ---------------------------------------------- |
| Kamino Lending program                | `KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD`  |
| Lending market (Main Market)          | `7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF` |
| Lending market authority              | `9DrvZvyWh1HuAoZxvYWMvkf2XCzryCpGgHqrMjyDWpmo` |
| USDC reserve                          | `D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59` |
| USDC mint (reserve liquidity mint)    | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
| Reserve liquidity supply (USDC vault) | `Bgq7trRgVMeq33yt235zM2onQ4bRDBsY5EWiTetF4qw6` |
| Reserve collateral mint (cUSDC)       | `B8V6WVjPxW1UGwVDfxH2d2r8SyT4cqn7dQRK6XneVa7D` |
| Reserve price oracle (Scope)          | `3t4JZcueEzTbVP6kLxXrL3VpWx45jDer4eqysweBchNH` |
| SPL Token program                     | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`  |
| Instructions sysvar                   | `Sysvar1nstructions1111111111111111111111111`  |

The two addresses you supply yourself are the intermediary's own token accounts:

* **`<intermediary USDC ATA>`** — the intermediary's associated token account for the USDC mint (the source of the USDC being deposited).
* **`<intermediary cUSDC ATA>`** — the intermediary's associated token account for the cUSDC mint (where the minted cUSDC lands). This usually does not exist before your first deposit. The service creates it for you inside the signed transaction and bills its one-time rent only if it was actually missing.

Derive each ATA from your resolved Solana intermediary and the relevant mint — a program-derived address of `[ intermediary, TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA, mint ]` under the associated-token-account program `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`. Fetch the resolved intermediary from `GET /api/v1/executions/{wallet}/intermediary` → `result.solana`.

***

## Mode 1 — steps-only (intermediary already holds USDC)

### The steps

```jsonc
"steps": [
  {
    "metadata": { "name": "Refresh reserve", "description": "Refresh the Kamino USDC reserve price" },
    "programId": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD",
    "discriminator": "02da8aeb4fc91966",
    "args": [],
    "accounts": [
      { "pubkey": "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59", "isSigner": false, "isWritable": true  },
      { "pubkey": "7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF", "isSigner": false, "isWritable": false },
      { "pubkey": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", "isSigner": false, "isWritable": false },
      { "pubkey": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", "isSigner": false, "isWritable": false },
      { "pubkey": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", "isSigner": false, "isWritable": false },
      { "pubkey": "3t4JZcueEzTbVP6kLxXrL3VpWx45jDer4eqysweBchNH", "isSigner": false, "isWritable": false }
    ]
  },
  {
    "metadata": { "name": "Deposit into Kamino", "description": "Supply USDC, receive cUSDC" },
    "programId": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD",
    "discriminator": "a9c91e7e06cd6644",
    "args": [
      { "name": "liquidity_amount", "type": "u64", "value": "25000000" }   // 25 USDC (6 decimals)
    ],
    "accounts": [
      { "pubkey": "{INTERMEDIARY}",                                 "isSigner": true,  "isWritable": false },
      { "pubkey": "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59",   "isSigner": false, "isWritable": true  },
      { "pubkey": "7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF",   "isSigner": false, "isWritable": false },
      { "pubkey": "9DrvZvyWh1HuAoZxvYWMvkf2XCzryCpGgHqrMjyDWpmo",   "isSigner": false, "isWritable": false },
      { "pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",   "isSigner": false, "isWritable": false },
      { "pubkey": "Bgq7trRgVMeq33yt235zM2onQ4bRDBsY5EWiTetF4qw6",   "isSigner": false, "isWritable": true  },
      { "pubkey": "B8V6WVjPxW1UGwVDfxH2d2r8SyT4cqn7dQRK6XneVa7D",   "isSigner": false, "isWritable": true  },
      { "pubkey": "<intermediary USDC ATA>",                        "isSigner": false, "isWritable": true  },
      { "pubkey": "<intermediary cUSDC ATA>",                       "isSigner": false, "isWritable": true  },
      { "pubkey": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",   "isSigner": false, "isWritable": false },
      { "pubkey": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",   "isSigner": false, "isWritable": false },
      { "pubkey": "Sysvar1nstructions1111111111111111111111111",   "isSigner": false, "isWritable": false }
    ]
  }
]
```

### 1a. The single-request path (`dry: false`)

If you already know a concrete amount that leaves **enough USDC in the intermediary to cover the network fee** (see the fee note below), you don't need a preview — send **one** create request with `dry: false`:

```jsonc
POST /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5/steps
{
  "version": "1.0",
  "type": "solana",
  "destinationAsset": "<destination asset id for USDC on Solana>",
  "steps": [ /* the two steps above, with liquidity_amount = 25000000 */ ],
  "dry": false
}
```

Then sign and submit (below). That is **one** create request (plus the universal `submit`). Use this when your deposit amount is a fixed figure and the intermediary holds comfortably more USDC than the fee.

### 1b. The three-request path (adjust the amount by the fee)

**Why you often can't send a fixed amount.** After your steps, the service appends its **own** fee instruction that debits the intermediary **in the destination token (USDC)** — SPL destinations settle via a gasless relayer (Kora) that pays the SOL gas and takes its fee in the destination token (the mint must be on the deployment's `kora_fee_token_mints` allow-list, or create returns `400`). So if you deposit your *entire* USDC balance, nothing is left to pay the fee and the transaction fails. To supply "everything minus the fee" — or any amount close to your balance — you must first learn the fee, then reduce the deposit by it. This is exactly what the reference frontend does, in **three requests**:

1. **Estimate** — a `dry: true` create. The response returns `result.details.networkFee`, the fee in USDC base units.

   ```jsonc
   POST /api/v1/executions/{wallet}/steps
   { "version": "1.0", "type": "solana", "destinationAsset": "…",
     "steps": [ /* liquidity_amount = your target amount, e.g. 25000000 */ ],
     "dry": true }
   ```

   ```jsonc
   // response
   { "result": { "details": { "networkFee": "40000" /* 0.04 USDC */ } } }
   ```
2. **Adjust the amount in your steps.** Set the deposit step's `liquidity_amount` to `amount − networkFee` — the carved value the intermediary can actually supply while retaining the fee:

   ```
   liquidity_amount = 25000000 − 40000 = 24960000
   ```
3. **Execute** — the same request with the carved amount and `dry: false`:

   ```jsonc
   POST /api/v1/executions/{wallet}/steps
   { "version": "1.0", "type": "solana", "destinationAsset": "…",
     "steps": [ /* liquidity_amount = 24960000 */ ],
     "dry": false }
   ```

The three requests are therefore **`dry:true` estimate → `dry:false` execute → `submit`** (step 4, below). The service injects the fee instruction into the signed transaction. You never add a fee step yourself — you only size your deposit so the intermediary keeps the fee behind.

> A `dry: true` preview echoes your submitted steps back unchanged. A real (`dry: false`) create instead returns the steps the service actually signs — with `{INTERMEDIARY}` resolved, the prepended cUSDC ATA create (when it was missing), and the appended fee instruction rendered generically (full instruction data as the `discriminator`, empty `args`).

***

## Mode 2 — quote-with-steps (bridge USDC in, then stake)

To bridge USDC in from another chain **and** supply it in a single signed request, use the quote-backed create endpoint. This is a **bridge-in** execution (`execution_mode = quote_with_steps`).

Two things change versus steps-only:

* The request carries a **`quote`** (origin asset, amount, destination asset, slippage, swap type, deadline) and **requires an `x-api-key` header**.
* The deposit step's `liquidity_amount` is the **`{MIN_AMOUNT_OUT}`** placeholder instead of a number. The service resolves it to the full **post-fee** bridged amount server-side — you never carve the fee yourself and you cannot know the exact figure up front. (`{MIN_AMOUNT_OUT}` is bridge-in only. It is rejected in steps-only.)

```jsonc
POST /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "solana",
  "quote": {
    "originAsset": "<1click origin asset id, e.g. USDC on Base>",
    "amount": "25000000",
    "destinationAsset": "<1click asset id for USDC on Solana>",
    "slippageTolerance": 100,          // basis points (1%)
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-17T12:00:00Z"
  },
  "steps": [
    { /* refresh_reserve — identical to Mode 1 */ },
    {
      "programId": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD",
      "discriminator": "a9c91e7e06cd6644",
      "args": [
        { "name": "liquidity_amount", "type": "u64", "value": "{MIN_AMOUNT_OUT}" }
      ],
      "accounts": [ /* identical to Mode 1 */ ]
    }
  ],
  "metadata": { "title": "Stake to Kamino", "intent": "kamino_stake" },
  "dry": false
}
```

This is a **single** `dry: false` create request. (A `dry: true` preflight is optional. It returns a best-effort `result.details.networkFee` plus a fee-adjusted amount preview, but for an SPL destination that fee is only advisory — the authoritative fee is computed and carved from `{MIN_AMOUNT_OUT}` at execution time, so the dry round does not fix your exact deposited amount.)

#### EXACT\_INPUT vs EXACT\_OUTPUT

The **steps are identical** across swap types — the only differences are the `quote.swapType`/`quote.amount` fields and how much you deposit to the returned deposit address. `{AMOUNT_IN}` is **out-op only** and is rejected on a bridge-in.

|                               | **EXACT\_INPUT**                                                                          | **EXACT\_OUTPUT**                                                                                            |
| ----------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `quote.amount` denominated in | the **origin** token (what you send)                                                      | the **destination** USDC (what you want staked)                                                              |
| What's fixed                  | the input you bridge                                                                      | the USDC that ends up supplied (`≈ Y`)                                                                       |
| Fee handling                  | the service carves the network fee out of `{MIN_AMOUNT_OUT}`, so you stake `input − fees` | the service **grosses the 1Click quote up** by the network fee first, so after the carve you still net `≈ Y` |
| How much you deposit          | the amount you set (per-chain atomic units)                                               | `result.quote.amountIn` — the origin commitment the service computes (already atomic, origin-token units)    |

* **EXACT\_INPUT** — "I'm sending 25 USDC from Base. Stake whatever arrives after fees." `quote.amount` is the origin amount. You deposit exactly that.
* **EXACT\_OUTPUT** — "I want 25 USDC supplied to Kamino. Charge me whatever that costs." `quote.amount` is the destination target `Y`. Read `result.quote.amountIn` from the create response and deposit **that** to the deposit address.

#### Bridge-in lifecycle (what follows the create)

1. **Create** (above) returns `result.quote.depositAddress` (and `depositMemo` for memo-mode origins), plus a signing `payload` in `result.details` for the action intent.
2. **Sign the intent** with your origin wallet and **submit** it (the service can pre-sign the Kamino transaction so it fires the moment the deposit confirms).
3. **Deposit** to `depositAddress` — the origin amount for EXACT\_INPUT, or `result.quote.amountIn` for EXACT\_OUTPUT — and record it with `POST /api/v1/executions/deposit/submit`.
4. The bridge settles to your Solana intermediary and the pre-signed Kamino transaction executes.

> **Bridge-in risk.** A bridge-in skips the pre-sign simulation guard (the funds arrive \~150 s after signing), so an action that reverts on-chain after signing burns the durable nonce and strands the bridged USDC in the intermediary (recoverable later via a steps-only execution). Keep the action comfortably under the 200000 compute-unit budget — this two-instruction deposit is well within it.

***

### Notes on the accounts

* **The three repeated `KLend…` entries in `refresh_reserve`** are the unused Pyth / Switchboard oracle slots. Kamino's convention is to pass the program's own id for an oracle the reserve does not use. The USDC reserve is priced by **Scope**, so only the last oracle slot (`3t4J…`) is a real account.
* **`deposit_reserve_liquidity` account order is fixed by the program.** The order shown — owner, reserve, market, market authority, liquidity mint, liquidity supply, collateral mint, source liquidity, destination collateral, then two token-program slots and the instructions sysvar — must be preserved. The two trailing token-program slots are the collateral and liquidity token programs (both the legacy SPL Token program for USDC).
* **`{INTERMEDIARY}` is the deposit owner and the only signer.**
* **`<intermediary USDC ATA>`** is the source liquidity account. **`<intermediary cUSDC ATA>`** is the destination collateral account.

### Discriminators

Both are Anchor 8-byte discriminators — the first 8 bytes of `sha256("global:" + instruction_name)`:

| Instruction                 | Discriminator      |
| --------------------------- | ------------------ |
| `refresh_reserve`           | `02da8aeb4fc91966` |
| `deposit_reserve_liquidity` | `a9c91e7e06cd6644` |

### Amount

`liquidity_amount` is in USDC base units (6 decimals): `25000000` = 25 USDC.

* **steps-only:** a concrete number. Either send it directly (Mode 1a) or carve it down to `amount − networkFee` after a dry estimate (Mode 1b).
* **quote-with-steps:** the `{MIN_AMOUNT_OUT}` placeholder — the service fills the post-fee bridged amount, which you cannot know up front.

### Rules that apply here

* **Only `{INTERMEDIARY}` may be a signer.**
* **Do not add compute-budget, nonce, or fee instructions** — the service injects those. A `dry: true` preview omits those injected instructions. A steps-only echo is otherwise your submitted steps verbatim, while a bridge-in echo additionally resolves `{MIN_AMOUNT_OUT}` to its post-fee value. A real create returns the exact transaction the service signs (resolved `{INTERMEDIARY}`, the prepended cUSDC ATA create when it was missing, and the appended fee instruction).
* **Stay within the limits.** The service injects a compute-unit budget (200000 by default, set by the deployment) covering all your steps together, and the assembled transaction must be ≤ **1232 bytes**. This two-instruction deposit fits comfortably under both.
* **A steps-only SPL action must touch its destination token** — the deposit references the intermediary's USDC ATA, so the requirement is satisfied.

### Sign and submit

The create response returns, under `result.details`, a `payload` to sign and a `signingStandard` — **your origin wallet's** standard. Sign `result.details.payload` with your origin wallet exactly as for any destination — you never handle the inner Solana message — then submit the signature:

```
POST /api/v1/executions/{wallet}/submit
```

The submit body is `{ signature, executionId }` for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, or Stellar) must also include `publicKey`. A TON origin includes both `publicKey` and a `tonConnect` envelope, so its body is `{ signature, executionId, publicKey, tonConnect }`.

After it settles, your intermediary holds cUSDC representing the supplied USDC plus accrued interest, which you can redeem later.


# Kamino withdraw scenario

A worked, self-contained example of withdrawing from a **Kamino Lending** reserve — burning the **cUSDC** you received when you supplied USDC, and getting your USDC back plus accrued interest. It covers **both** execution modes: **steps-only** (the redeemed USDC stays in your Solana intermediary) and **quote-with-steps** as an **out-operation** (redeem on Solana, then bridge the USDC out to another chain in one signed request).

Withdrawing is the mirror of supplying:

1. **`refresh_reserve`** — refresh the reserve's price so the redemption values your cUSDC correctly.
2. **`redeem_reserve_collateral`** — burn your cUSDC and return the underlying USDC (plus accrued interest) to your intermediary.

For an out-operation a third **producer transfer** step moves the redeemed USDC to the 1Click deposit address that bridges it out.

> **The account order in `redeem_reserve_collateral` differs from the deposit.** The lending market comes **before** the reserve, and the two token accounts swap roles (cUSDC is now the source, USDC the destination). Preserve the order shown.

### The two execution modes

The `refresh` + `redeem` pair is identical in both modes. What differs is the endpoint, whether a `quote`/API key is involved, and whether a producer transfer bridges the proceeds out.

|                          | **steps-only**                           | **quote-with-steps** (out-operation)                                             |
| ------------------------ | ---------------------------------------- | -------------------------------------------------------------------------------- |
| Endpoint                 | `POST /api/v1/executions/{wallet}/steps` | `POST /api/v1/executions/{wallet}` + `"outOperation": true`                      |
| `execution_mode`         | `steps_only`                             | `quote_with_steps`                                                               |
| Carries a `quote`        | no                                       | yes                                                                              |
| `x-api-key` header       | **not** required                         | **required**                                                                     |
| What happens to the USDC | stays in the intermediary's USDC account | a producer step transfers it to the 1Click deposit address, which bridges it out |
| Extra step               | none                                     | producer `transfer_checked` → `{DEPOSIT_ADDRESS}`                                |
| Swap type                | n/a (no quote)                           | `EXACT_INPUT` or `EXACT_OUTPUT`                                                  |

Everything else — the request envelope, signing with your origin wallet, and the submit call — is the same as for any destination. For the conceptual overview see Using a Solana destination.

### Addresses (Kamino Main Market USDC reserve, Solana mainnet)

These are the same accounts as the corresponding supply flow (Kamino stake). Only the instruction and account order change.

| Account                               | Address                                        |
| ------------------------------------- | ---------------------------------------------- |
| Kamino Lending program                | `KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD`  |
| Lending market (Main Market)          | `7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF` |
| Lending market authority              | `9DrvZvyWh1HuAoZxvYWMvkf2XCzryCpGgHqrMjyDWpmo` |
| USDC reserve                          | `D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59` |
| USDC mint (reserve liquidity mint)    | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
| Reserve liquidity supply (USDC vault) | `Bgq7trRgVMeq33yt235zM2onQ4bRDBsY5EWiTetF4qw6` |
| Reserve collateral mint (cUSDC)       | `B8V6WVjPxW1UGwVDfxH2d2r8SyT4cqn7dQRK6XneVa7D` |
| Reserve price oracle (Scope)          | `3t4JZcueEzTbVP6kLxXrL3VpWx45jDer4eqysweBchNH` |
| SPL Token program                     | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`  |
| Instructions sysvar                   | `Sysvar1nstructions1111111111111111111111111`  |

The two addresses you supply yourself are the intermediary's own token accounts:

* **`<intermediary cUSDC ATA>`** — the source, the cUSDC being burned.
* **`<intermediary USDC ATA>`** — the destination, where the redeemed USDC lands.

Derive each ATA from your resolved Solana intermediary and the relevant mint — a program-derived address of `[ intermediary, TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA, mint ]` under the associated-token-account program `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`. Fetch the resolved intermediary from `GET /api/v1/executions/{wallet}/intermediary` → `result.solana`.

***

## Mode 1 — steps-only (redeem into the intermediary)

### The steps

```jsonc
"steps": [
  {
    "metadata": { "name": "Refresh reserve", "description": "Refresh the Kamino USDC reserve price" },
    "programId": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD",
    "discriminator": "02da8aeb4fc91966",
    "args": [],
    "accounts": [
      { "pubkey": "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59", "isSigner": false, "isWritable": true  },
      { "pubkey": "7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF", "isSigner": false, "isWritable": false },
      { "pubkey": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", "isSigner": false, "isWritable": false },
      { "pubkey": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", "isSigner": false, "isWritable": false },
      { "pubkey": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", "isSigner": false, "isWritable": false },
      { "pubkey": "3t4JZcueEzTbVP6kLxXrL3VpWx45jDer4eqysweBchNH", "isSigner": false, "isWritable": false }
    ]
  },
  {
    "metadata": { "name": "Withdraw from Kamino", "description": "Burn cUSDC, receive USDC" },
    "programId": "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD",
    "discriminator": "ea75b57db98edc1d",
    "args": [
      { "name": "collateral_amount", "type": "u64", "value": "24500000" }   // cUSDC base units to redeem
    ],
    "accounts": [
      { "pubkey": "{INTERMEDIARY}",                                 "isSigner": true,  "isWritable": false },
      { "pubkey": "7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF",   "isSigner": false, "isWritable": false },
      { "pubkey": "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59",   "isSigner": false, "isWritable": true  },
      { "pubkey": "9DrvZvyWh1HuAoZxvYWMvkf2XCzryCpGgHqrMjyDWpmo",   "isSigner": false, "isWritable": false },
      { "pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",   "isSigner": false, "isWritable": false },
      { "pubkey": "B8V6WVjPxW1UGwVDfxH2d2r8SyT4cqn7dQRK6XneVa7D",   "isSigner": false, "isWritable": true  },
      { "pubkey": "Bgq7trRgVMeq33yt235zM2onQ4bRDBsY5EWiTetF4qw6",   "isSigner": false, "isWritable": true  },
      { "pubkey": "<intermediary cUSDC ATA>",                       "isSigner": false, "isWritable": true  },
      { "pubkey": "<intermediary USDC ATA>",                        "isSigner": false, "isWritable": true  },
      { "pubkey": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",   "isSigner": false, "isWritable": false },
      { "pubkey": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",   "isSigner": false, "isWritable": false },
      { "pubkey": "Sysvar1nstructions1111111111111111111111111",   "isSigner": false, "isWritable": false }
    ]
  }
]
```

### 1a. The single-request path (`dry: false`)

If you're redeeming a concrete cUSDC amount and the redemption yields comfortably more USDC than the network fee, send **one** create request with `dry: false`:

```jsonc
POST /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5/steps
{
  "version": "1.0",
  "type": "solana",
  "destinationAsset": "<destination asset id for USDC on Solana>",
  "steps": [ /* the two steps above, with collateral_amount = 24500000 */ ],
  "dry": false
}
```

Then sign and submit (below). That is **one** create request (plus the universal `submit`).

### 1b. Making sure the fee is covered

After your steps, the service appends its **own** fee instruction that debits the intermediary **in USDC** (the destination token). Unlike a deposit, a redeem *produces* USDC and the fee is taken **from that same redeemed USDC** — so you do **not** subtract the fee from `collateral_amount`. (`collateral_amount` is a cUSDC amount, and shrinking it would shrink the USDC the redeem yields, i.e. shrink the balance the fee is paid from.) Just make sure the redemption yields more USDC than the fee. A full-position redeem normally does. Against a very thin position, redeem *more*, not less.

To see the fee up front, send a `dry: true` create — it returns `result.details.networkFee` (USDC base units):

```jsonc
POST /api/v1/executions/{wallet}/steps
{ "version": "1.0", "type": "solana", "destinationAsset": "…",
  "steps": [ /* collateral_amount = the cUSDC you want to redeem */ ],
  "dry": true }
```

```jsonc
// response
{ "result": { "details": { "networkFee": "40000" /* 0.04 USDC */ } } }
```

Then execute the same request with `dry: false` and submit (step 4, below). You never add a fee step yourself — the service injects it.

> A `dry: true` preview echoes your submitted steps back unchanged. A real (`dry: false`) create returns the steps the service actually signs — resolved `{INTERMEDIARY}`, any prepended ATA create, and the appended fee instruction rendered generically (full instruction data as the `discriminator`, empty `args`).

***

## Mode 2 — quote-with-steps (redeem, then bridge USDC out)

To redeem your cUSDC on Solana **and** bridge the resulting USDC out to another chain in a single signed request, use the quote-backed create endpoint with `"outOperation": true`. This is a Solana **out-operation** (`execution_mode = quote_with_steps`): the action runs on Solana and its output is bridged out. There is **no deposit phase** — you sign the action at creation.

Two things change versus steps-only:

* The request carries a **`quote`** and \*\*requires an `x-api-key` header`**, plus the top-level` "outOperation": true\`.
* A third **producer transfer** step moves the redeemed USDC to the 1Click deposit address. Its destination is the **`{DEPOSIT_ADDRESS}`** placeholder (out-op only) and its amount is a placeholder the service resolves (see the swap-type table).

```jsonc
POST /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "solana",
  "outOperation": true,
  "quote": {
    "originAsset": "<1click asset id for USDC on Solana>",
    "amount": "24500000",
    "destinationAsset": "<1click destination asset id, e.g. USDC on Base>",
    "slippageTolerance": 100,          // basis points (1%)
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-17T12:00:00Z",
    "recipient": "<optional destination-chain recipient>"
  },
  "steps": [
    { /* refresh_reserve — identical to Mode 1 */ },
    { /* redeem_reserve_collateral — identical to Mode 1, collateral_amount = the cUSDC to redeem */ },
    {
      "metadata": { "name": "Transfer to deposit address", "description": "Send redeemed USDC to the 1Click bridge" },
      "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      "discriminator": "0c",                                    // TransferChecked
      "args": [
        // EXACT_INPUT: send any u64 (e.g. the amount you're redeeming) — the service
        // overwrites it with quote.amount − networkFee. EXACT_OUTPUT: send the
        // literal "{AMOUNT_IN}" placeholder instead.
        { "name": "amount",   "type": "u64", "value": "24500000" },
        { "name": "decimals", "type": "u8",  "value": 6 }
      ],
      "accounts": [
        { "pubkey": "<intermediary USDC ATA>",                     "isSigner": false, "isWritable": true  },  // source
        { "pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "isSigner": false, "isWritable": false }, // USDC mint
        { "pubkey": "{DEPOSIT_ADDRESS}",                            "isSigner": false, "isWritable": true  }, // 1Click deposit
        { "pubkey": "{INTERMEDIARY}",                              "isSigner": true,  "isWritable": false }   // authority
      ]
    }
  ],
  "metadata": { "title": "Withdraw from Kamino (cross-chain)", "intent": "kamino_withdraw" },
  "dry": false
}
```

This is a **single** `dry: false` create request. The out-op signs at creation and there is no separate deposit transfer.

#### EXACT\_INPUT vs EXACT\_OUTPUT

The redeem's `collateral_amount` is always the cUSDC you burn. What changes is the **producer transfer's amount placeholder** and what `quote.amount` means:

|                               | **EXACT\_INPUT**                                                                                                                                                                                                   | **EXACT\_OUTPUT**                                                                                                                                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `quote.amount` denominated in | the **origin** USDC on Solana (what you redeem/bridge)                                                                                                                                                             | the **destination** token on the far chain (what you want to receive)                                                                                                                                  |
| Producer amount value         | a concrete `u64` (overwritten)                                                                                                                                                                                     | the `{AMOUNT_IN}` placeholder (overwritten)                                                                                                                                                            |
| Who sets the producer amount  | you send any `u64` — the service **overwrites** it with a deterministic `quote.amount − networkFee` (known up front). Ensure the redeem yields at least `quote.amount`, or the pre-sign simulation reverts (`400`) | `{AMOUNT_IN}` substitutes to `quote.amountIn + network fee`, but the producer amount is then overwritten to `quote.amountIn` (the fee is a separate injected transfer). 1Click refunds unused slippage |
| What's fixed                  | the USDC you bridge out                                                                                                                                                                                            | the amount received on the destination chain (`≈ Y`)                                                                                                                                                   |

* **`{DEPOSIT_ADDRESS}`** is out-op only. For this SPL producer it substitutes to the 1Click deposit address's **associated token account** for the mint (the service also prepends a `CreateIdempotent` to provision it) — not the raw deposit wallet. (For a native-SOL producer it substitutes to the bare deposit wallet.)
* **`{AMOUNT_IN}`** is valid **only** for an out-operation with `swapType = EXACT_OUTPUT`. Using it anywhere else is a `400`.
* For **EXACT\_INPUT**, put any `u64` in the producer amount (e.g. the amount you're redeeming). The service overwrites it server-side with `quote.amount − networkFee` (a deterministic value, known up front — not the redeem's actual on-chain output), so size the redeem to yield at least `quote.amount`. **There is no `{amount}` sentinel on the wire** — the only backend placeholders are `{INTERMEDIARY}`, `{MIN_AMOUNT_OUT}`, `{DEPOSIT_ADDRESS}`, and `{AMOUNT_IN}`. (The reference frontend uses an `{amount}` editor placeholder but resolves it to a concrete number before POSTing.)

#### Out-operation lifecycle (what follows the create)

1. **Create** (above) returns a signing `payload` in `result.details`.
2. **Sign** it with your origin wallet and **submit** the signature. The out-operation executes on Solana (redeem + producer transfer), the producer transfer funds the 1Click deposit address, and 1Click bridges the USDC out to the destination chain. There is no deposit for you to send.

***

### Notes on the accounts

* **The three repeated `KLend…` entries in `refresh_reserve`** are the unused Pyth / Switchboard oracle slots — the same as in the supply flow. The USDC reserve is Scope-priced, so only the last slot (`3t4J…`) is a real account.
* **`redeem_reserve_collateral` account order:** owner, **lending market**, **reserve**, market authority, reserve liquidity mint, **reserve collateral mint**, **reserve liquidity supply**, source collateral (cUSDC), destination liquidity (USDC), then the two token-program slots and the instructions sysvar. This differs from the deposit order — do not reuse the deposit account list.
* **`{INTERMEDIARY}` is the only signer** — in every step, including the producer transfer's authority.
* **`<intermediary cUSDC ATA>`** is the source collateral account (burned). **`<intermediary USDC ATA>`** is the destination liquidity account (received) and the producer transfer's source.

### Discriminators

Anchor 8-byte discriminators (first 8 bytes of `sha256("global:" + name)`), plus the SPL Token opcode for the producer transfer:

| Instruction                            | Discriminator      |
| -------------------------------------- | ------------------ |
| `refresh_reserve`                      | `02da8aeb4fc91966` |
| `redeem_reserve_collateral`            | `ea75b57db98edc1d` |
| SPL Token `TransferChecked` (producer) | `0c`               |

### Amount

`collateral_amount` is in **cUSDC** base units — the amount of collateral token to burn, not the USDC you expect back. Because cUSDC accrues value against USDC, redeeming your full cUSDC balance returns slightly more USDC than you originally deposited.

* **steps-only:** a concrete cUSDC number. Send it directly (Mode 1a). Optionally preview the fee first (Mode 1b) to confirm the redeem yields more USDC than the fee. To redeem everything, use your intermediary's full cUSDC balance.
* **quote-with-steps (out-op):** the redeem `collateral_amount` is still the cUSDC to burn. The **producer** amount is either a concrete `u64` (EXACT\_INPUT, which the service overwrites to `quote.amount − networkFee`) or the `{AMOUNT_IN}` placeholder (EXACT\_OUTPUT). `{amount}` is only the reference frontend's editor sentinel, resolved to a number before POST — it is not a backend placeholder.

### Rules that apply here

* **Only `{INTERMEDIARY}` may be a signer.**
* **Do not add compute-budget, nonce, or fee instructions** — the service injects those. Your steps are echoed back **untouched** in a `dry: true` preview. A real create returns the exact transaction the service signs (resolved `{INTERMEDIARY}`, any prepended ATA create, the appended fee instruction, and — for an out-op — the resolved `{DEPOSIT_ADDRESS}` / producer amount).
* **Stay within the limits.** The service injects a compute-unit budget (200000 by default, set by the deployment) covering all your steps together, and the assembled transaction must be ≤ **1232 bytes**. This withdraw fits comfortably under both.
* **A steps-only SPL action must touch its destination token** — the redemption references the intermediary's USDC ATA (the destination mint), so the requirement is satisfied.
* **SPL destinations settle via a gasless relayer (Kora)** that pays the SOL gas and takes the network fee in the destination token. The mint must be on the deployment's `kora_fee_token_mints` allow-list, or create returns `400`.

### Sign and submit

The create response returns, under `result.details`, a `payload` to sign and a `signingStandard` — **your origin wallet's** standard. Sign `result.details.payload` with your origin wallet exactly as for any destination — you never handle the inner Solana message — then submit the signature:

```
POST /api/v1/executions/{wallet}/submit
```

The submit body is `{ signature, executionId }` for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, or Stellar) must also include `publicKey`. A TON origin includes both `publicKey` and a `tonConnect` envelope, so its body is `{ signature, executionId, publicKey, tonConnect }`.

After it settles, the cUSDC is burned and — for steps-only — your intermediary holds the redeemed USDC. For an out-operation the USDC has been bridged out to the destination chain.


# Native SOL scenario

A worked, self-contained guide to acting on **native SOL** — as opposed to an SPL token like USDC. It covers a plain **SOL transfer**, and staking/unstaking native SOL through the **Jito SPL Stake Pool** (native SOL in, native SOL out, minting and burning the **jitoSOL** liquid-staking token). Each example is shown in **both** execution modes: **steps-only** (the intermediary already holds SOL / jitoSOL) and **quote-with-steps** (bridge SOL in, or bridge the proceeds out, in one signed request).

### How native SOL differs from an SPL token

Native SOL is not an SPL token, and several rules change because of it:

* **No token account.** SOL lives directly in the account's lamport balance — there is no associated token account (ATA) to derive or create. SOL is moved with an explicit **System `Transfer`** instruction (`programId` `11111111111111111111111111111111`, discriminator `02000000`, a `u64` `lamports` arg, accounts `[from, to]`). There is no `value`/lamports field on a step.
* **No wrapping.** The service does **not** wrap SOL into wrapped-SOL. The Jito stake pool takes native SOL in and out directly, which is why it — not a wrapped-SOL Kamino reserve — backs the native flow here.
* **9 decimals.** 1 SOL = `1000000000` lamports. (jitoSOL, an SPL token, also has 9 decimals.)
* **The network fee is charged in native SOL** (lamports), debited from the intermediary — see Fees and the rent reserve.
* **Exempt from the "must touch the destination token" guard.** A steps-only SPL destination must include a step referencing the dest mint or its ATA. A native-SOL destination has no mint, so this check does not apply.
* **The liquid-staking token&#x20;*****is*****&#x20;an SPL token.** jitoSOL is a normal SPL token, so the intermediary's **jitoSOL ATA** is a real ATA the service creates in-message if it's missing (billing its one-time rent only when it was). Only the SOL side needs no account.

Everything else — the request envelope, signing with your origin wallet, and the submit call — is the same as for any destination. For the conceptual overview and the two-mode contrast (steps-only vs quote-with-steps), see Using a Solana destination. The mechanics mirror Kamino stake and Kamino withdraw.

### Addresses (Jito SPL Stake Pool, Solana mainnet)

Read off-chain 2026-07. The stake-pool accounts are specific to the Jito pool. The programs and sysvars are network constants.

| Account                       | Address                                        |
| ----------------------------- | ---------------------------------------------- |
| SPL Stake Pool program        | `SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy`  |
| Stake pool (Jito)             | `Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb`  |
| Pool withdraw authority (PDA) | `6iQKfEyhr3bZMotVkW6beNZz5CPAkiwvgV2CTje9pVSS` |
| Reserve stake account         | `BgKUXdS29YcHCFrPm5M8oLHiTzZaMDjsebggjoaQ6KFL` |
| Pool mint (jitoSOL)           | `J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn` |
| Manager fee account           | `8yoigZfzZ1nNaadumY9uPVD118225UYHTDpmjpr2nrSa` |
| System program                | `11111111111111111111111111111111`             |
| Stake program                 | `Stake11111111111111111111111111111111111111`  |
| Clock sysvar                  | `SysvarC1ock11111111111111111111111111111111`  |
| Stake history sysvar          | `SysvarStakeHistory1111111111111111111111111`  |
| SPL Token program             | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`  |

The one address you supply yourself for the stake-pool flows is:

* **`<intermediary jitoSOL ATA>`** — the intermediary's associated token account for the jitoSOL pool mint. Derive it from your resolved Solana intermediary and the pool mint (a PDA of `[ intermediary, TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA, J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn ]` under `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`). Fetch the resolved intermediary from `GET /api/v1/executions/{wallet}/intermediary` → `result.solana`.

The SPL Stake Pool discriminators are single-byte Borsh enum variant indices:

| Instruction       | Variant | Discriminator |
| ----------------- | ------- | ------------- |
| `DepositSol`      | 14      | `0e`          |
| `WithdrawSol`     | 16      | `10`          |
| System `Transfer` | —       | `02000000`    |

`DepositSol`'s sol-deposit authority and `WithdrawSol`'s sol-withdraw authority are both `None` (permissionless) on this pool, so the optional trailing authority account is omitted from both instructions.

***

## Example A — transfer native SOL (steps-only)

Moving SOL out of the intermediary is a single System `Transfer`. No ATA, no create step — the recipient is a plain wallet address.

```jsonc
POST /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5/steps
{
  "version": "1.0",
  "type": "solana",
  "destinationAsset": "<destination asset id for native SOL>",
  "steps": [
    {
      "metadata": { "name": "Transfer SOL", "description": "Send 0.5 SOL to the recipient" },
      "programId": "11111111111111111111111111111111",
      "discriminator": "02000000",
      "args": [
        { "name": "lamports", "type": "u64", "value": "500000000" }   // 0.5 SOL (9 decimals)
      ],
      "accounts": [
        { "pubkey": "{INTERMEDIARY}",   "isSigner": true,  "isWritable": true },   // from (only signer)
        { "pubkey": "<recipient wallet>", "isSigner": false, "isWritable": true }  // to
      ]
    }
  ],
  "dry": false
}
```

That's **one** create request (plus `submit`). Because the fee is charged in SOL, keep an amount that leaves the intermediary with the fee plus its rent-exempt minimum — or use the three-request carve (Example B, step 1b) to size it exactly.

***

## Example B — stake native SOL (Jito `DepositSol`)

`DepositSol` takes native SOL from the intermediary and mints **jitoSOL** into the intermediary's jitoSOL ATA. A single instruction — no `refresh`, no lookup tables.

### The step

```jsonc
{
  "metadata": { "name": "Deposit SOL to Jito", "description": "Stake native SOL, receive jitoSOL" },
  "programId": "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy",
  "discriminator": "0e",
  "args": [
    { "name": "lamports", "type": "u64", "value": "500000000" }   // 0.5 SOL
  ],
  "accounts": [
    { "pubkey": "Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb", "isSigner": false, "isWritable": true  },  // stake pool
    { "pubkey": "6iQKfEyhr3bZMotVkW6beNZz5CPAkiwvgV2CTje9pVSS", "isSigner": false, "isWritable": false },  // withdraw authority
    { "pubkey": "BgKUXdS29YcHCFrPm5M8oLHiTzZaMDjsebggjoaQ6KFL", "isSigner": false, "isWritable": true  },  // reserve stake
    { "pubkey": "{INTERMEDIARY}",                               "isSigner": true,  "isWritable": true  },  // funding SOL (only signer)
    { "pubkey": "<intermediary jitoSOL ATA>",                   "isSigner": false, "isWritable": true  },  // destination pool token
    { "pubkey": "8yoigZfzZ1nNaadumY9uPVD118225UYHTDpmjpr2nrSa", "isSigner": false, "isWritable": true  },  // manager fee account
    { "pubkey": "<intermediary jitoSOL ATA>",                   "isSigner": false, "isWritable": true  },  // referral (no referrer → user pool ATA)
    { "pubkey": "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", "isSigner": false, "isWritable": true  },  // pool mint (jitoSOL)
    { "pubkey": "11111111111111111111111111111111",            "isSigner": false, "isWritable": false },  // system program
    { "pubkey": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "isSigner": false, "isWritable": false }   // SPL token program
  ]
}
```

### Mode 1 — steps-only (intermediary already holds SOL)

#### 1a. Single-request path (`dry: false`)

If the amount leaves the intermediary with enough SOL for the fee (and rent), send one `dry: false` create with `lamports` set to a concrete value:

```jsonc
POST /api/v1/executions/{wallet}/steps
{ "version": "1.0", "type": "solana", "destinationAsset": "<native SOL asset id>",
  "steps": [ /* the DepositSol step above, lamports = 500000000 */ ],
  "dry": false }
```

#### 1b. Three-request path (adjust the amount by the fee)

To stake close to your whole balance you must leave SOL for the appended fee (and the rent-exempt minimum). Learn the fee, carve the amount, then execute — three requests:

1. **Estimate** — `dry: true` create returns `result.details.networkFee` (lamports):

   ```jsonc
   POST /api/v1/executions/{wallet}/steps
   { "version": "1.0", "type": "solana", "destinationAsset": "<native SOL asset id>",
     "steps": [ /* lamports = your target, e.g. 500000000 */ ], "dry": true }
   ```

   ```jsonc
   { "result": { "details": { "networkFee": "15000" } } }
   ```
2. **Adjust** `lamports` to `amount − networkFee` (and keep the rent-exempt reserve — see below): `500000000 − 15000 = 499985000`.
3. **Execute** the same request with the carved `lamports` and `dry: false`.

The three requests are **`dry:true` estimate → `dry:false` execute → `submit`**.

### Mode 2 — quote-with-steps (bridge SOL in, then stake)

Bridge SOL in from another chain and stake it in one signed request via the quote-backed create endpoint (**requires `x-api-key`**). Set `lamports` to the **`{MIN_AMOUNT_OUT}`** placeholder — the service fills the post-fee bridged amount (after also reserving the rent-exempt minimum for native SOL). This is a bridge-in (`execution_mode = quote_with_steps`).

```jsonc
POST /api/v1/executions/{wallet}
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "solana",
  "quote": {
    "originAsset": "<1click origin asset id, e.g. ETH on Base>",
    "amount": "500000000",
    "destinationAsset": "<1click asset id for native SOL>",
    "slippageTolerance": 100,
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-17T12:00:00Z"
  },
  "steps": [
    {
      "programId": "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy",
      "discriminator": "0e",
      "args": [ { "name": "lamports", "type": "u64", "value": "{MIN_AMOUNT_OUT}" } ],
      "accounts": [ /* identical to the DepositSol step above */ ]
    }
  ],
  "dry": false
}
```

**EXACT\_INPUT vs EXACT\_OUTPUT** — the step is identical. Only `quote.swapType` / `quote.amount` and the deposit differ (`{AMOUNT_IN}` is out-op only and rejected on a bridge-in):

* **EXACT\_INPUT** — `quote.amount` is the origin token you send. You deposit exactly that. The service stakes `arrived − fees`.
* **EXACT\_OUTPUT** — `quote.amount` is the native SOL you want staked (`≈ Y`). The service grosses the quote up by the fee, and you deposit `result.quote.amountIn` (origin-token atomic units) to the returned deposit address.

***

## Example C — unstake to native SOL (Jito `WithdrawSol`)

`WithdrawSol` burns jitoSOL from the intermediary's jitoSOL ATA and returns native SOL to the intermediary. Note the account order differs from `DepositSol` and the intermediary appears **twice** — once as the burn authority, once as the lamport recipient.

### The step

```jsonc
{
  "metadata": { "name": "Withdraw SOL from Jito", "description": "Burn jitoSOL, receive native SOL" },
  "programId": "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy",
  "discriminator": "10",
  "args": [
    { "name": "poolTokens", "type": "u64", "value": "500000000" }   // jitoSOL base units to burn
  ],
  "accounts": [
    { "pubkey": "Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb", "isSigner": false, "isWritable": true  },  // stake pool
    { "pubkey": "6iQKfEyhr3bZMotVkW6beNZz5CPAkiwvgV2CTje9pVSS", "isSigner": false, "isWritable": false },  // withdraw authority
    { "pubkey": "{INTERMEDIARY}",                               "isSigner": true,  "isWritable": false },  // transfer authority (only signer)
    { "pubkey": "<intermediary jitoSOL ATA>",                   "isSigner": false, "isWritable": true  },  // burn pool token from here
    { "pubkey": "BgKUXdS29YcHCFrPm5M8oLHiTzZaMDjsebggjoaQ6KFL", "isSigner": false, "isWritable": true  },  // reserve stake
    { "pubkey": "{INTERMEDIARY}",                               "isSigner": false, "isWritable": true  },  // receives lamports
    { "pubkey": "8yoigZfzZ1nNaadumY9uPVD118225UYHTDpmjpr2nrSa", "isSigner": false, "isWritable": true  },  // manager fee account
    { "pubkey": "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", "isSigner": false, "isWritable": true  },  // pool mint (jitoSOL)
    { "pubkey": "SysvarC1ock11111111111111111111111111111111", "isSigner": false, "isWritable": false },  // clock sysvar
    { "pubkey": "SysvarStakeHistory1111111111111111111111111", "isSigner": false, "isWritable": false },  // stake history sysvar
    { "pubkey": "Stake11111111111111111111111111111111111111", "isSigner": false, "isWritable": false },  // stake program
    { "pubkey": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "isSigner": false, "isWritable": false }   // SPL token program
  ]
}
```

### Mode 1 — steps-only

Same shape as Example B, Mode 1: one `dry: false` create for a concrete `poolTokens`, or the three-request `dry:true → dry:false → submit` carve when you need to size against the fee. The redeemed SOL lands in the intermediary.

### Mode 2 — quote-with-steps (unstake, then bridge SOL out)

To unstake **and** bridge the SOL out to another chain, use the quote-backed create endpoint with `"outOperation": true`. A second **producer** step — a native System `Transfer` — sends the withdrawn SOL to the 1Click deposit address (a bare wallet for native SOL, not an ATA):

```jsonc
POST /api/v1/executions/{wallet}
// header: x-api-key: <your key>
{
  "version": "1.0",
  "type": "solana",
  "outOperation": true,
  "quote": {
    "originAsset": "<1click asset id for native SOL>",
    "amount": "500000000",
    "destinationAsset": "<1click destination asset id on the far chain>",
    "slippageTolerance": 100,
    "swapType": "EXACT_INPUT",
    "deadline": "2026-07-17T12:00:00Z"
  },
  "steps": [
    { /* the WithdrawSol step above, poolTokens = jitoSOL to burn */ },
    {
      "metadata": { "name": "Transfer to deposit address", "description": "Send withdrawn SOL to the 1Click bridge" },
      "programId": "11111111111111111111111111111111",
      "discriminator": "02000000",
      "args": [
        // EXACT_INPUT: send any u64 (e.g. the amount you're withdrawing) — the service
        // overwrites it with quote.amount − networkFee. EXACT_OUTPUT: send the
        // literal "{AMOUNT_IN}" placeholder instead.
        { "name": "lamports", "type": "u64", "value": "500000000" }
      ],
      "accounts": [
        { "pubkey": "{INTERMEDIARY}",     "isSigner": true,  "isWritable": true },  // from (only signer)
        { "pubkey": "{DEPOSIT_ADDRESS}",  "isSigner": false, "isWritable": true }   // 1Click deposit (bare wallet)
      ]
    }
  ],
  "dry": false
}
```

* **EXACT\_INPUT** — put any `u64` in the producer `lamports` (e.g. the amount you're withdrawing). The service **overwrites** it with a deterministic `quote.amount − networkFee` (known up front — not the withdraw's actual on-chain output). Size the `WithdrawSol` so the SOL it produces covers `quote.amount`, or the pre-sign simulation reverts (`400`). **There is no `{amount}` sentinel on the wire** — the only backend placeholders are `{INTERMEDIARY}`, `{MIN_AMOUNT_OUT}`, `{DEPOSIT_ADDRESS}`, and `{AMOUNT_IN}`.
* **EXACT\_OUTPUT** — put the **`{AMOUNT_IN}`** placeholder in the producer `lamports`. It substitutes to the origin commitment `quote.amountIn + network fee`, but the producer's amount is then overwritten to `quote.amountIn` (the fee is a separate injected transfer). 1Click refunds unused slippage. `{AMOUNT_IN}` is valid only in this position.

An out-operation signs at creation and has no deposit phase.

***

### Fees and the rent reserve

* **The fee is native SOL.** For a native-SOL action the service uses its 2-signer gas model (a backend account that is both fee payer and durable-nonce authority, plus your intermediary). The service pays the on-chain SOL gas, then appends a native-lamport `Transfer` to its service-fee address that debits the intermediary. So the fee comes out of the intermediary's **SOL** balance — which is why staking or transferring your *whole* balance fails, and why the three-request carve exists.
* **Rent-exempt reserve.** A system account can't be left in the sub-rent "dust" band. On a **bridge-in** the service reserves the rent-exempt minimum as well as the fee when it resolves `{MIN_AMOUNT_OUT}`. For a **steps-only** action you must leave that reserve yourself — size the amount to `balance − networkFee − rentReserve`.
* **Do not add compute-budget, nonce, or fee instructions** — the service injects those. A `dry: true` preview omits those injected instructions. Steps-only and out-operation echoes are otherwise your submitted steps verbatim, while a bridge-in echo additionally resolves `{MIN_AMOUNT_OUT}` to its post-fee value. A real create returns the exact transaction the service signs.

### Sign and submit

The create response returns, under `result.details`, a `payload` to sign and a `signingStandard` — **your origin wallet's** standard. Sign `result.details.payload` with your origin wallet exactly as for any destination — you never handle the inner Solana message — then submit:

```
POST /api/v1/executions/{wallet}/submit
```

The submit body is `{ signature, executionId }` for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, or Stellar) must also include `publicKey`. A TON origin includes both `publicKey` and a `tonConnect` envelope, so its body is `{ signature, executionId, publicKey, tonConnect }`.


# Transfer USDC scenario

A worked, self-contained example of moving USDC out of your Solana intermediary's USDC account with a **steps-only** execution — the intermediary already holds the USDC, and you simply send some of it to another account.

The transfer itself is a single SPL Token `Transfer` instruction. This document also includes the small amount of surrounding context you need (the intermediary, the token accounts, and the sign/submit flow) so it stands on its own.

### What runs on-chain

One SPL Token `Transfer` (opcode `03`):

* **program**: the SPL Token program `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`
* **arg**: a single `u64` `amount` in the token's base units
* **accounts**, in this exact order:
  1. `source` — the intermediary's USDC token account (funds leave here)
  2. `destination` — the recipient's USDC token account (funds arrive here)
  3. `authority` — the intermediary, which authorizes the transfer (`{INTERMEDIARY}`)

USDC has **6 decimals**, so amounts are `value × 10^6` base units: 25 USDC = `25000000`, 1.5 USDC = `1500000`.

### The two token accounts

**`source` — your intermediary's USDC ATA.** Derive it from your resolved Solana intermediary and the USDC mint:

1. Fetch your Solana intermediary: `GET /api/v1/executions/{wallet}/intermediary` → read `result.solana`.
2. Derive its associated token account — a program-derived address of `[ intermediary, TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA, EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v ]` under the associated-token-account program `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`, where `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` is the USDC mint.
3. Use that base58 address as `source`.

**`destination` — the recipient's USDC token account.** This is the recipient's associated token account for the USDC mint (derived the same way, with the recipient's wallet as the owner). If it might not exist yet, prepend an associated-token-account `CreateIdempotent` for it and name `{INTERMEDIARY}` as the funder — the service reassigns the funder to the transaction fee payer at signing time, and bills the one-time rent into the fee only if the account was actually missing.

**`authority`** is the account that owns `source` and authorizes the debit — your intermediary. Send it as the `{INTERMEDIARY}` placeholder. The service substitutes your resolved Solana address before signing. It is the **only** account you may mark as a signer.

### The request

```jsonc
POST /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5/steps
{
  "version": "1.0",
  "type": "solana",
  "destinationAsset": "<destination asset id for USDC on Solana>",
  "steps": [
    {
      "metadata": { "name": "Transfer USDC", "description": "Send 25 USDC to the recipient" },
      "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      "discriminator": "03",
      "args": [
        { "name": "amount", "type": "u64", "value": "25000000" }   // 25 USDC (6 decimals)
      ],
      "accounts": [
        { "pubkey": "<intermediary USDC ATA>", "isSigner": false, "isWritable": true  },
        { "pubkey": "<recipient USDC account>", "isSigner": false, "isWritable": true  },
        { "pubkey": "{INTERMEDIARY}",           "isSigner": true,  "isWritable": false }
      ]
    }
  ],
  "dry": false
}
```

* `type: "solana"` selects the Solana step shape.
* `destinationAsset` is the destination token id for USDC on Solana.
* The `metadata` object is optional display text and can be omitted.
* Add `"dry": true` to preview the network fee without committing.

#### `TransferChecked` variant (optional, safer)

If you prefer the checked variant — which additionally verifies the mint and its decimals on-chain — use opcode `0c`, add a `u8` `decimals` arg after `amount`, and insert the mint as the second account:

```jsonc
{
  "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
  "discriminator": "0c",
  "args": [
    { "name": "amount",   "type": "u64", "value": "25000000" },
    { "name": "decimals", "type": "u8",  "value": 6 }
  ],
  "accounts": [
    { "pubkey": "<intermediary USDC ATA>",                         "isSigner": false, "isWritable": true  },
    { "pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",    "isSigner": false, "isWritable": false },  // USDC mint
    { "pubkey": "<recipient USDC account>",                        "isSigner": false, "isWritable": true  },
    { "pubkey": "{INTERMEDIARY}",                                  "isSigner": true,  "isWritable": false }
  ]
}
```

### Rules that apply here

* **Only `{INTERMEDIARY}` may be a signer.** Do not mark any other account `"isSigner": true`.
* **Do not add a fee, compute-budget, or nonce instruction.** The service injects those itself. On a real create the returned `steps` mirror the transaction the service signs, so they include the fee transfer the service appends (plus any associated-token-account create it prepends), and your `{INTERMEDIARY}` placeholder appears resolved to a concrete address. Only a `dry: true` preview echoes your submitted steps back unchanged.
* **The fee is charged in USDC via a gasless relayer (Kora).** SPL destinations settle through Kora, which pays the SOL gas and collects the network fee as an SPL transfer in the destination token — debited from the same intermediary USDC balance, so the amount you send plus the fee must fit. The destination mint must be on the deployment's `kora_fee_token_mints` allow-list (USDC is), or create returns `400`.
* **A steps-only SPL action must touch its destination token.** This transfer does — `source` is the intermediary's ATA for the destination mint — so the requirement is satisfied.
* **Amounts are base units, strictly encoded.** A value that overflows `u64` or a negative value is rejected, not wrapped.

### Sign and submit

The create response returns, under `result.details`, a `payload` to sign and a `signingStandard` — **your origin wallet's** standard (for example `erc191` for an EVM origin, `raw_ed25519` for a Solana origin). Sign `result.details.payload` with your origin wallet exactly as you would for any destination — you never handle the inner Solana message — then submit:

```
POST /api/v1/executions/{wallet}/submit
```

The submit body is `{ signature, executionId }` for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, or Stellar) must also include `publicKey`. A TON origin includes both `publicKey` and a `tonConnect` envelope, so its body is `{ signature, executionId, publicKey, tonConnect }`. On success the execution proceeds and the USDC moves from the intermediary's account to the recipient.


# Using Solana as destination

This guide explains how to interact with the service when your execution runs on **Solana** — how your account on Solana is addressed, how to describe an on-chain action as a list of steps, and how to sign and submit. It is the conceptual overview. The step format, arg types, placeholders, and rules below are illustrated with inline snippets and a quick reference. For full worked examples see the companion guides (USDC transfer, native SOL, Kamino stake/withdraw).

When an execution targets Solana, the only thing that changes versus any other destination is the **contents of the `steps` array** — each step is a Solana instruction instead of an EVM call. Everything around it (the request envelope, signing with your origin wallet, and the submit call) is the same as for any destination.

### Your Solana intermediary account

The action does not run from your own wallet. It runs from a dedicated **intermediary account** that the service controls on your behalf. On Solana this is an ed25519 account that is **deterministically derived from your origin wallet** — it is stable for a given origin wallet, but you cannot compute it yourself, so you fetch it from the API.

#### Get your Solana address from your origin wallet

Call the intermediary endpoint with your origin wallet in the path. For an EVM origin, that is your `0x…` address:

```
GET /api/v1/executions/0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5/intermediary
```

```jsonc
{
  "result": {
    "originAccount": "0xF1a2B3c4D5E6f7089A0b1C2d3E4f5061728394a5",
    "originType":    "evm",
    "evm":           "0x9c8B7a6F5e4D3c2B1a09F8e7D6c5B4a3928170615",  // your EVM intermediary
    "solana":        "2fjhr2fzcoHYvdKkYpxBsUnE5QDg2hhb2mnxfwrL7RTY"   // your SOLANA intermediary
  }
}
```

Read `result.solana` — that base58 string is **your** Solana intermediary account. The same endpoint works for Solana, NEAR, Stellar, and Tron origins too (TON origins additionally require a `publicKey` query parameter). `solana` is `null` if Solana destinations are not enabled for the deployment.

You normally do **not** paste this address into your steps. Instead you use the placeholder `{INTERMEDIARY}` wherever the intermediary appears (as an owner, authority, or signer), and the service substitutes the real address for you. Fetch the resolved address only when you need to **derive another account from it** — most commonly a token account (below).

#### Your intermediary's token accounts

Token balances are held in **associated token accounts (ATAs)** owned by the intermediary. There is no placeholder for an ATA, because an ATA address depends on the resolved intermediary. So when a step needs one of your intermediary's token accounts:

1. Fetch the resolved Solana intermediary address (above).
2. Derive its associated token account for the relevant mint using the standard associated-token-account derivation (owner = the resolved intermediary, plus the mint and the token program).
3. Put that concrete base58 ATA address into the step's `accounts`.

If a step references both a **mint** and the intermediary's **writable** ATA for that mint, and the ATA does **not exist yet**, the service creates it for you inside the same signed transaction — you do not add a create step, and you are billed its one-time rent only if it was actually missing. This covers the instructions that carry the mint as an account (`TransferChecked`, `MintTo`, `Burn`, and most program deposit/withdraw instructions). A bare SPL `Transfer` (opcode `03`) does **not** list the mint, so the service cannot detect a missing intermediary ATA from it — into a fresh ATA it would revert on-chain. Use `TransferChecked` or add the `CreateIdempotent` yourself. (The one exception to needing an ATA at all is a bridge-in: the destination token's ATA is provisioned by the bridge as it settles your deposit.)

If a step pays out to **someone else's** token account that might not exist, you must include the account-create yourself. Name `{INTERMEDIARY}` as its funder (the only signer you may name) and the service reassigns the funder to the fee payer at signing time.

### The three ways to reach a Solana action

| Mode              | Endpoint                                                    | Envelope                                | When                                                                     |
| ----------------- | ----------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------ |
| **Steps-only**    | `POST /api/v1/executions/{wallet}/steps`                    | no `quote`                              | the intermediary already holds the destination token. You just act on it |
| **Bridge-in**     | `POST /api/v1/executions/{wallet}`                          | carries a `quote`, requires `x-api-key` | bridge a token in from another chain and act on it in one signed request |
| **Out-operation** | `POST /api/v1/executions/{wallet}` + `"outOperation": true` | carries a `quote`, requires `x-api-key` | the action runs on Solana and its output is bridged out to another chain |

The Solana step shape applies whenever the action chain is Solana. On `POST /api/v1/executions/{wallet}` an explicit `type` must agree with the action chain: `type: "solana"` means a Solana action, and `type: "evm"` may not target one. On the steps-only endpoint a Solana destination uses Solana steps when `type` is unset or `"solana"`. An explicit `"evm"` there is rejected.

### The step format

Each step is one Solana instruction:

```jsonc
{
  "programId":     "string",   // base58 program address — a real address only, never a placeholder
  "discriminator": "string",   // OPTIONAL hex (no 0x) — the raw instruction-data prefix
  "args":          [ { "name": "amount", "type": "u64", "value": "1000000" } ],
  "accounts":      [ { "pubkey": "string", "isSigner": false, "isWritable": true } ],
  "metadata":      { "name": "…", "description": "…" }   // OPTIONAL display hints
}
```

**`discriminator`** is the byte prefix the target program dispatches on, as hex **without `0x`**. It is prepended verbatim to the encoded args. Common forms:

* An **Anchor** 8-byte discriminator (16 hex chars) = the first 8 bytes of `sha256("global:" + snake_case_instruction_name)`.
* A **1-byte SPL Token** opcode, e.g. `Transfer` = `03`, `TransferChecked` = `0c`.
* A **4-byte little-endian** System discriminant, e.g. `Transfer` = `02000000`.
* Omit it entirely for a program whose instruction data is just its args.

**`args`** are typed and Borsh-encoded. Accepted `type` values (nothing else):

| `type`                        | value form                                                            |
| ----------------------------- | --------------------------------------------------------------------- |
| `u8` `u16` `u32` `u64` `u128` | a number, or a quoted decimal string (prefer quoted for `u64`/`u128`) |
| `i8` `i16` `i32` `i64`        | a number, or a quoted decimal string                                  |
| `bool`                        | `true` / `false`                                                      |
| `pubkey`                      | base58 string (or `{INTERMEDIARY}`)                                   |
| `bytes`                       | hex string **without** `0x`, e.g. `"deadbeef"`                        |
| `string`                      | a UTF-8 string                                                        |

Encoding is strict: an over-width integer (`u8 = 256`) or a negative value for an unsigned type is rejected rather than silently wrapped, and an unknown `type` is rejected. If an instruction needs a type this set can't express (a `Vec`, an `Option`, a struct, a non-32-byte fixed array), Borsh-encode the whole instruction data yourself and send it as a single `discriminator` blob with `"args": []`.

**`accounts`** are listed **in the exact order** the instruction expects. Each carries `isSigner` and `isWritable`. There is **no `value`/lamports field** on a step — to move native SOL you emit an explicit System `Transfer` instruction.

#### The `{INTERMEDIARY}` placeholder

Use `{INTERMEDIARY}` in an account `pubkey` or a `pubkey`-typed arg value wherever your intermediary must appear — typically as the token-account owner / transfer authority / signer. The service substitutes the resolved address before encoding. Substitution touches only account pubkeys and arg values — **never** the `programId` or the `discriminator`.

There are additional placeholders for the bridge-in and out-operation flows:

| Sentinel            | Resolves to                                                        | Valid in                           |
| ------------------- | ------------------------------------------------------------------ | ---------------------------------- |
| `{INTERMEDIARY}`    | your MPC-derived ed25519 Solana account                            | all modes                          |
| `{MIN_AMOUNT_OUT}`  | the post-fee bridged amount (use as a `u64`)                       | bridge-in only                     |
| `{DEPOSIT_ADDRESS}` | the deposit address the output is sent to                          | out-operation only                 |
| `{AMOUNT_IN}`       | the origin commitment (`quote.amountIn` + network fee), as a `u64` | out-operation, `EXACT_OUTPUT` only |

### Rules to follow

* **Only your intermediary may sign.** Across all steps, the only account you may mark `"isSigner": true` is `{INTERMEDIARY}` (the service supplies the fee payer and any other required signer itself). Consequently you cannot build an instruction that needs a brand-new keypair to sign its own creation — e.g. opening a concentrated-liquidity position whose position NFT is a fresh keypair, or creating a non-associated token account. Depositing into an **existing** position is fine. Program-derived accounts (PDAs, associated token accounts) need no signer and are fine.
* **Do not add compute-budget, durable-nonce, or fee instructions.** The service injects the compute-budget and nonce instructions and appends its own fee — adding your own would duplicate them.
* **Stay within the compute budget.** The whole transaction shares a compute-unit budget (200000 by default, set by the deployment) across all your steps. Keep an action comfortably under it or split it across executions. This matters most on a bridge-in, where a transaction that reverts on-chain after signing burns the durable nonce and strands the bridged funds in the intermediary (recoverable later via a steps-only execution).
* **Stay within the size limit.** The assembled transaction must be ≤ 1232 bytes (one packet). For account-heavy actions you can pass a list of address lookup table addresses in the request to fit more accounts under the limit (see below).
* **Every step needs at least one account.** Every account pubkey must be valid base58 (or `{INTERMEDIARY}`). Every `programId` must be a real base58 address, never a placeholder.
* **A steps-only SPL action must touch its destination token** — at least one step must reference the token's mint or the intermediary's associated token account for that mint. Native-SOL destinations are exempt.
* **SPL destinations settle via a gasless relayer (Kora).** The relayer pays the SOL gas and collects the network fee as an SPL transfer in the **destination token**, debited from the intermediary. The destination mint must be on the deployment's `kora_fee_token_mints` allow-list, or create returns `400` (`"destination mint is not in the configured kora_fee_token_mints allow-list"`). Native-SOL destinations pay a SOL fee instead.

The service validates steps **before** signing, so mistakes surface as a descriptive `400` rather than a late on-chain failure. Two non-`400` create outcomes are also worth handling: `409` if an execution for this wallet is already in progress (only one in-flight execution per wallet / destination chain at a time), and `503` (`"no durable nonce account available, retry shortly"`) when the durable-nonce pool is momentarily exhausted.

### Signing and submitting

1. **Create** the execution (steps-only shown — bridge-in and out-operation use `POST /api/v1/executions/{wallet}` with a `quote`):

   ```
   POST /api/v1/executions/{wallet}/steps
   ```

   with a body carrying `version`, `type: "solana"`, the destination asset id, and your `steps`. Add `"dry": true` to preview the network fee without committing.
2. The response returns everything you need to sign under `result.details`: a `payload` to sign and a `signingStandard` — which is **your origin wallet's** standard (`erc191` for an EVM origin, `raw_ed25519` for a Solana origin, `nep413` for NEAR, `tip191` for Tron, `sep53` for Stellar, `ton_connect` for TON). You sign `details.payload` exactly as you would for any destination. You never handle the inner Solana message.
3. **Submit** the signature:

   ```
   POST /api/v1/executions/{wallet}/submit
   ```

   The submit body is `{ signature, executionId }` for an EVM or Tron origin. An ed25519 origin (Solana, NEAR, Stellar) also includes `publicKey`. A TON origin includes both `publicKey` and a `tonConnect` envelope, so its body is `{ signature, executionId, publicKey, tonConnect }`.

On a real create the returned `steps` mirror the exact transaction the service signs, so they are not identical to what you sent. They include any associated-token-account create the service prepended and the fee instruction it appended, and your `{INTERMEDIARY}` placeholders appear resolved to concrete addresses. Injected instructions render generically — their full instruction data as the `discriminator`, with empty `args`. To preview the network fee, send `dry: true`. A `dry: true` echo omits the injected compute-budget / nonce / fee instructions. Steps-only and out-operation echoes are otherwise your submitted steps verbatim, while a bridge-in echo additionally resolves `{MIN_AMOUNT_OUT}` to its post-fee value (`{INTERMEDIARY}` / `{DEPOSIT_ADDRESS}` stay as placeholders).

### How your transaction stays valid — durable nonces

A normal Solana transaction is pinned to a recent blockhash and expires once that blockhash is more than **150 blocks** old — on the order of a minute or two. This service cannot rely on that. The exact transaction bytes are fixed when you **create** the execution — your origin-wallet signature commits to them — but the transaction is not broadcast until well after: once your signature is collected, once the service's own co-signature is produced through the MPC network (a round-trip on the order of a couple of minutes), and, on a **bridge-in**, once your bridged deposit has actually arrived. That gap easily outlives a blockhash.

So the service pins every Solana transaction to a **durable nonce** instead of a recent blockhash. What this means for you:

* **Your signed transaction does not go stale.** It stays valid while the service waits for signing and (on a bridge-in) for the deposit, then broadcasts once.
* **It executes at most once.** The nonce advances the instant the transaction lands — success or revert alike — so the same bytes can never replay. This is the mechanism behind the compute-budget warning above: because a revert still advances the nonce, the service cannot re-broadcast a transaction that reverted on-chain.

#### There is still an expiration window

A durable nonce is a limited, pooled resource, so the service will not hold one open indefinitely. If an execution has not completed within a **hold window of 25 minutes** (the deployment default), measured from when you create it, the service force-fails the execution (status `OPERATION_FAILED`) and reclaims the nonce.

* **Steps-only and out-operation** broadcast as soon as you submit your signature, because the funds are already in place, so they normally finish well within the window.
* **Bridge-in** is the case to plan for: the transaction sits waiting for your bridged deposit. Treat the hold window as an effective **deadline for that deposit to arrive** — if it has not landed in time, the execution is failed and you must create a fresh one. The quote's own `deadline` can expire a bridge-in earlier, so whichever is shorter wins.

### Fitting large actions — address lookup tables

A fully-inline Solana message lists every account at 32 bytes and hits the 1232-byte single-packet limit at roughly 35 accounts. Many real actions (an aggregated swap, for example) reference more. To fit them, pass the action's **address lookup table** addresses as a top-level `addressLookupTables` array on the create body, and the service emits a **versioned (v0)** message that compresses matching accounts into lookup indices:

```jsonc
{
  "type": "solana",
  "steps": [ /* unchanged — still list every account inline */ ],
  "addressLookupTables": [ "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" ]
}
```

* **Additive.** Omit it (or send `[]`) and nothing changes — a legacy message is built exactly as before.
* **You still list every account inline** in each step. The tables only tell the service which accounts it *may* compress. You pre-compress nothing.
* **Signers and program ids stay inline** — only non-signer, non-program accounts fold into lookup indices, so lookup tables help account-heavy actions, not signer- or program-heavy ones.
* At most 16 tables per request by default (deployment-configurable). Each must be valid base58 and resolve on-chain.
* **Signing is unchanged** — a v0 message just carries a one-byte version prefix. You sign the payload the same way.

### Quick reference

**SPL Token program** `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` — common opcodes:

| Opcode | Instruction     |
| ------ | --------------- |
| `03`   | Transfer        |
| `07`   | MintTo          |
| `08`   | Burn            |
| `09`   | CloseAccount    |
| `0c`   | TransferChecked |

**System program** `11111111111111111111111111111111` — `Transfer` = discriminant `02000000`, a `u64` lamports arg, accounts `[from, to]`.

**Anchor programs** use an 8-byte discriminator = first 8 bytes of `sha256("global:" + snake_case_instruction_name)`, hex-encoded (16 chars).


# Submit signing

How to sign the message returned by `GET /api/v1/executions/{walletAddress}` and post it back to `POST /api/v1/executions/{walletAddress}/submit`, for all six signing standards: `erc191` (EVM / MetaMask), `nep413` (NEAR), `raw_ed25519` (Solana / Phantom), `sep53` (Stellar / Freighter), `ton_connect` (TON / Tonkeeper), and `tip191` (Tron / TronLink).

### TL;DR

| Chain   | `signingStandard` | What to sign                                                  | Signing op                           | Submit body fields                                    | Signature encoding              |
| ------- | ----------------- | ------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------- | ------------------------------- |
| EVM     | `erc191`          | `payload.payload_json` (string, verbatim)                     | `personal_sign` (EIP-191)            | `signature`, `executionId`                            | `secp256k1:` + bs58(`r‖s‖v0/1`) |
| NEAR    | `nep413`          | `payload.payload_json` (parsed → `{message,recipient,nonce}`) | NEP-413 `signMessage`                | `signature`, `publicKey`, `executionId`               | `ed25519:` + bs58(64 raw bytes) |
| Solana  | `raw_ed25519`     | decoded bytes of `payload.payload_bytes_base64`               | `signMessage(bytes)`                 | `signature`, `publicKey`, `executionId`               | `ed25519:` + bs58(64 raw bytes) |
| Stellar | `sep53`           | `payload.payload_json` (string, verbatim)                     | Freighter `signMessage` (SEP-53)     | `signature`, `publicKey`, `executionId`               | `ed25519:` + bs58(64 raw bytes) |
| TON     | `ton_connect`     | `payload.payload_json` (string, verbatim, as TonConnect text) | TonConnect `signData({type:'text'})` | `signature`, `publicKey`, `tonConnect`, `executionId` | `ed25519:` + bs58(64 raw bytes) |
| Tron    | `tip191`          | `payload.payload_json` (string, verbatim)                     | `signMessageV2` (TIP-191)            | `signature`, `executionId`                            | `secp256k1:` + bs58(`r‖s‖v0/1`) |

`publicKey` is **required** for NEAR/Solana/Stellar/TON and **omitted** for EVM and Tron (both secp256k1, signer recovered from the signature) — see below. TON additionally requires a `tonConnect` envelope — see the TON section.

### The exact field to sign

Starting from the execution response:

```
GET /api/v1/executions/{walletAddress}
→ result.details.payload.{ payload_json, payload_bytes_base64, standard }
```

A typical execution payload:

```json
{
  "result": {
    "details": {
      "payload": {
        "payload_json": "{\"deadline\":\"2026-05-27T13:37:19Z\",\"intents\":[...],\"signer_id\":\"...\",\"verifying_contract\":\"intents.near\"}",
        "payload_bytes_base64": "eyJkZWFkbGluZSI6...",
        "standard": "erc191"
      },
      "signingStandard": "erc191"
    }
  }
}
```

`payload_json` is the canonical source-of-truth field — always reach for `result.details.payload.payload_json`. `payload_bytes_base64` is just the same JSON pre-encoded to the exact bytes the backend will verify against; it exists so Solana clients can sidestep UTF-8 surprises (see Solana section below).

Per chain:

* **`erc191`** → sign `payload.payload_json` verbatim as a string. Do not `JSON.parse` it and then `JSON.stringify` it — that can reorder keys or change whitespace and will change the hash. Do not pre-hash with keccak256; `personal_sign` hashes for you.
* **`nep413`** → `payload.payload_json` may be a JSON string or an already-parsed object; parse if it's a string, then feed `{ message, recipient, nonce }` to the NEAR wallet's `signMessage`. The wallet builds the NEP-413 prefix tag, borsh-encodes the payload, and hashes with SHA-256 internally — do not pre-hash, do not pre-borsh.
* **`raw_ed25519`** → base64-decode `payload.payload_bytes_base64` to a `Uint8Array` and sign those bytes. Do **not** sign `payload_json` directly here: Phantom's `signMessage` accepts a `Uint8Array`, and some wallet versions UTF-8 re-encode strings in a way that does not byte-match what the backend hashes. Do not wrap in any envelope (no SIWS, no `"\x19Solana Signed Message:"`), do not pre-hash.
* **`sep53`** → sign `payload.payload_json` verbatim as a string (same field as `erc191`), passing it to Freighter's `signMessage`. Do not pre-hash and do not wrap it yourself — Freighter applies the SEP-53 framing (`"Stellar Signed Message:\n"` domain prefix + SHA-256) internally, and the backend's `sep53` verifier expects exactly that.
* **`ton_connect`** → sign `payload.payload_json` verbatim as the TonConnect **text** payload via `signData({ type: 'text', text })`. Do not pre-hash and do not wrap it — TonConnect builds the `0xffff || "ton-connect/sign-data/" … "txt" …` + SHA-256 digest internally. Unlike the others, you must also return the wallet-chosen `tonConnect` envelope (`{domain, timestamp, address}`) so the backend can rebuild that digest — see the TON section.
* **`tip191`** → sign `payload.payload_json` verbatim as a string (same field as `erc191`), passing it to TronLink's `signMessageV2`. Do not pre-hash and do not wrap it yourself — the wallet applies the TIP-191 framing (`"\x19TRON Signed Message:\n"` prefix + keccak256) internally, the Tron analog of `personal_sign`.

### EVM / `erc191`

```js
// `payload` here is `result.details.payload` from the execution response.
const payloadJSON =
  typeof payload.payload_json === 'string'
    ? payload.payload_json
    : JSON.stringify(payload.payload_json)

const sigHex = await window.ethereum.request({
  method: 'personal_sign',
  params: [payloadJSON, walletAddress],   // [message, signer]
})

// sigHex is 0x<r 32B><s 32B><v 1B>; v comes out as 0x1b (27) or 0x1c (28)
const sigBytes = Buffer.from(sigHex.slice(2), 'hex')
if (sigBytes[64] >= 27) sigBytes[64] -= 27   // normalize v to 0 / 1

const signature = 'secp256k1:' + bs58.encode(sigBytes)
```

Three encoding details that matter:

1. **Argument order**: `[message, address]`.
2. **`v` normalization**: only byte 64 of the 65-byte signature is touched (`27 → 0`, `28 → 1`). Do not edit `r` or `s`. Do not re-add `27` later.
3. **bs58, not base64**: the NEAR intents verifier expects base58 with the `secp256k1:` prefix.

Submit body:

```http
POST /api/v1/executions/{walletAddress}/submit
Content-Type: application/json

{
  "signature": "secp256k1:ASzCLKN2HFa…",
  "executionId": "e5abf7ce-1187-4562-b2a7-c52d6560f327"
}
```

**Do not include `publicKey`.** The backend runs `ecrecover` on the signature + the `payload_json` it has on file and compares the recovered address to the execution's `signer_id` (lowercase `0x…` for erc191).

### NEAR / `nep413`

```js
const nearWallet = nearWalletRef.current
if (!nearWallet) throw new Error('NEAR wallet not connected. Please reconnect.')

const nep413Payload =
  typeof payload.payload_json === 'string'
    ? JSON.parse(payload.payload_json)
    : payload.payload_json

const nonceBytes = Uint8Array.from(
  atob(nep413Payload.nonce),
  (c) => c.charCodeAt(0),
)

const result = await nearWallet.signMessage({
  message:   nep413Payload.message,
  recipient: nep413Payload.recipient,
  nonce:     nonceBytes,
})

const publicKey = result.publicKey.startsWith('ed25519:')
  ? result.publicKey
  : 'ed25519:' + result.publicKey

// HOT returns the signature as base64 (per NEP-413), but the backend wants
// ed25519:<base58>. Re-encode here.
const sigBytes =
  typeof result.signature === 'string'
    ? result.signature.startsWith('ed25519:')
      ? null
      : Buffer.from(result.signature, 'base64')
    : Buffer.from(result.signature)

const signature =
  sigBytes === null ? result.signature : 'ed25519:' + bs58.encode(sigBytes)

return { signature, publicKey }
```

Four encoding details that matter:

1. **Nonce is base64 → `Uint8Array`.** Don't pass the base64 string to the wallet. The wallet expects 32 raw bytes; if it receives the string, the borsh-encoded payload diverges from what the backend verifies.
2. **Signature re-encoding.** NEP-413 wallets return base64. The backend verifier wants base58 with the `ed25519:` prefix. Decode base64 → bytes → bs58 → prefix.
3. **Public key prefix.** Wallets disagree about whether to include `ed25519:`. Detect and add it if missing — never add it twice.
4. **No envelope on the message.** Don't prepend `"NEAR Signed Message:"` or anything similar. NEP-413's framing is what the wallet adds internally; doing it yourself double-wraps.

Submit body:

```http
POST /api/v1/executions/{walletAddress}/submit
Content-Type: application/json

{
  "signature":   "ed25519:3N2B…",
  "publicKey":   "ed25519:ED7S…",
  "executionId": "e5abf7ce-1187-4562-b2a7-c52d6560f327"
}
```

Three fields only. `nonce`, `recipient`, `callbackUrl`, `state`, `accountId` are **not** sent — the backend already has them from the execution it issued and only needs the signature material to verify.

### Solana / `raw_ed25519`

```js
// `payload` here is `result.details.payload` from the execution response.
const payloadBytes = Uint8Array.from(
  atob(payload.payload_bytes_base64),
  (c) => c.charCodeAt(0),
)

const { signature: sigBytes } = await window.solana.signMessage(payloadBytes)
const publicKey = window.solana.publicKey.toBase58()

return {
  signature: 'ed25519:' + bs58.encode(Buffer.from(sigBytes)),
  publicKey: 'ed25519:' + publicKey,
}
```

Three encoding details that matter:

1. **Sign bytes, not a string.** `signMessage` accepts a `Uint8Array`; pass the decoded buffer directly.
2. **Signature is base58, not base64.** Phantom returns 64 raw bytes (`R‖S`). Encode with `bs58`, then prefix with `ed25519:`.
3. **Public key**: Phantom returns it in Solana's native base58 via `publicKey.toBase58()`. Just prefix with `ed25519:` — no further encoding. There is no `v` byte and no normalization step (that's an ERC-191 / secp256k1 thing).

Submit body:

```http
POST /api/v1/executions/{walletAddress}/submit
Content-Type: application/json

{
  "signature":   "ed25519:5QXh…",
  "publicKey":   "ed25519:7XSf…",
  "executionId": "e5abf7ce-1187-4562-b2a7-c52d6560f327"
}
```

The backend uses `publicKey` as both the verification key and — base58-decoded — as the `signer_id` it compares against the execution.

### Stellar / `sep53`

```js
// `payload` here is `result.details.payload` from the execution response.
// `walletAddress` is the connected Stellar account (G… StrKey).
const payloadJSON =
  typeof payload.payload_json === 'string'
    ? payload.payload_json
    : JSON.stringify(payload.payload_json)

const result = await freighter.signMessage(payloadJSON, { address: walletAddress })
if (result?.error) {
  throw new Error(result.error?.message ?? 'Freighter signMessage failed')
}

const { signedMessage, signerAddress } = result
const sigBytes =
  signedMessage instanceof Uint8Array
    ? Buffer.from(signedMessage)
    : Buffer.from(signedMessage, 'base64')
if (sigBytes.length !== 64) {
  throw new Error(`SEP-53 signature must be 64 bytes, got ${sigBytes.length}`)
}

const pubkeyBytes = StrKey.decodeEd25519PublicKey(signerAddress ?? walletAddress)

return {
  signature: 'ed25519:' + bs58.encode(sigBytes),
  publicKey: 'ed25519:' + bs58.encode(Buffer.from(pubkeyBytes)),
}
```

Four encoding details that matter:

1. **Sign the `payload_json` string** (like `erc191`). Don't pre-hash and don't wrap it yourself — Freighter applies the SEP-53 framing (`"Stellar Signed Message:\n"` prefix + SHA-256) internally, and the backend's `sep53` verifier expects exactly that.
2. **`signedMessage` may be base64 or a `Uint8Array`.** Freighter versions disagree; normalize to a 64-byte `R‖S` buffer and assert the length — a different length means the wallet returned something other than a raw Ed25519 signature.
3. **Signature is base58, not base64.** Encode the 64 bytes with `bs58`, then prefix with `ed25519:` (same as NEAR / Solana).
4. **Public key needs `StrKey` decoding first.** The Stellar address is a `G…` StrKey string, **not** raw base58. Decode it with `StrKey.decodeEd25519PublicKey()` to 32 raw bytes, then bs58-encode and prefix with `ed25519:`. Do **not** bs58 the `G…` string directly (unlike Solana, where `publicKey.toBase58()` is already the right base58).

Submit body:

```http
POST /api/v1/executions/{walletAddress}/submit
Content-Type: application/json

{
  "signature":   "ed25519:5QXh…",
  "publicKey":   "ed25519:7XSf…",
  "executionId": "e5abf7ce-1187-4562-b2a7-c52d6560f327"
}
```

Same three fields as NEAR/Solana. `publicKey` is **required** — Ed25519 cannot be recovered from the signature alone. `{walletAddress}` in the URL is the Stellar `G…` account from Freighter `requestAccess().address`.

#### Stellar deposit (memo required)

Separate from signing the `/submit` message: a Stellar deposit also needs a **memo**, and it must appear in **two** places. Stellar 1Click uses a *shared* deposit address, so the per-quote memo is the only thing that attributes the incoming funds to your quote — omit it on either step and the deposit is lost.

```js
// The quote (POST /api/v1/executions/{walletAddress}) returns these for Stellar:
const { depositAddress, depositMemo } = executionResult.quote

// 1) The on-chain payment MUST carry the memo (MEMO_TEXT, ≤ 28 bytes).
const builder = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase })
  .addOperation(Operation.payment({ destination: depositAddress, asset, amount }))
if (depositMemo) builder.addMemo(Memo.text(depositMemo))
const tx = builder.setTimeout(180).build()
// …Freighter signTransaction → Horizon submitTransaction → { hash: txHash }

// 2) Record the deposit WITH the same memo.
await axios.post(`${API_URL}/api/v1/executions/deposit/submit`, {
  txHash,
  depositAddress,
  ...(depositMemo ? { memo: depositMemo } : {}),
})
```

### TON / `ton_connect`

```js
// `payload` here is `result.details.payload` from the execution response.
// The frontend dispatches on the standard, then signs via TonConnect.
const text =
  typeof payload.payload_json === 'string'
    ? payload.payload_json
    : JSON.stringify(payload.payload_json)

// signTonData(text):
const ui = getTonConnectUI()
const account = ui.account
if (!account?.publicKey) {
  throw new Error('TON wallet not connected. Please reconnect.')
}

const result = await ui.signData({ type: 'text', text })
const sigBytes = Buffer.from(result.signature, 'base64')
if (sigBytes.length !== 64) {
  throw new Error(`TON signature must be 64 bytes, got ${sigBytes.length}`)
}

return {
  signature: 'ed25519:' + bs58.encode(sigBytes),
  publicKey: 'ed25519:' + bs58.encode(Buffer.from(account.publicKey, 'hex')),
  tonConnect: {
    domain: result.domain,
    timestamp: Number(result.timestamp),
    address: result.address,
  },
}
```

Four encoding details that matter:

1. **Use `signData` (text variant), not `sendTransaction`.** This is an off-chain message signature, not an on-chain transaction. The wallet builds the `0xffff || "ton-connect/sign-data/" … "txt" …` + SHA-256 digest internally — do not pre-hash, do not wrap the text.
2. **Signature is base64 from the wallet → base58 on the wire.** TonConnect returns base64; decode to bytes, assert 64 (`R‖S`), `bs58`-encode, then prefix `ed25519:` (same as NEAR / Solana / Stellar).
3. **`publicKey` is from connect time and mandatory.** A TON address doesn't contain the key, so it can't be derived from the address or recovered from the signature. TonConnect exposes `account.publicKey` (hex) at connect; `bs58`-encode and prefix `ed25519:`. Use a wallet that returns the account public key (e.g. Tonkeeper).
4. **The `tonConnect` envelope must be sent.** The wallet folds `domain`, `timestamp`, and `address` into the signed digest and picks them at signing time, so the backend rebuilds the digest from these wire values plus its stored copy of the payload. No other chain sends this.

Submit body:

```http
POST /api/v1/executions/{walletAddress}/submit
Content-Type: application/json

{
  "signature":   "ed25519:5QXh…",
  "publicKey":   "ed25519:7XSf…",
  "tonConnect":  { "domain": "…", "timestamp": 1716817039, "address": "UQ…" },
  "executionId": "e5abf7ce-1187-4562-b2a7-c52d6560f327"
}
```

`publicKey` and `tonConnect` are both **required**. Omitting the envelope returns `400 {"error": "tonConnect {domain, timestamp, address} is required for TON"}`. `{walletAddress}` is the URL-safe non-bounceable account (`UQ…`). Full per-chain TON guide: `ton-signing.md`. Backend rationale: `../ton/ton-public-key.md` and `../ton/ton-signature-verification.md`.

### Tron / `tip191`

```js
// `payload` here is `result.details.payload` from the execution response.
const payloadJSON =
  typeof payload.payload_json === 'string'
    ? payload.payload_json
    : JSON.stringify(payload.payload_json)

// signMessageV2 applies "\x19TRON Signed Message:\n" + len + msg + keccak256
const sigHex = await window.tronWeb.trx.signMessageV2(payloadJSON)

// sigHex is 0x<r 32B><s 32B><v 1B>; v comes out as 0x1b (27) or 0x1c (28)
const sigBytes = Buffer.from(sigHex.replace(/^0x/, ''), 'hex')
if (sigBytes[64] >= 27) sigBytes[64] -= 27   // normalize v to 0 / 1

const signature = 'secp256k1:' + bs58.encode(sigBytes)
```

Three encoding details that matter:

1. **Use `signMessageV2`, not the legacy `sign`/`signMessage`.** Only V2 implements TIP-191 (the `\x19TRON Signed Message:\n` prefix the backend verifies); the older calls use a different, incompatible scheme.
2. **`v` normalization**: only byte 64 of the 65-byte signature is touched (`27 → 0`, `28 → 1`). Do not edit `r` or `s`. Identical to `erc191`.
3. **bs58, not base64**: base58 with the `secp256k1:` prefix.

Submit body:

```http
POST /api/v1/executions/{walletAddress}/submit
Content-Type: application/json

{
  "signature": "secp256k1:ASzCLKN2HFa…",
  "executionId": "e5abf7ce-1187-4562-b2a7-c52d6560f327"
}
```

**Do not include `publicKey`.** The backend recovers the secp256k1 signer, converts the wallet's base58 `T…` address to its embedded 20-byte account, and compares. Full per-chain guide: `tron-signing.md`.

### Why `publicKey` is required for NEAR/Solana/Stellar/TON but not EVM/Tron

Ed25519 (NEP-413, `raw_ed25519`, `sep53`, and `ton_connect`) does not let the verifier recover the public key from the signature alone — the backend needs `publicKey` to verify and to derive the `signer_id`.

secp256k1 (`erc191` and `tip191`) does: `ecrecover` reconstructs the address from the signature + message, so the field is omitted on the wire. Tron addresses embed the same 20-byte account as the recovered EVM address, so the recovered signer maps straight onto the base58 `T…` wallet.

**TON is a further special case.** Its address is `hash(StateInit(code, pubkey))`, so the key isn't even contained in the address (unlike a Solana base58 address or a Stellar `G…` StrKey, where the key can be read off the address). The frontend supplies the connect-time key, and the backend additionally proves that key **owns** the claimed wallet by re-deriving the address from it across every known wallet version — see `../ton/ton-public-key.md`.

### HTTP shape

```http
POST /api/v1/executions/{walletAddress}/submit
Content-Type: application/json
```

`{walletAddress}` format per chain:

* **Solana** — base58 (e.g. `7XSfQk…`), the same string `window.solana.publicKey.toBase58()` returns.
* **NEAR** — named account (`alice.near`) or 64-char hex implicit account, the same string returned by `wallet.getAccounts()[0].accountId`.
* **EVM** — `0x…` (40 hex chars, case-tolerant).
* **Stellar** — `G…` StrKey ed25519 address, the same string Freighter `requestAccess()` returns.
* **TON** — URL-safe non-bounceable user-friendly address (`UQ…`), the string `Address.parse(account.address).toString({ urlSafe: true, bounceable: false })` returns. The bounceable (`EQ…`) and raw (`<workchain>:<hex>`) forms are also accepted — the backend matches on `(workchain, accountHash)`, not the raw string.
* **Tron** — base58 `T…` address (e.g. `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`), the string `window.tronWeb.defaultAddress.base58` returns.


# Delete execution

## `DELETE /executions/{wallet}/{executionId}`

Delete a non-final execution by signing the literal string `delete_execution:{executionId}` with the wallet that owns it.

### TL;DR

1. **Message to sign**: the ASCII string `delete_execution:` + the execution ID. No JSON, no envelope, no hashing.
2. **Signing op**: `signMessage` (Solana / NEAR), `personal_sign` (EVM), `signMessageV2` (Tron), or TonConnect `signData({type:'text'})` (TON).
3. **HTTP**: `DELETE /api/v1/executions/{walletAddress}/{executionId}`, JSON body. Per-chain shape:

| Chain  | Signing standard | Body fields                                                      |
| ------ | ---------------- | ---------------------------------------------------------------- |
| Solana | `raw_ed25519`    | `signature`, `publicKey`                                         |
| NEAR   | `nep413`         | `signature`, `publicKey`, `nep413{recipient,nonce}`              |
| EVM    | `erc191`         | `signature`                                                      |
| Tron   | `tip191`         | `signature`                                                      |
| TON    | `ton_connect`    | `signature`, `publicKey`, `tonConnect{domain,timestamp,address}` |

EVM and Tron omit `publicKey` because both are secp256k1 — the backend recovers the address from the signature via `ecrecover`. Solana, NEAR, and TON must send it — Ed25519 doesn't let the verifier recover the key from the signature. TON additionally sends a `tonConnect` envelope (the wallet folds its `domain`/`timestamp`/`address` into the signed digest).

### The message

```
delete_execution:e5abf7ce-1187-4562-b2a7-c52d6560f327
```

Build it client-side from the execution ID — there is no GET to fetch it. The backend rebuilds the same string from the URL path and verifies your signature against it.

Do **not** wrap in JSON, do **not** hash, do **not** prepend any chain envelope. The wallet adds whatever framing its standard requires (`"\x19Ethereum Signed Message:"`, NEP-413 borsh, etc.) — that's the wallet's job.

### Solana / `raw_ed25519`

Sign the raw bytes of the message with Phantom (or any Solana wallet exposing `signMessage`).

```js
const message = `delete_execution:${executionId}`
const messageBytes = new TextEncoder().encode(message)

const { signature: sigBytes } = await window.solana.signMessage(messageBytes)
const publicKey = window.solana.publicKey.toBase58()

const body = {
  signature: 'ed25519:' + bs58.encode(Buffer.from(sigBytes)),
  publicKey: 'ed25519:' + publicKey,
}
```

Three encoding details that matter:

1. **Sign bytes, not a string.** `signMessage` expects a `Uint8Array`. Passing the JS string may UTF-8-re-encode in a way that doesn't match what the backend hashes.
2. **Signature is base58, not base64.** Phantom returns 64 raw bytes (`R‖S`). Encode with `bs58`, then prefix with `ed25519:`.
3. **Public key**: Phantom returns it in Solana's native base58 via `publicKey.toBase58()`. Just prefix with `ed25519:` — no further encoding.

Example submit body:

```json
{
  "signature": "ed25519:NGiuLjC6WMTaeWyDRwZ8d9uvqykw3E4Y26PvkMFapnvWP4P3fjpgJn93fjdqtj4XSkYKtAGqtqaHYvRJu3rFcYJ",
  "publicKey": "ed25519:4nn959rPTCxboxXKUxZwMq4knJMKPURA4WciuJyrDAvQ"
}
```

`{walletAddress}` in the URL is the base58 Solana account — the same string `window.solana.publicKey.toBase58()` returns.

### NEAR / `nep413`

Sign a NEP-413 payload with a NEAR wallet (HOT, MyNearWallet, Meteor, etc. — anything exposing `signMessage`), then **also** send the `nonce` and `recipient` in a top-level `nep413` block.

```js
const message = `delete_execution:${executionId}`
const recipient = 'intents.near'

const nonceBytes = crypto.getRandomValues(new Uint8Array(32))
const nonceB64 = btoa(String.fromCharCode(...nonceBytes))

const result = await nearWallet.signMessage({
  message,
  recipient,
  nonce: nonceBytes,            // 32 raw bytes, not the base64 string
})

const publicKey = result.publicKey.startsWith('ed25519:')
  ? result.publicKey
  : 'ed25519:' + result.publicKey

// Wallets return the signature as base64 per NEP-413; backend wants
// ed25519:<base58>. Re-encode unless the wallet already prefixed it.
const signature = result.signature.startsWith('ed25519:')
  ? result.signature
  : 'ed25519:' + bs58.encode(Buffer.from(result.signature, 'base64'))

const body = {
  signature,
  publicKey,
  nep413: { recipient, nonce: nonceB64 },
}
```

Four encoding details that matter:

1. **Nonce is 32 raw bytes for the wallet, base64 on the wire.** The wallet expects 32 bytes; the JSON body carries the same nonce base64-encoded.
2. **Recipient must be exactly `intents.near`.** It is mixed into the signed digest server-side.
3. **Signature re-encoding.** Wallets return base64 per NEP-413; the backend verifier wants base58 with `ed25519:`.
4. **Public key prefix.** Wallets disagree about whether to include `ed25519:`. Detect and add it if missing — never add it twice.

Example submit body:

```json
{
  "signature": "ed25519:5DDM4CJkDP66xEciWmC5cZvb4dzHieSvBrpuQiDcutSBubWcnjeDFmyhrtm3C5puTJY5BTsJwYfSgRMjBrveijJt",
  "publicKey": "ed25519:ASn2sWxWc1KC8qvNsrTcFmrRTrbtwSPkmm46mPvL9crw",
  "nep413": {
    "recipient": "intents.near",
    "nonce": "mxSIcq3Pj9slgPeQYZpY87p4IwV6lAjhEXbxGEnWF+g="
  }
}
```

#### Why send `nep413.{recipient, nonce}` separately?

They're inside the signed payload — why repeat them on the wire?

Because the backend has to **reconstruct the exact bytes the wallet signed** in order to verify the signature, and NEP-413 mixes `recipient` and `nonce` into those bytes (borsh-encoded with the message, then SHA-256-hashed). The signed payload itself isn't sent as a single blob — the wire format splits it into `signature` + `publicKey` + the `nep413` fields, and the server reassembles them.

`nep413` is therefore **mandatory** for NEAR. If you omit it the handler returns `400 {"error": "nep413 fields are required for NEAR"}`. There is no raw-ed25519 path for NEAR — in practice NEAR wallets don't expose raw byte signing anyway, only `signMessage`.

`{walletAddress}` in the URL is either the named account (`alice.near`) or the 64-char hex implicit account.

### TON / `ton_connect`

Sign the literal message via TonConnect's `signData` text payload, then send the signature, the public key, and the wallet-chosen `tonConnect` envelope.

```js
const message = `delete_execution:${executionId}`

// signTonData(message):
const ui = getTonConnectUI()
const account = ui.account
if (!account?.publicKey) {
  throw new Error('TON wallet not connected. Please reconnect.')
}

const result = await ui.signData({ type: 'text', text: message })
const sigBytes = Buffer.from(result.signature, 'base64')
if (sigBytes.length !== 64) {
  throw new Error(`TON signature must be 64 bytes, got ${sigBytes.length}`)
}

const body = {
  signature: 'ed25519:' + bs58.encode(sigBytes),
  publicKey: 'ed25519:' + bs58.encode(Buffer.from(account.publicKey, 'hex')),
  tonConnect: {
    domain: result.domain,
    timestamp: Number(result.timestamp),
    address: result.address,
  },
}
```

Four encoding details that matter:

1. **Use `signData` (text variant), not `sendTransaction`.** This is an off-chain message signature. The wallet builds the `0xffff || "ton-connect/sign-data/" … "txt" …` + SHA-256 digest internally — do not pre-hash, do not wrap.
2. **Signature is base64 from the wallet → base58 on the wire.** Decode to bytes, assert 64 (`R‖S`), `bs58`-encode, prefix `ed25519:`.
3. **`publicKey` is from connect time and mandatory.** A TON address doesn't contain the key — TonConnect exposes `account.publicKey` (hex); `bs58`-encode and prefix `ed25519:`. Use a wallet that returns it (e.g. Tonkeeper).
4. **The `tonConnect` envelope must be sent.** The wallet folds `domain`/`timestamp`/`address` into the signed digest at signing time, so the backend rebuilds the digest from these wire values plus the `delete_execution:<id>` string it reconstructs from the URL.

Example submit body:

```json
{
  "signature": "ed25519:NGiuLjC6WMTaeWyDRwZ8d9uvqykw3E4Y26PvkMFapnvWP4P3fjpgJn93fjdqtj4XSkYKtAGqtqaHYvRJu3rFcYJ",
  "publicKey": "ed25519:4nn959rPTCxboxXKUxZwMq4knJMKPURA4WciuJyrDAvQ",
  "tonConnect": { "domain": "intents.aurora.dev", "timestamp": 1716817039, "address": "UQ…" }
}
```

`publicKey` and `tonConnect` are both required — omit `publicKey` → `400 {"error": "publicKey is required"}`; omit the envelope → `400 {"error": "tonConnect fields are required for TON"}`. Unlike the `/submit` path there's no `signer_id` or deadline check here — there is no stored intent, just the literal message. See `../ton/ton-signature-verification.md`.

`{walletAddress}` in the URL is the URL-safe non-bounceable account (`UQ…`); the bounceable (`EQ…`) and raw (`<workchain>:<hex>`) forms are also accepted.

### EVM / `erc191`

Sign the message as a UTF-8 string with MetaMask `personal_sign` (or any EVM wallet exposing the EIP-191 / `personal_sign` RPC).

```js
const message = `delete_execution:${executionId}`

const sigHex = await window.ethereum.request({
  method: 'personal_sign',
  params: [message, walletAddress],   // [message, signer]
})

// sigHex is 0x<r 32B><s 32B><v 1B>; v comes out as 0x1b (27) or 0x1c (28)
const sigBytes = Buffer.from(sigHex.slice(2), 'hex')
if (sigBytes[64] >= 27) sigBytes[64] -= 27   // normalize v to 0 / 1

const body = {
  signature: 'secp256k1:' + bs58.encode(sigBytes),
}
```

Three encoding details that matter:

1. **Argument order**: `[message, address]`.
2. **`v` normalization**: only byte 64 of the 65-byte signature is touched (`27 → 0`, `28 → 1`). Do not edit `r` or `s`. Do not re-add `27` later.
3. **bs58, not base64**: the backend expects base58 with the `secp256k1:` prefix.

Example submit body:

```json
{
  "signature": "secp256k1:8oQEEa4JNNpuhHrRiTZjgdWsdSEey3s2aaUn6aaHi57QQRAFXGsjuCep2gZaVVNLQ7W94FqekZ8n5Ux3dMK9KhWmN"
}
```

**No `publicKey`.** The backend runs `ecrecover` on `"delete_execution:" + executionId` and the signature, then compares the recovered address to `{walletAddress}` in the URL (case-tolerant).

### Tron / `tip191`

Sign the literal message with TronLink `signMessageV2` (TIP-191) — the Tron analog of `personal_sign`. Tron is the secp256k1 twin of EVM: same wire shape as `erc191`, only the standard label and wallet prefix differ.

```js
const message = `delete_execution:${executionId}`

// signMessageV2 applies "\x19TRON Signed Message:\n" + len + msg keccak
// internally — do not pre-hash, do not wrap.
const sigHex = await window.tronWeb.trx.signMessageV2(message)

// sigHex is 0x<r 32B><s 32B><v 1B>; v comes out as 0x1b (27) or 0x1c (28)
const sigBytes = Buffer.from(sigHex.replace(/^0x/, ''), 'hex')
if (sigBytes[64] >= 27) sigBytes[64] -= 27   // normalize v to 0 / 1

const body = {
  signature: 'secp256k1:' + bs58.encode(sigBytes),
}
```

Three encoding details that matter:

1. **Use `signMessageV2`, not the legacy `sign`/`signMessage`.** Only V2 implements TIP-191 (the `\x19TRON Signed Message:\n` prefix the backend verifies against); the older calls use a different, incompatible scheme.
2. **`v` normalization**: only byte 64 of the 65-byte signature is touched (`27 → 0`, `28 → 1`). Do not edit `r` or `s`. Do not re-add `27` later. Identical to the ERC-191 path.
3. **bs58, not base64**: the backend expects base58 with the `secp256k1:` prefix.

Example submit body:

```json
{
  "signature": "secp256k1:8oQEEa4JNNpuhHrRiTZjgdWsdSEey3s2aaUn6aaHi57QQRAFXGsjuCep2gZaVVNLQ7W94FqekZ8n5Ux3dMK9KhWmN"
}
```

**No `publicKey`.** Like EVM, the backend recovers the secp256k1 signer from `"delete_execution:" + executionId` and the signature, then parses the wallet's base58 `T…` address to its embedded 20-byte account and compares — that account is the same 20 bytes an EVM address carries.

### HTTP shape

```http
DELETE /api/v1/executions/{walletAddress}/{executionId}
Content-Type: application/json
```

The body is sent in the request body. With `axios.delete` specifically, that means the `data:` option — not the second positional argument:

```js
await axios.delete(
  `${API_URL}/api/v1/executions/${walletAddress}/${encodeURIComponent(executionId)}`,
  { data: body },
)
```

`{walletAddress}` format per chain:

* Solana: base58 (e.g. `7XSfQk…`)
* NEAR: named account (`alice.near`) or 64-char hex implicit
* EVM: `0x…` (40 hex chars, case-tolerant)
* Tron: base58 `T…` (e.g. `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`)
* TON: URL-safe non-bounceable user-friendly address (`UQ…`); `EQ…` and raw `<workchain>:<hex>` also accepted

### Preconditions and responses

**Deletable statuses**: `CREATED`, `DEPOSIT_PENDING`, `OPERATION_PENDING`, `EXPIRED`, `DEPOSIT_FAILED`, `OPERATION_FAILED`.

`SUCCESS` is **not** deletable — successful swaps stay in the user's history.

**Response codes**

| Code | When                                            | Body                                |
| ---- | ----------------------------------------------- | ----------------------------------- |
| 200  | Deleted                                         | `{"result": {"status": "DELETED"}}` |
| 400  | Bad signature, missing field, unsupported chain | `{"error": "..."}`                  |
| 404  | Execution not found for this wallet             | `{"error": "..."}`                  |
| 409  | Execution is in a non-deletable status          | `{"error": "..."}`                  |


# What are Intents Deposits?

Deposit Addresses are a deposit primitive built on NEAR Intents. Generate a deposit address for the user - the system handles chain detection, routing, asset conversion, and settlement.

<figure><img src="/files/EmX1NHzn3kThhrUWcV1s" alt="" width="375"><figcaption></figcaption></figure>

The user performs a standard token transfer to the address. Everything after that — monitoring, routing via NEAR Intents, optional swap, and crediting the destination — is handled on the infrastructure side with no further input required from the user or the integrating application.

## Features

### Cross-chain routing

Deposits from any supported source chain are automatically detected and routed. The integrating application does not need to handle per-chain logic or monitor incoming transactions.

### Non-EVM Support

Source chains include EVM networks and non-EVM chains: Tron, Bitcoin, Zcash, Solana, TON, and NEAR. See [Supported Chains](/intents-deposits/supported-chains) for the full list.

### Permissionless

No allowlist, no approval process. API keys are issued immediately. Start generating addresses and testing end-to-end flows without contacting the team.

### Webhooks (coming soon)

Stay informed about transaction status updates with webhooks. Set up URL endpoints to receive event notifications directly from the system, allowing seamless integration into your existing workflows.

### Persistent addresses (coming soon)

Each address is permanent and reusable. There is no TTL, no session, and no requirement to re-generate between deposits. The same address accepts unlimited sequential deposits. Read more about [Persistent Addresses](/intents-deposits/persistent-addresses).

## Next Steps

{% stepper %}
{% step %}

### Create API key

First, create an account and set up [API Keys & Fees](/intents-swap/api-keys-and-fees) for your integration.
{% endstep %}

{% step %}

### Decide on the integration path

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td><a data-mention href="/pages/j4WqMPv9KnwlUMXYRwU2">/pages/j4WqMPv9KnwlUMXYRwU2</a></td><td data-object-fit="contain"><a href="/files/EmX1NHzn3kThhrUWcV1s">/files/EmX1NHzn3kThhrUWcV1s</a></td></tr><tr><td><a data-mention href="/pages/oybFu9M8SthLm2bTMW5w">/pages/oybFu9M8SthLm2bTMW5w</a></td><td><a href="https://images.unsplash.com/photo-1594904351111-a072f80b1a71?crop=entropy&#x26;cs=srgb&#x26;fm=jpg&#x26;ixid=M3wxOTcwMjR8MHwxfHNlYXJjaHwzfHxhcGl8ZW58MHx8fHwxNzc2OTI1NjMwfDA&#x26;ixlib=rb-4.1.0&#x26;q=85">https://images.unsplash.com/photo-1594904351111-a072f80b1a71?crop=entropy&#x26;cs=srgb&#x26;fm=jpg&#x26;ixid=M3wxOTcwMjR8MHwxfHNlYXJjaHwzfHxhcGl8ZW58MHx8fHwxNzc2OTI1NjMwfDA&#x26;ixlib=rb-4.1.0&#x26;q=85</a></td></tr></tbody></table>

{% endstep %}
{% endstepper %}

**Questions?** [Talk to the team](mailto:contact@aurora.dev)


# Quickstart


# Widget integration

{% hint style="info" %}

### Try it out in the Studio

You can check out the Deposit Widget in the [**Intents Widget Studio**](https://studio.aurora.dev/?configId=8cb195f4-f1ae-45cb-9726-43830954d029). It uses [USDC on Base](https://basescan.org/token/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913?a=0x195954FBa43C1A4266F5A66B4Fe468f459Fd0611) as the destination asset and our [test EVM account](https://basescan.org/address/0x195954FBa43C1A4266F5A66B4Fe468f459Fd0611) as the recipient.
{% endhint %}

## Step by step

You can embed a ready-to-use **Deposit Widget** directly into your app using an **iframe** or a **React component**.

{% stepper %}
{% step %}

### Create Intents Studio account

Navigate to [Widget Studio](https://studio.aurora.dev/) and log in with your email using the button in the top-right corner of the interface.
{% endstep %}

{% step %}

### Configure the widget

Make sure to use Deposit Widget mode, select the **Destination asset**, and enter the **Receiver address**. Both need to be compatible.

In the UI, you can configure settings such as the displayed Networks and Tokens, and how the Wallet Connection is handled.
{% endstep %}

{% step %}

### Configure the design

You can also customise the design to match your branding with different Styles, Accents, or Layouts. Reach out to our team for more advanced styling options.
{% endstep %}

{% step %}

### Embed the widget

Click the **Embed in your app** button on the top right of the interface, either through:

* An iframe by using **Generate a new link to embed** section
* Or React component using **Use React code snippet**
  {% endstep %}

{% step %}

### (Optional) Use advanced settings of the widget

If you decide to embed a React component, you can use advanced settings, such as your own wallet connection or use widget hooks. Check the detailed docs on the [Widget Configuration](/intents-swap/widget-configuration).
{% endstep %}
{% endstepper %}


# API integration

Intents API allows you to perform cross-chain deposits across all [Supported Chains](/intents-deposits/supported-chains).

You'll need to reference supported assets by their IDs, then request a quote that generates a deposited address to which the requested funds need to be transferred. After that, the swap will be processed, and the destination asset will land in the recipient's account.

{% hint style="info" %}
You need to [create an API key](/intents-swap/api-keys-and-fees) to interact with the API.
{% endhint %}

{% stepper %}
{% step %}

### Get supported tokens

Use [Get supported tokens](/api-reference/swap-api-reference/get-supported-tokens) to find the `assetId` values you will need.

{% tabs %}
{% tab title="cURL" %}

```sh
curl "https://intents-api.aurora.dev/api/tokens/${appKey}"
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-api.aurora.dev/api/tokens/${appKey}`);
const tokens = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Explanation</summary>

The response includes tokens with their `assetId` in this format:

* NEAR tokens: `nep141:wrap.near`
* Bridged tokens: `nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near`

```json
[
  {
    "assetId": "nep141:wrap.near",
    "decimals": 24,
    "blockchain": "near",
    "symbol": "wNEAR",
    "price": 1.1,
    "priceUpdatedAt": "2026-02-27T15:18:30.437Z",
    "contractAddress": "wrap.near"
  },
  {
    "assetId": "nep141:eth.omft.near",
    "decimals": 18,
    "blockchain": "eth",
    "symbol": "ETH",
    "price": 1947.28,
    "priceUpdatedAt": "2026-02-27T15:25:30.527Z",
    "contractAddress": null
  },
  {
    "assetId": "nep141:btc.omft.near",
    "decimals": 8,
    "blockchain": "btc",
    "symbol": "BTC",
    "price": 66093,
    "priceUpdatedAt": "2026-02-27T15:25:30.527Z",
    "contractAddress": null
  }
]
```

</details>
{% endstep %}

{% step %}

### Request a quote

Use [Request a quote](/api-reference/swap-api-reference/request-a-quote) with your desired parameters

{% tabs %}
{% tab title="cURL" %}

```sh
curl -X POST "https://intents-api.aurora.dev/api/tokens/${appKey}" \
  -H "Content-Type: application/json" \
  -d '{
    "dry": false,
    "swapType": "EXACT_INPUT",
    "slippageTolerance": 100,
    "originAsset": "nep141:wrap.near",
    "depositType": "ORIGIN_CHAIN",
    "destinationAsset": "nep141:arb-0x912ce59144191c1204e64559fe8253a0e49e6548.omft.near",
    "amount": "100000000000000000000000",
    "recipient": "0xYourArbitrumAddress",
    "recipientType": "DESTINATION_CHAIN",
    "refundTo": "your-account.near",
    "refundType": "ORIGIN_CHAIN",
    "deadline": "2025-01-01T00:00:00.000Z"
  }'
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const quote = await fetch(`https://intents-api.aurora.dev/api/tokens/${appKey}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    dry: false,
    swapType: 'EXACT_INPUT',
    slippageTolerance: 100,
    originAsset: 'nep141:wrap.near',
    depositType: 'ORIGIN_CHAIN',
    destinationAsset: 'nep141:arb-0x912ce59144191c1204e64559fe8253a0e49e6548.omft.near',
    amount: '100000000000000000000000',
    recipient: '0xYourArbitrumAddress',
    recipientType: 'DESTINATION_CHAIN',
    refundTo: 'your-account.near',
    refundType: 'ORIGIN_CHAIN',
    deadline: new Date(Date.now() + 3 * 60 * 1000).toISOString()
  })
});
const result = await quote.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Key parameters</summary>

| Parameter           | Description                                                                          |
| ------------------- | ------------------------------------------------------------------------------------ |
| `dry`               | `true` to validate parameters and **get a quote without executing the swap**         |
| `swapType`          | `EXACT_INPUT` (specify input amount) or `EXACT_OUTPUT` (specify output amount)       |
| `slippageTolerance` | Maximum acceptable slippage in basis points (100 = 1%)                               |
| `originAsset`       | Source token `assetId` from the tokens endpoint                                      |
| `depositType`       | `ORIGIN_CHAIN` (deposit on source chain) or `INTENTS` (already in Verifier contract) |
| `destinationAsset`  | Target token `assetId` from the tokens endpoint                                      |
| `amount`            | Amount in smallest unit (wei, yoctoNEAR, etc.)                                       |
| `recipient`         | Address to receive swapped tokens                                                    |
| `recipientType`     | `DESTINATION_CHAIN` (native address) or `INTENTS` (NEAR Intents account)             |
| `refundTo`          | Address for refunds if swap fails                                                    |
| `refundType`        | `ORIGIN_CHAIN` or `INTENTS`                                                          |
| `deadline`          | Quote expiration timestamp in ISO format                                             |

</details>
{% endstep %}

{% step %}

### Send tokens

Transfer tokens to  `depositAddress` from the quote response. The swap will be processed automatically upon deposit.

Save the deposit address and your transaction hash for tracking.
{% endstep %}

{% step %}

### Monitor the swap

Use [Get transactions history](/api-reference/swap-api-reference/get-transactions-history) for your account to monitor the status of the swap.

{% tabs %}
{% tab title="cURL" %}

```sh
curl "https://intents-api.aurora.dev/api/transactions/${appKey}?walletAddress=${walletAddress}"
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const response = await fetch(`https://intents-api.aurora.dev/api/transactions/${appKey}`);
const tokens = await response.json();
```

{% endtab %}
{% endtabs %}

<details>

<summary>Example response</summary>

```json
{
    "data": [
        {
            "originAsset": "nep141:wrap.near",
            "destinationAsset": "nep141:sol-0xaad74c68eecfc9f8c5bdcea614f6167048c795ef.omdep.near",
            "depositAddress": "42518b0fae0ba57ab1c671d05f0f27b78a9f9393850def062dea189299103930",
            "depositMemo": null,
            "depositAddressAndMemo": "42518b0fae0ba57ab1c671d05f0f27b78a9f9393850def062dea189299103930",
            "recipient": "your-account.near",
            "status": "SUCCESS",
            "createdAt": "2026-04-08T15:29:12.274Z",
            "createdAtTimestamp": 1775662152,
            "intentHashes": "3GvvNb1ujKyZzfBYigzNykJsWuHUu1BHftpR6xfYMkX4",
            "referral": "aurora-widget-widget-studio-user",
            "amountInFormatted": "2.0",
            "amountOutFormatted": "406.722426",
            "appFees": [
                {
                    "fee": 20,
                    "recipient": "widget_collect.sputnik-dao.near"
                }
            ],
            "nearTxHashes": [
                "224p1YPQaBicpGXj2Gz2PthGDaXukNzhGAGkGLen26ze",
                "2mJ9Yid2fvCubFY1QLBxUGc28mzfrjFXAJWVUvhtZ7cB"
            ],
            "originChainTxHashes": [],
            "destinationChainTxHashes": [],
            "amountIn": "2000000000000000000000000",
            "amountInUsd": "2.7000",
            "amountOut": "407031063",
            "amountOutUsd": "2.6869",
            "refundTo": "your-account.near",
            "senders": [],
            "refundReason": null,
            "refundFeeFormatted": "0",
            "refundFee": "0"
        },
    ]
}
```

</details>

<details>

<summary>Status response</summary>

| Status               | Description                                   |
| -------------------- | --------------------------------------------- |
| `PENDING_DEPOSIT`    | Awaiting your token deposit                   |
| `KNOWN_DEPOSIT_TX`   | Deposit transaction detected                  |
| `PROCESSING`         | Swap being executed                           |
| `SUCCESS`            | Tokens delivered to destination address       |
| `INCOMPLETE_DEPOSIT` | Deposit below required amount                 |
| `REFUNDED`           | Swap failed, funds returned to refund address |
| `FAILED`             | Swap encountered an error                     |

</details>
{% endstep %}
{% endstepper %}


# Supported Chains

Our API is powered by NEAR Intents, enabling cross-chain asset discovery, routing, and execution through a unified interface.

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="50.65234375">‎</th><th>CHAIN</th><th>SOURCE</th><th>DESTINATION</th></tr></thead><tbody><tr><td><img src="/files/3TMJHPLDv9jPeaKoBCpq" alt="" data-size="line"></td><td>ADI</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/5MdN9CaOPOc5gL65WowM" alt="" data-size="line"></td><td>Aleo</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/Y4jp8lJt3VAmAhcZXlvS" alt="" data-size="line"></td><td>Arbitrum</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/PgR51HT4CoiG3hxGD1eE" alt="" data-size="line"></td><td>Aurora</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/dbRLOxgWBb5TKyHNfoRk" alt="" data-size="line"></td><td>Avalanche</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/eQCNu5AT5BDgRfnxLPof" alt="" data-size="line"></td><td>Base</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/SG75T9nLhPXLWVNHG7tA" alt="" data-size="line"></td><td>Bera</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/eV6tbERf5hoiw3OnclnS" alt="" data-size="line"></td><td>Binance Smart Chain</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/KyvtFMxJVw7U4iOtZS3v" alt="" data-size="line"></td><td>Bitcoin</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/xHrIedtsjNmnd2bSxdrn" alt="" data-size="line"></td><td>Bitcoin Cash</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/YSLVZFeIEeHfvCaiZJjO" alt="" data-size="line"></td><td>Cardano</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/uivorID6vldc3OnzIqiF" alt="" data-size="line"></td><td>Dash</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/MI0F9k03AHjvfu7k0CJ0" alt="" data-size="line"></td><td>Dogecoin</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/9GMNeb7aSeyjAl4BZAlm" alt="" data-size="line"></td><td>Ethereum</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/9To32s13yHKCpV4ocoQP" alt="" data-size="line"></td><td>Gnosis</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/KIz44F0jBmAIO57BpbRH" alt="" data-size="line"></td><td>Hyperliquid</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/FVO5fls0pdWU9xz4eEVB" alt="" data-size="line"></td><td>Litecoin</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/C5flpvND0b50ZVPXXoo5" alt="" data-size="line"></td><td>Monad</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/R1KCSwtvWIdUog0usGKv" alt="" data-size="line"></td><td>NEAR</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/hGMywzPPsG8DtLEpyY2b" alt="" data-size="line"></td><td>Optimism</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/EIAQtyX84IdcqBFNlRHr" alt="" data-size="line"></td><td>Plasma</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/qqth9R8ovyroAS1uKLj1" alt="" data-size="line"></td><td>Polygon</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/dpPqAgrtn7jhyvGsy50n" alt="" data-size="line"></td><td>Scroll</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/ga9kAWhqZXqPZU93WOC1" alt="" data-size="original"></td><td>Solana</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/kTiQWNy7oEyVHCLYT3kd" alt="" data-size="line"></td><td>Starknet</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/shBCBJE9ub6pvsJgC4hm" alt="" data-size="line"></td><td>Stellar</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/18ZAvVYV1TGltUgv2qMG" alt="" data-size="line"></td><td>Sui</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/scqD9LQbaRLO4ks8574N" alt="" data-size="line"></td><td>TON</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/cJsy0zs1wZ0h49sVZQRS" alt="" data-size="line"></td><td>Tron</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/JyErasHvx1dxp16FMg65" alt="" data-size="line"></td><td>XLayer</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/AZj68Bm65FieD7mV7NPl" alt="" data-size="line"></td><td>XRP</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/FKIbuoo07NQdpVADeUwF" alt="" data-size="line"></td><td>Zcash</td><td>✅ Supported</td><td>✅ Supported</td></tr></tbody></table>


# Persistent Addresses

Persistent Addresses are deterministic, chain-specific addresses derived from a fixed set of inputs: the user identifier, the source chain, the recipient address, and the output asset. Given the same inputs, the API always returns the same address. There is no expiry, no regeneration, and no per-transaction state to manage.

{% hint style="info" %}
The PDA is unique per chain and supports multiple asset deposits.
{% endhint %}

## Business Flow

When your app calls the Persistent Addresses API, it receives a reusable deposit address tied to a specific source chain and destination. The user sends a regular token transfer to that address — no bridging UI, no extra steps. The system detects the deposit, routes it through NEAR Intents, executes any required swap, and funds the destination.

<figure><img src="/files/Sgnr0wA5C46vhMw0uOZM" alt=""><figcaption><p>Example Persistent Addresses flow with recipient address on BASE chain</p></figcaption></figure>

{% stepper %}
{% step %}

### App requests a deposit address

Apps call the PDA API with the minimal parameters: deposit chain, recipient and output asset.
{% endstep %}

{% step %}

### Receive a reusable address

The PDA API returns a reusable Deposit Address along with the current exchange rates.
{% endstep %}

{% step %}

### User sends funds

User transfers tokens to the Deposit Address.
{% endstep %}

{% step %}

### System processes the transfer

NEAR Intents swap agent handles routing and processes the transaction.
{% endstep %}

{% step %}

### Recipient gets funds

The destination asset is delivered to the recipient on the destination chain.
{% endstep %}
{% endstepper %}

## Features

### Static address

Each address is permanent and reusable. There is no TTL, no session, and no requirement to re-generate between deposits. The same address accepts unlimited sequential deposits.

### Asset conversion

An optional output asset can be specified at address generation time. If the deposited asset differs from the output asset, a swap is executed in the same atomic flow via NEAR Intents solvers. Example: `USDT on Tron → USDC on Base`.

### Destination action

You define what happens on arrival: credit a balance, execute a contract call, or fund a position. The destination action is encoded at generation time and does not require a separate call.

## Use Cases

### Neobank & fintech top-up

Give every user a persistent top-up address. They can send from any chain or CEX, and the funds are automatically added to their balance. No bridging instructions, no wrong-chain support tickets.

```
Example: SOL on Solana → routed → Neobank account credited in USDC on Base
```

### Prediction market funding

Users with assets on any chain can fund positions directly: no redirect, no bridge UI, no chain-switching prompt in the middle of a flow.

```
Example: USDT on Arbitrum → routed → prediction market account balance credited
```

### Trading account deposit

A persistent deposit address means capital flows from any chain or CEX directly into a trading account: no manual bridging, no network selection, no drop-off between decision and execution.

### Infrastructure & protocol inbound liquidity

Handle deposits from any chain natively without maintaining per-chain connectors. Embed one address generation call and let the routing layer handle the rest.


# Custom Actions

Coming soon

It allows the chain of actions on the target chain

* The widget studio will allow the definition of custom actions for the deposit widget mode


# Custom actions - Widget


# Custom actions - API

use case

* user clicks deposit
* send ETH on ethereum
* opens a position with USDC on Morpho

Integration

* we have one endpoint /deposits

<http://localhost:3000/deposit/{appKey}>

* I want to retrieve the deposit address
* BUT I want to chain an action on target chain


# Swap API Reference


# Get supported tokens

## GET /api/tokens/{apiKey}

> Get asset available tokens and volume statistics.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/tokens/{apiKey}":{"get":{"tags":["tokens"],"description":"Get asset available tokens and volume statistics.","parameters":[{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Assets and stats retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"asset_stats":{"type":"array","items":{"type":"object","properties":{"token_id":{"type":"string","description":"ID of the asset"},"blockchain":{"type":"string","description":"Blockchain associated with the token"},"symbol":{"type":"string","description":"Token symbol (e.g. BTC, ETH)"},"volume_amount_usd":{"type":"string","description":"24-hours volume of the asset denominated in USD"}},"required":["token_id","blockchain","symbol","volume_amount_usd"],"additionalProperties":false},"description":"24-hour asset volume statistics used by the widget"},"tokens":{"type":"array","items":{"type":"object","properties":{"assetId":{"type":"string","description":"Unique asset identifier"},"decimals":{"type":"number","description":"Number of decimals for the token"},"blockchain":{"type":"string","description":"Blockchain associated with the token.\n\nAvailable options: `near`, `eth`, `base`, `arb`, `btc`, `sol`, `ton`, `dash`, `doge`, `xrp`, `zec`, `gnosis`, `bera`, `bsc`, `pol`, `tron`, `sui`, `op`, `avax`, `cardano`, `ltc`, `xlayer`, `monad`, `bch`, `adi`, `plasma`, `scroll`, `starknet`, `aleo`."},"symbol":{"type":"string","description":"Token symbol (e.g. BTC, ETH)"},"price":{"type":"number","description":"Current price of the token in USD"},"priceUpdatedAt":{"type":"string","description":"Date when the token price was last updated"},"contractAddress":{"description":"Contract address of the token, if available","type":"string"}},"required":["assetId","decimals","blockchain","symbol","price","priceUpdatedAt"],"additionalProperties":false},"description":"Tokens currently supported by the Intents API"}},"required":["asset_stats","tokens"],"additionalProperties":false,"description":"Assets and stats retrieved successfully"}}}},"500":{"description":"Internal Server Error - Failed to fetch data from Dune or tokens API","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to fetch data from Dune or tokens API"}}}}}}}}}
```


# Request a quote

## POST /api/quote/{apiKey}

> Request a swap quote based on input parameters such as the assets, amount, slippage tolerance, and recipient/refund information.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/quote/{apiKey}":{"post":{"tags":["quote"],"description":"Request a swap quote based on input parameters such as the assets, amount, slippage tolerance, and recipient/refund information.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"dry":{"type":"boolean","description":"Flag indicating whether this is a dry run request.\n\nIf `true`, the response will NOT contain the following fields:\n\n- `depositAddress`\n\n- `timeWhenInactive`\n\n- `deadline`"},"swapType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT","FLEX_INPUT","ANY_INPUT"],"description":"How to interpret `amount` (and refunds) when performing the swap:\n\n- `EXACT_INPUT` — requests the output amount for an exact input.\n\n  - If deposit is less than `amountIn`, the deposit is refunded by deadline.\n\n  - If deposit is above than `amountIn`, the swap is processed and the excess is refunded to refundTo address after swap is complete.\n\n- `EXACT_OUTPUT` — requests the input amount for an exact output.\n\n  - The quote response includes `minAmountIn` and `maxAmountIn`.\n\n  - If the input is above `maxAmountIn`, the swap is processed and the excess is refunded to `refundTo` after the swap is complete.\n\n  - If the input is below `minAmountIn`, the deposit is refunded by deadline.\n\n- `FLEX_INPUT` — a flexible input amount that allows for partial deposits and variable amounts.\n\n  - `slippage` applies both to `amountOut` and `amountIn` and defines an acceptable range (`minAmountIn` and `minAmountOut`).\n\n  - Any amount higher than `minAmountIn` is accepted and converted to the output asset as long as `minAmountOut` is met.\n\n  - `amountIn` can be less, as long as the 'slippage + 1%' constraint is met. If the total received by the deadline is below the lower bound, the deposit is refunded.\n\n  - If deposits exceed the upper bound, the swap is still processed."},"depositType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of deposit address:\n\n- `ORIGIN_CHAIN` - deposit address on the origin chain.\n\n- `INTENTS` - the account ID within NEAR Intents to which you should transfer assets.\n\n- `CONFIDENTIAL_INTENTS` - the account ID within Confidential Intents to which assets are to be transferred. Fund the swap from a Confidential Intents account by submitting a signed transfer intent to the quote `depositAddress`. Direct token transfers are not supported."},"depositMode":{"description":"What deposit address mode you will get in the response.\n\nMost chains support only `SIMPLE`, and some (for example `stellar`) only `MEMO`:\n\n- `SIMPLE` - usual deposit with only a deposit address.\n\n- `MEMO` - some chains require the `memo` together with `depositAddress` for the swap to work.","type":"string","enum":["SIMPLE","MEMO"]},"quoteWaitingTimeMs":{"description":"Time in milliseconds the user is willing to wait for a quote from the relay.\n\nUse `0` to request the fastest available quote.","type":"number"},"sessionId":{"description":"Unique client session identifier.","type":"string"},"confidentiality":{"description":"Confidentiality mode for this quote (defaults to `public`):\n\n- `public` - the swap is settled on public Intents rails.\n\n- `basic` / `advanced` - the swap is settled on Confidential Intents rails.","type":"string","enum":["public","basic","advanced"]},"amount":{"type":"string","description":"Amount to swap as the base amount, in the smallest unit of the currency. It is interpreted as input or output based on `swapType`."},"originAsset":{"type":"string","description":"ID of the origin asset."},"destinationAsset":{"type":"string","description":"ID of the destination asset."},"slippageTolerance":{"type":"number","description":"Slippage tolerance for the swap in basis points (1/100th of a percent), e.g. 100 for 1%."},"refundTo":{"type":"string","description":"Address used for refunds."},"refundType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of refund address:\n\n- `ORIGIN_CHAIN` - assets are refunded to the `refundTo` address on the origin chain.\n\n- `INTENTS` - assets are refunded to the `refundTo` Intents account.\n\n- `CONFIDENTIAL_INTENTS` - assets are refunded to the `refundTo` Confidential Intents account. On Confidential Intents, 1Click settles refunds via signed transfer intent rather than direct token transfer."},"recipient":{"type":"string","description":"Recipient address. The format must match `recipientType`."},"recipientType":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of recipient address:\n\n- `DESTINATION_CHAIN` - assets are transferred to the chain of `destinationAsset`.\n\n- `INTENTS` - assets are transferred to an account inside Intents.\n\n- `CONFIDENTIAL_INTENTS` - assets are transferred to the `recipient` account inside Confidential Intents. On Confidential Intents, 1Click settles swap output via signed transfer intent rather than direct token transfer."},"virtualChainRecipient":{"description":"EVM address of a transfer recipient in a virtual chain","type":"string"},"virtualChainRefundRecipient":{"description":"EVM address of a refund recipient in a virtual chain","type":"string"},"deadline":{"description":"Timestamp in ISO format indicating when refunds begin if the swap is not completed by then. If omitted, a default deadline is applied.","type":"string"},"connectedWallets":{"description":"Addresses of connected wallets.","type":"array","items":{"type":"string"}}},"required":["dry","swapType","depositType","amount","originAsset","destinationAsset","slippageTolerance","refundTo","refundType","recipient","recipientType"]}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Quote successfully generated","content":{"application/json":{"schema":{"type":"object","properties":{"timestamp":{"type":"string","description":"Timestamp in ISO format used to derive the deposit address for this quote."},"signature":{"type":"string","description":"1Click service signature confirming the quote for the specific deposit address."},"correlationId":{"description":"Unique identifier for request tracing and debugging.","nullable":true,"type":"string"},"quoteRequest":{"type":"object","properties":{"dry":{"type":"boolean","description":"Flag indicating whether this is a dry run request.\n\nIf `true`, the response will NOT contain the following fields:\n\n- `depositAddress`\n\n- `timeWhenInactive`\n\n- `deadline`"},"swapType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT","FLEX_INPUT","ANY_INPUT"],"description":"How to interpret `amount` (and refunds) when performing the swap:\n\n- `EXACT_INPUT` — requests the output amount for an exact input.\n\n  - If deposit is less than `amountIn`, the deposit is refunded by deadline.\n\n  - If deposit is above than `amountIn`, the swap is processed and the excess is refunded to refundTo address after swap is complete.\n\n- `EXACT_OUTPUT` — requests the input amount for an exact output.\n\n  - The quote response includes `minAmountIn` and `maxAmountIn`.\n\n  - If the input is above `maxAmountIn`, the swap is processed and the excess is refunded to `refundTo` after the swap is complete.\n\n  - If the input is below `minAmountIn`, the deposit is refunded by deadline.\n\n- `FLEX_INPUT` — a flexible input amount that allows for partial deposits and variable amounts.\n\n  - `slippage` applies both to `amountOut` and `amountIn` and defines an acceptable range (`minAmountIn` and `minAmountOut`).\n\n  - Any amount higher than `minAmountIn` is accepted and converted to the output asset as long as `minAmountOut` is met.\n\n  - `amountIn` can be less, as long as the 'slippage + 1%' constraint is met. If the total received by the deadline is below the lower bound, the deposit is refunded.\n\n  - If deposits exceed the upper bound, the swap is still processed."},"depositType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of deposit address:\n\n- `ORIGIN_CHAIN` - deposit address on the origin chain.\n\n- `INTENTS` - the account ID within NEAR Intents to which you should transfer assets.\n\n- `CONFIDENTIAL_INTENTS` - the account ID within Confidential Intents to which assets are to be transferred. Fund the swap from a Confidential Intents account by submitting a signed transfer intent to the quote `depositAddress`. Direct token transfers are not supported."},"depositMode":{"description":"What deposit address mode you will get in the response.\n\nMost chains support only `SIMPLE`, and some (for example `stellar`) only `MEMO`:\n\n- `SIMPLE` - usual deposit with only a deposit address.\n\n- `MEMO` - some chains require the `memo` together with `depositAddress` for the swap to work.","type":"string","enum":["SIMPLE","MEMO"]},"quoteWaitingTimeMs":{"description":"Time in milliseconds the user is willing to wait for a quote from the relay.\n\nUse `0` to request the fastest available quote.","type":"number"},"sessionId":{"description":"Unique client session identifier.","type":"string"},"confidentiality":{"description":"Confidentiality mode for this quote (defaults to `public`):\n\n- `public` - the swap is settled on public Intents rails.\n\n- `basic` / `advanced` - the swap is settled on Confidential Intents rails.","type":"string","enum":["public","basic","advanced"]},"amount":{"type":"string","description":"Amount to swap as the base amount, in the smallest unit of the currency. It is interpreted as input or output based on `swapType`."},"originAsset":{"type":"string","description":"ID of the origin asset."},"destinationAsset":{"type":"string","description":"ID of the destination asset."},"slippageTolerance":{"type":"number","description":"Slippage tolerance for the swap in basis points (1/100th of a percent), e.g. 100 for 1%."},"refundTo":{"type":"string","description":"Address used for refunds."},"refundType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of refund address:\n\n- `ORIGIN_CHAIN` - assets are refunded to the `refundTo` address on the origin chain.\n\n- `INTENTS` - assets are refunded to the `refundTo` Intents account.\n\n- `CONFIDENTIAL_INTENTS` - assets are refunded to the `refundTo` Confidential Intents account. On Confidential Intents, 1Click settles refunds via signed transfer intent rather than direct token transfer."},"recipient":{"type":"string","description":"Recipient address. The format must match `recipientType`."},"recipientType":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of recipient address:\n\n- `DESTINATION_CHAIN` - assets are transferred to the chain of `destinationAsset`.\n\n- `INTENTS` - assets are transferred to an account inside Intents.\n\n- `CONFIDENTIAL_INTENTS` - assets are transferred to the `recipient` account inside Confidential Intents. On Confidential Intents, 1Click settles swap output via signed transfer intent rather than direct token transfer."},"virtualChainRecipient":{"description":"EVM address of a transfer recipient in a virtual chain.","nullable":true,"type":"string"},"virtualChainRefundRecipient":{"description":"EVM address of a refund recipient in a virtual chain.","nullable":true,"type":"string"},"deadline":{"type":"string","description":"Timestamp in ISO format indicating when refunds begin if the swap is not completed by then."},"connectedWallets":{"description":"Addresses of connected wallets.","nullable":true,"type":"array","items":{"type":"string"}},"referral":{"description":"Referral identifier (lowercase only). It is reflected in on-chain data and public analytics platforms.","nullable":true,"type":"string"},"appFees":{"description":"List of recipients and their fees.","nullable":true,"type":"array","items":{"type":"object","properties":{"recipient":{"type":"string","description":"Fee recipient identifier or address."},"fee":{"type":"number","description":"Fee amount in basis points (1/100th of a percent)."}},"required":["recipient","fee"],"additionalProperties":false}},"customRecipientMsg":{"description":"HIGHLY EXPERIMENTAL: message to pass to `ft_transfer_call` when withdrawing assets to NEAR.\n\nOtherwise, `ft_transfer` will be used.\n\nWARNING: Funds can be lost if used with non NEP-141 tokens, with insufficient `storage_deposit`, or if the recipient does not implement `ft_on_transfer`.","nullable":true,"type":"string"}},"required":["dry","swapType","depositType","amount","originAsset","destinationAsset","slippageTolerance","refundTo","refundType","recipient","recipientType","deadline"],"additionalProperties":false,"description":"User request payload."},"quote":{"type":"object","properties":{"timeEstimate":{"type":"number","description":"Estimated swap completion time in seconds."},"deadline":{"description":"Timestamp in ISO format indicating when the quote becomes inactive and refunds may begin.","type":"string"},"timeWhenInactive":{"description":"Timestamp in ISO format indicating when the deposit address becomes inactive.","type":"string"},"depositAddress":{"description":"Unique deposit address to which the origin asset must be transferred to initiate the swap.","type":"string"},"depositMemo":{"description":"Additional memo required together with `depositAddress` for memo-based deposit chains.","type":"string"},"chainDepositAddresses":{"description":"Deposit addresses across all bridge-supported blockchains for funding the same Intents account.\n\nPresent only for public `ANY_INPUT` quotes (`depositType` `INTENTS`); the confidential rail does not support multi-chain funding addresses yet.\n\nEach entry forwards to the Intents account in `depositAddress`, so the user may deposit from any listed chain.","type":"array","items":{"type":"object","properties":{"blockchain":{"type":"string","enum":["near","eth","base","arb","btc","sol","ton","dash","doge","xrp","zec","gnosis","bera","bsc","pol","tron","sui","movement","op","avax","stellar","aptos","cardano","ltc","xlayer","monad","bch","adi","plasma","scroll","starknet","aleo","hypercore"],"description":"Blockchain on which this deposit address accepts funds."},"address":{"type":"string","description":"Deposit address on `blockchain`. Funds sent here are credited to the same Intents account."},"memo":{"description":"Memo required together with `address` on chains that need it (e.g. Stellar, XRP, TON).","type":"string"}},"required":["blockchain","address"],"additionalProperties":false}},"amountIn":{"type":"string","description":"Input amount in the smallest unit of the origin asset."},"amountInFormatted":{"type":"string","description":"Human-readable formatted input amount."},"amountInUsd":{"type":"string","description":"Estimated USD value of the input amount."},"minAmountIn":{"type":"string","description":"Minimum accepted input amount for the quote."},"maxAmountIn":{"description":"Maximum accepted input amount for the quote, when applicable.","type":"string"},"amountOut":{"type":"string","description":"Expected output amount in the smallest unit of the destination asset."},"amountOutFormatted":{"type":"string","description":"Human-readable formatted output amount."},"amountOutUsd":{"type":"string","description":"Estimated USD value of the output amount."},"minAmountOut":{"type":"string","description":"Minimum guaranteed output amount for the quote."},"refundFee":{"description":"Fee charged for refunding assets to the refund address, in the smallest unit of the origin asset.","nullable":true,"type":"string"},"withdrawFee":{"description":"Fee charged for withdrawing assets to the recipient, in the smallest unit of the destination asset. This fee is already accounted for in the final `amountOut`.","nullable":true,"type":"string"},"virtualChainRecipient":{"description":"EVM address of a transfer recipient in a virtual chain.","nullable":true,"type":"string"},"virtualChainRefundRecipient":{"description":"EVM address of a refund recipient in a virtual chain.","nullable":true,"type":"string"},"customRecipientMsg":{"description":"HIGHLY EXPERIMENTAL: message passed to `ft_transfer_call` when withdrawing assets to NEAR. Otherwise, `ft_transfer` is used.","nullable":true,"type":"string"}},"required":["timeEstimate","amountIn","amountInFormatted","amountInUsd","minAmountIn","amountOut","amountOutFormatted","amountOutUsd","minAmountOut"],"additionalProperties":false,"description":"Quote details, including deposit instructions and expected swap amounts."}},"required":["timestamp","signature","quoteRequest","quote"],"additionalProperties":false,"description":"Quote successfully generated"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"API error or fee configuration is invalid"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"404":{"description":"Application key is not assigned","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[404]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Application key is not assigned"}}}},"500":{"description":"Internal Server Error - Failed to get a quote service or missing deposit address","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to get a quote service or missing deposit address"}}}}}}}}}
```


# Submit a deposit

## POST /api/deposit/submit/{apiKey}

> Optionally notify the 1Click service that a deposit has been sent to the deposit address, using the blockchain transaction hash. This can speed up swap processing by allowing the system to preemptively verify the deposit.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/deposit/submit/{apiKey}":{"post":{"tags":["deposit"],"description":"Optionally notify the 1Click service that a deposit has been sent to the deposit address, using the blockchain transaction hash. This can speed up swap processing by allowing the system to preemptively verify the deposit.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"txHash":{"type":"string","description":"Transaction hash of your deposit."},"depositAddress":{"type":"string","description":"Deposit address returned in the quote response."},"nearSenderAccount":{"description":"Sender account (used only for the NEAR blockchain).","type":"string"},"memo":{"description":"Memo. Use if the deposit was submitted with one.","type":"string"}},"required":["txHash","depositAddress"]}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Deposit transaction submitted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"quoteResponse":{"type":"object","properties":{"timestamp":{"type":"string","description":"Timestamp in ISO format used to derive the deposit address for this quote."},"signature":{"type":"string","description":"1Click service signature confirming the quote for the specific deposit address."},"correlationId":{"description":"Unique identifier for request tracing and debugging.","nullable":true,"type":"string"},"quoteRequest":{"type":"object","properties":{"dry":{"type":"boolean","description":"Flag indicating whether this is a dry run request.\n\nIf `true`, the response will NOT contain the following fields:\n\n- `depositAddress`\n\n- `timeWhenInactive`\n\n- `deadline`"},"swapType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT","FLEX_INPUT","ANY_INPUT"],"description":"How to interpret `amount` (and refunds) when performing the swap:\n\n- `EXACT_INPUT` — requests the output amount for an exact input.\n\n  - If deposit is less than `amountIn`, the deposit is refunded by deadline.\n\n  - If deposit is above than `amountIn`, the swap is processed and the excess is refunded to refundTo address after swap is complete.\n\n- `EXACT_OUTPUT` — requests the input amount for an exact output.\n\n  - The quote response includes `minAmountIn` and `maxAmountIn`.\n\n  - If the input is above `maxAmountIn`, the swap is processed and the excess is refunded to `refundTo` after the swap is complete.\n\n  - If the input is below `minAmountIn`, the deposit is refunded by deadline.\n\n- `FLEX_INPUT` — a flexible input amount that allows for partial deposits and variable amounts.\n\n  - `slippage` applies both to `amountOut` and `amountIn` and defines an acceptable range (`minAmountIn` and `minAmountOut`).\n\n  - Any amount higher than `minAmountIn` is accepted and converted to the output asset as long as `minAmountOut` is met.\n\n  - `amountIn` can be less, as long as the 'slippage + 1%' constraint is met. If the total received by the deadline is below the lower bound, the deposit is refunded.\n\n  - If deposits exceed the upper bound, the swap is still processed."},"depositType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of deposit address:\n\n- `ORIGIN_CHAIN` - deposit address on the origin chain.\n\n- `INTENTS` - the account ID within NEAR Intents to which you should transfer assets.\n\n- `CONFIDENTIAL_INTENTS` - the account ID within Confidential Intents to which assets are to be transferred. Fund the swap from a Confidential Intents account by submitting a signed transfer intent to the quote `depositAddress`. Direct token transfers are not supported."},"depositMode":{"description":"What deposit address mode you will get in the response.\n\nMost chains support only `SIMPLE`, and some (for example `stellar`) only `MEMO`:\n\n- `SIMPLE` - usual deposit with only a deposit address.\n\n- `MEMO` - some chains require the `memo` together with `depositAddress` for the swap to work.","type":"string","enum":["SIMPLE","MEMO"]},"quoteWaitingTimeMs":{"description":"Time in milliseconds the user is willing to wait for a quote from the relay.\n\nUse `0` to request the fastest available quote.","type":"number"},"sessionId":{"description":"Unique client session identifier.","type":"string"},"confidentiality":{"description":"Confidentiality mode for this quote (defaults to `public`):\n\n- `public` - the swap is settled on public Intents rails.\n\n- `basic` / `advanced` - the swap is settled on Confidential Intents rails.","type":"string","enum":["public","basic","advanced"]},"amount":{"type":"string","description":"Amount to swap as the base amount, in the smallest unit of the currency. It is interpreted as input or output based on `swapType`."},"originAsset":{"type":"string","description":"ID of the origin asset."},"destinationAsset":{"type":"string","description":"ID of the destination asset."},"slippageTolerance":{"type":"number","description":"Slippage tolerance for the swap in basis points (1/100th of a percent), e.g. 100 for 1%."},"refundTo":{"type":"string","description":"Address used for refunds."},"refundType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of refund address:\n\n- `ORIGIN_CHAIN` - assets are refunded to the `refundTo` address on the origin chain.\n\n- `INTENTS` - assets are refunded to the `refundTo` Intents account.\n\n- `CONFIDENTIAL_INTENTS` - assets are refunded to the `refundTo` Confidential Intents account. On Confidential Intents, 1Click settles refunds via signed transfer intent rather than direct token transfer."},"recipient":{"type":"string","description":"Recipient address. The format must match `recipientType`."},"recipientType":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of recipient address:\n\n- `DESTINATION_CHAIN` - assets are transferred to the chain of `destinationAsset`.\n\n- `INTENTS` - assets are transferred to an account inside Intents.\n\n- `CONFIDENTIAL_INTENTS` - assets are transferred to the `recipient` account inside Confidential Intents. On Confidential Intents, 1Click settles swap output via signed transfer intent rather than direct token transfer."},"virtualChainRecipient":{"description":"EVM address of a transfer recipient in a virtual chain.","nullable":true,"type":"string"},"virtualChainRefundRecipient":{"description":"EVM address of a refund recipient in a virtual chain.","nullable":true,"type":"string"},"deadline":{"type":"string","description":"Timestamp in ISO format indicating when refunds begin if the swap is not completed by then."},"connectedWallets":{"description":"Addresses of connected wallets.","nullable":true,"type":"array","items":{"type":"string"}},"referral":{"description":"Referral identifier (lowercase only). It is reflected in on-chain data and public analytics platforms.","nullable":true,"type":"string"},"appFees":{"description":"List of recipients and their fees.","nullable":true,"type":"array","items":{"type":"object","properties":{"recipient":{"type":"string","description":"Fee recipient identifier or address."},"fee":{"type":"number","description":"Fee amount in basis points (1/100th of a percent)."}},"required":["recipient","fee"],"additionalProperties":false}},"customRecipientMsg":{"description":"HIGHLY EXPERIMENTAL: message to pass to `ft_transfer_call` when withdrawing assets to NEAR.\n\nOtherwise, `ft_transfer` will be used.\n\nWARNING: Funds can be lost if used with non NEP-141 tokens, with insufficient `storage_deposit`, or if the recipient does not implement `ft_on_transfer`.","nullable":true,"type":"string"}},"required":["dry","swapType","depositType","amount","originAsset","destinationAsset","slippageTolerance","refundTo","refundType","recipient","recipientType","deadline"],"additionalProperties":false,"description":"User request payload."},"quote":{"type":"object","properties":{"timeEstimate":{"type":"number","description":"Estimated swap completion time in seconds."},"deadline":{"description":"Timestamp in ISO format indicating when the quote becomes inactive and refunds may begin.","type":"string"},"timeWhenInactive":{"description":"Timestamp in ISO format indicating when the deposit address becomes inactive.","type":"string"},"depositAddress":{"description":"Unique deposit address to which the origin asset must be transferred to initiate the swap.","type":"string"},"depositMemo":{"description":"Additional memo required together with `depositAddress` for memo-based deposit chains.","type":"string"},"chainDepositAddresses":{"description":"Deposit addresses across all bridge-supported blockchains for funding the same Intents account.\n\nPresent only for public `ANY_INPUT` quotes (`depositType` `INTENTS`); the confidential rail does not support multi-chain funding addresses yet.\n\nEach entry forwards to the Intents account in `depositAddress`, so the user may deposit from any listed chain.","type":"array","items":{"type":"object","properties":{"blockchain":{"type":"string","enum":["near","eth","base","arb","btc","sol","ton","dash","doge","xrp","zec","gnosis","bera","bsc","pol","tron","sui","movement","op","avax","stellar","aptos","cardano","ltc","xlayer","monad","bch","adi","plasma","scroll","starknet","aleo","hypercore"],"description":"Blockchain on which this deposit address accepts funds."},"address":{"type":"string","description":"Deposit address on `blockchain`. Funds sent here are credited to the same Intents account."},"memo":{"description":"Memo required together with `address` on chains that need it (e.g. Stellar, XRP, TON).","type":"string"}},"required":["blockchain","address"],"additionalProperties":false}},"amountIn":{"type":"string","description":"Input amount in the smallest unit of the origin asset."},"amountInFormatted":{"type":"string","description":"Human-readable formatted input amount."},"amountInUsd":{"type":"string","description":"Estimated USD value of the input amount."},"minAmountIn":{"type":"string","description":"Minimum accepted input amount for the quote."},"maxAmountIn":{"description":"Maximum accepted input amount for the quote, when applicable.","type":"string"},"amountOut":{"type":"string","description":"Expected output amount in the smallest unit of the destination asset."},"amountOutFormatted":{"type":"string","description":"Human-readable formatted output amount."},"amountOutUsd":{"type":"string","description":"Estimated USD value of the output amount."},"minAmountOut":{"type":"string","description":"Minimum guaranteed output amount for the quote."},"refundFee":{"description":"Fee charged for refunding assets to the refund address, in the smallest unit of the origin asset.","nullable":true,"type":"string"},"withdrawFee":{"description":"Fee charged for withdrawing assets to the recipient, in the smallest unit of the destination asset. This fee is already accounted for in the final `amountOut`.","nullable":true,"type":"string"},"virtualChainRecipient":{"description":"EVM address of a transfer recipient in a virtual chain.","nullable":true,"type":"string"},"virtualChainRefundRecipient":{"description":"EVM address of a refund recipient in a virtual chain.","nullable":true,"type":"string"},"customRecipientMsg":{"description":"HIGHLY EXPERIMENTAL: message passed to `ft_transfer_call` when withdrawing assets to NEAR. Otherwise, `ft_transfer` is used.","nullable":true,"type":"string"}},"required":["timeEstimate","amountIn","amountInFormatted","amountInUsd","minAmountIn","amountOut","amountOutFormatted","amountOutUsd","minAmountOut"],"additionalProperties":false,"description":"Quote details, including deposit instructions and expected swap amounts."}},"required":["timestamp","signature","quoteRequest","quote"],"additionalProperties":false,"description":"Quote response from the original request."},"status":{"type":"string","enum":["KNOWN_DEPOSIT_TX","PENDING_DEPOSIT","INCOMPLETE_DEPOSIT","PROCESSING","SUCCESS","REFUNDED","FAILED"],"description":"Current execution status of the swap:\n\n- `KNOWN_DEPOSIT_TX` - a deposit transaction is known but not yet confirmed.\n\n- `PENDING_DEPOSIT` - waiting for the deposit to arrive at the deposit address.\n\n- `INCOMPLETE_DEPOSIT` - the deposit received is below the required amount.\n\n- `PROCESSING` - the deposit was received and the swap is being processed.\n\n- `SUCCESS` - the swap completed successfully.\n\n- `REFUNDED` - the deposit was refunded.\n\n- `FAILED` - the swap failed."},"updatedAt":{"type":"string","description":"Timestamp in ISO format when the state was last updated."},"swapDetails":{"type":"object","properties":{"intentHashes":{"type":"array","items":{"type":"string"},"description":"All intent hashes that took part in this swap."},"nearTxHashes":{"type":"array","items":{"type":"string"},"description":"All NEAR transactions executed for this swap."},"amountIn":{"description":"Exact amount of `originToken` after the trade was settled.","nullable":true,"type":"string"},"amountInFormatted":{"description":"Exact amount of `originToken` in readable format after the trade was settled.","nullable":true,"type":"string"},"amountInUsd":{"description":"Exact amount of `originToken` equivalent in USD.","nullable":true,"type":"string"},"amountOut":{"description":"Exact amount of `destinationToken` after the trade was settled.","nullable":true,"type":"string"},"amountOutFormatted":{"description":"Exact amount of `destinationToken` in readable format after the trade was settled.","nullable":true,"type":"string"},"amountOutUsd":{"description":"Exact amount of `destinationToken` equivalent in USD.","nullable":true,"type":"string"},"slippage":{"description":"Actual slippage.","nullable":true,"type":"number"},"originChainTxHashes":{"type":"array","items":{"type":"object","properties":{"hash":{"type":"string","description":"Transaction hash."},"explorerUrl":{"type":"string","description":"Explorer URL for the transaction."}},"required":["hash","explorerUrl"],"additionalProperties":false},"description":"Hashes and explorer URLs for all transactions on the origin chain."},"destinationChainTxHashes":{"type":"array","items":{"type":"object","properties":{"hash":{"type":"string","description":"Transaction hash."},"explorerUrl":{"type":"string","description":"Explorer URL for the transaction."}},"required":["hash","explorerUrl"],"additionalProperties":false},"description":"Hashes and explorer URLs for all transactions on the destination chain."},"refundedAmount":{"description":"Amount of `originAsset` transferred to `refundTo`.","nullable":true,"type":"string"},"refundedAmountFormatted":{"description":"Refunded amount in readable format.","nullable":true,"type":"string"},"refundedAmountUsd":{"description":"Refunded amount equivalent in USD.","nullable":true,"type":"string"},"refundReason":{"description":"Reason for refund.","nullable":true,"type":"string"},"depositedAmount":{"description":"Amount deposited to `depositAddress` onchain.","nullable":true,"type":"string"},"depositedAmountFormatted":{"description":"Amount deposited in readable format.","nullable":true,"type":"string"},"depositedAmountUsd":{"description":"Amount deposited equivalent in USD.","nullable":true,"type":"string"},"referral":{"description":"Referral identifier.","nullable":true,"type":"string"}},"required":["intentHashes","nearTxHashes","originChainTxHashes","destinationChainTxHashes"],"additionalProperties":false,"description":"Details of actual swaps and withdrawals."}},"required":["correlationId","quoteResponse","status","updatedAt","swapDetails"],"additionalProperties":false,"description":"Deposit transaction submitted successfully"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"API error or invalid request parameters"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"500":{"description":"Internal Server Error - Failed to reach the deposit service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach the deposit service"}}}}}}}}}
```


# Get swap status

## GET /api/status/{apiKey}

> Check the execution status of a swap using the unique deposit address from the quote response.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/status/{apiKey}":{"get":{"tags":["status"],"description":"Check the execution status of a swap using the unique deposit address from the quote response.","parameters":[{"schema":{"type":"string"},"in":"query","name":"depositAddress","required":true,"description":"Unique deposit address returned in the quote response for the swap whose status is requested."},{"schema":{"type":"string"},"in":"query","name":"depositMemo","required":false,"description":"Deposit memo. Required when the original quote response included a `depositMemo`."},{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Swap execution status retrieved successfully","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"quoteResponse":{"type":"object","properties":{"timestamp":{"type":"string","description":"Timestamp in ISO format used to derive the deposit address for this quote."},"signature":{"type":"string","description":"1Click service signature confirming the quote for the specific deposit address."},"correlationId":{"description":"Unique identifier for request tracing and debugging.","nullable":true,"type":"string"},"quoteRequest":{"type":"object","properties":{"dry":{"type":"boolean","description":"Flag indicating whether this is a dry run request.\n\nIf `true`, the response will NOT contain the following fields:\n\n- `depositAddress`\n\n- `timeWhenInactive`\n\n- `deadline`"},"swapType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT","FLEX_INPUT","ANY_INPUT"],"description":"How to interpret `amount` (and refunds) when performing the swap:\n\n- `EXACT_INPUT` — requests the output amount for an exact input.\n\n  - If deposit is less than `amountIn`, the deposit is refunded by deadline.\n\n  - If deposit is above than `amountIn`, the swap is processed and the excess is refunded to refundTo address after swap is complete.\n\n- `EXACT_OUTPUT` — requests the input amount for an exact output.\n\n  - The quote response includes `minAmountIn` and `maxAmountIn`.\n\n  - If the input is above `maxAmountIn`, the swap is processed and the excess is refunded to `refundTo` after the swap is complete.\n\n  - If the input is below `minAmountIn`, the deposit is refunded by deadline.\n\n- `FLEX_INPUT` — a flexible input amount that allows for partial deposits and variable amounts.\n\n  - `slippage` applies both to `amountOut` and `amountIn` and defines an acceptable range (`minAmountIn` and `minAmountOut`).\n\n  - Any amount higher than `minAmountIn` is accepted and converted to the output asset as long as `minAmountOut` is met.\n\n  - `amountIn` can be less, as long as the 'slippage + 1%' constraint is met. If the total received by the deadline is below the lower bound, the deposit is refunded.\n\n  - If deposits exceed the upper bound, the swap is still processed."},"depositType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of deposit address:\n\n- `ORIGIN_CHAIN` - deposit address on the origin chain.\n\n- `INTENTS` - the account ID within NEAR Intents to which you should transfer assets.\n\n- `CONFIDENTIAL_INTENTS` - the account ID within Confidential Intents to which assets are to be transferred. Fund the swap from a Confidential Intents account by submitting a signed transfer intent to the quote `depositAddress`. Direct token transfers are not supported."},"depositMode":{"description":"What deposit address mode you will get in the response.\n\nMost chains support only `SIMPLE`, and some (for example `stellar`) only `MEMO`:\n\n- `SIMPLE` - usual deposit with only a deposit address.\n\n- `MEMO` - some chains require the `memo` together with `depositAddress` for the swap to work.","type":"string","enum":["SIMPLE","MEMO"]},"quoteWaitingTimeMs":{"description":"Time in milliseconds the user is willing to wait for a quote from the relay.\n\nUse `0` to request the fastest available quote.","type":"number"},"sessionId":{"description":"Unique client session identifier.","type":"string"},"confidentiality":{"description":"Confidentiality mode for this quote (defaults to `public`):\n\n- `public` - the swap is settled on public Intents rails.\n\n- `basic` / `advanced` - the swap is settled on Confidential Intents rails.","type":"string","enum":["public","basic","advanced"]},"amount":{"type":"string","description":"Amount to swap as the base amount, in the smallest unit of the currency. It is interpreted as input or output based on `swapType`."},"originAsset":{"type":"string","description":"ID of the origin asset."},"destinationAsset":{"type":"string","description":"ID of the destination asset."},"slippageTolerance":{"type":"number","description":"Slippage tolerance for the swap in basis points (1/100th of a percent), e.g. 100 for 1%."},"refundTo":{"type":"string","description":"Address used for refunds."},"refundType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of refund address:\n\n- `ORIGIN_CHAIN` - assets are refunded to the `refundTo` address on the origin chain.\n\n- `INTENTS` - assets are refunded to the `refundTo` Intents account.\n\n- `CONFIDENTIAL_INTENTS` - assets are refunded to the `refundTo` Confidential Intents account. On Confidential Intents, 1Click settles refunds via signed transfer intent rather than direct token transfer."},"recipient":{"type":"string","description":"Recipient address. The format must match `recipientType`."},"recipientType":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of recipient address:\n\n- `DESTINATION_CHAIN` - assets are transferred to the chain of `destinationAsset`.\n\n- `INTENTS` - assets are transferred to an account inside Intents.\n\n- `CONFIDENTIAL_INTENTS` - assets are transferred to the `recipient` account inside Confidential Intents. On Confidential Intents, 1Click settles swap output via signed transfer intent rather than direct token transfer."},"virtualChainRecipient":{"description":"EVM address of a transfer recipient in a virtual chain.","nullable":true,"type":"string"},"virtualChainRefundRecipient":{"description":"EVM address of a refund recipient in a virtual chain.","nullable":true,"type":"string"},"deadline":{"type":"string","description":"Timestamp in ISO format indicating when refunds begin if the swap is not completed by then."},"connectedWallets":{"description":"Addresses of connected wallets.","nullable":true,"type":"array","items":{"type":"string"}},"referral":{"description":"Referral identifier (lowercase only). It is reflected in on-chain data and public analytics platforms.","nullable":true,"type":"string"},"appFees":{"description":"List of recipients and their fees.","nullable":true,"type":"array","items":{"type":"object","properties":{"recipient":{"type":"string","description":"Fee recipient identifier or address."},"fee":{"type":"number","description":"Fee amount in basis points (1/100th of a percent)."}},"required":["recipient","fee"],"additionalProperties":false}},"customRecipientMsg":{"description":"HIGHLY EXPERIMENTAL: message to pass to `ft_transfer_call` when withdrawing assets to NEAR.\n\nOtherwise, `ft_transfer` will be used.\n\nWARNING: Funds can be lost if used with non NEP-141 tokens, with insufficient `storage_deposit`, or if the recipient does not implement `ft_on_transfer`.","nullable":true,"type":"string"}},"required":["dry","swapType","depositType","amount","originAsset","destinationAsset","slippageTolerance","refundTo","refundType","recipient","recipientType","deadline"],"additionalProperties":false,"description":"User request payload."},"quote":{"type":"object","properties":{"timeEstimate":{"type":"number","description":"Estimated swap completion time in seconds."},"deadline":{"description":"Timestamp in ISO format indicating when the quote becomes inactive and refunds may begin.","type":"string"},"timeWhenInactive":{"description":"Timestamp in ISO format indicating when the deposit address becomes inactive.","type":"string"},"depositAddress":{"description":"Unique deposit address to which the origin asset must be transferred to initiate the swap.","type":"string"},"depositMemo":{"description":"Additional memo required together with `depositAddress` for memo-based deposit chains.","type":"string"},"chainDepositAddresses":{"description":"Deposit addresses across all bridge-supported blockchains for funding the same Intents account.\n\nPresent only for public `ANY_INPUT` quotes (`depositType` `INTENTS`); the confidential rail does not support multi-chain funding addresses yet.\n\nEach entry forwards to the Intents account in `depositAddress`, so the user may deposit from any listed chain.","type":"array","items":{"type":"object","properties":{"blockchain":{"type":"string","enum":["near","eth","base","arb","btc","sol","ton","dash","doge","xrp","zec","gnosis","bera","bsc","pol","tron","sui","movement","op","avax","stellar","aptos","cardano","ltc","xlayer","monad","bch","adi","plasma","scroll","starknet","aleo","hypercore"],"description":"Blockchain on which this deposit address accepts funds."},"address":{"type":"string","description":"Deposit address on `blockchain`. Funds sent here are credited to the same Intents account."},"memo":{"description":"Memo required together with `address` on chains that need it (e.g. Stellar, XRP, TON).","type":"string"}},"required":["blockchain","address"],"additionalProperties":false}},"amountIn":{"type":"string","description":"Input amount in the smallest unit of the origin asset."},"amountInFormatted":{"type":"string","description":"Human-readable formatted input amount."},"amountInUsd":{"type":"string","description":"Estimated USD value of the input amount."},"minAmountIn":{"type":"string","description":"Minimum accepted input amount for the quote."},"maxAmountIn":{"description":"Maximum accepted input amount for the quote, when applicable.","type":"string"},"amountOut":{"type":"string","description":"Expected output amount in the smallest unit of the destination asset."},"amountOutFormatted":{"type":"string","description":"Human-readable formatted output amount."},"amountOutUsd":{"type":"string","description":"Estimated USD value of the output amount."},"minAmountOut":{"type":"string","description":"Minimum guaranteed output amount for the quote."},"refundFee":{"description":"Fee charged for refunding assets to the refund address, in the smallest unit of the origin asset.","nullable":true,"type":"string"},"withdrawFee":{"description":"Fee charged for withdrawing assets to the recipient, in the smallest unit of the destination asset. This fee is already accounted for in the final `amountOut`.","nullable":true,"type":"string"},"virtualChainRecipient":{"description":"EVM address of a transfer recipient in a virtual chain.","nullable":true,"type":"string"},"virtualChainRefundRecipient":{"description":"EVM address of a refund recipient in a virtual chain.","nullable":true,"type":"string"},"customRecipientMsg":{"description":"HIGHLY EXPERIMENTAL: message passed to `ft_transfer_call` when withdrawing assets to NEAR. Otherwise, `ft_transfer` is used.","nullable":true,"type":"string"}},"required":["timeEstimate","amountIn","amountInFormatted","amountInUsd","minAmountIn","amountOut","amountOutFormatted","amountOutUsd","minAmountOut"],"additionalProperties":false,"description":"Quote details, including deposit instructions and expected swap amounts."}},"required":["timestamp","signature","quoteRequest","quote"],"additionalProperties":false,"description":"Quote response from the original request."},"status":{"type":"string","enum":["KNOWN_DEPOSIT_TX","PENDING_DEPOSIT","INCOMPLETE_DEPOSIT","PROCESSING","SUCCESS","REFUNDED","FAILED"],"description":"Current execution status of the swap:\n\n- `KNOWN_DEPOSIT_TX` - a deposit transaction is known but not yet confirmed.\n\n- `PENDING_DEPOSIT` - waiting for the deposit to arrive at the deposit address.\n\n- `INCOMPLETE_DEPOSIT` - the deposit received is below the required amount.\n\n- `PROCESSING` - the deposit was received and the swap is being processed.\n\n- `SUCCESS` - the swap completed successfully.\n\n- `REFUNDED` - the deposit was refunded.\n\n- `FAILED` - the swap failed."},"updatedAt":{"type":"string","description":"Timestamp in ISO format when the state was last updated."},"swapDetails":{"type":"object","properties":{"intentHashes":{"type":"array","items":{"type":"string"},"description":"All intent hashes that took part in this swap."},"nearTxHashes":{"type":"array","items":{"type":"string"},"description":"All NEAR transactions executed for this swap."},"amountIn":{"description":"Exact amount of `originToken` after the trade was settled.","nullable":true,"type":"string"},"amountInFormatted":{"description":"Exact amount of `originToken` in readable format after the trade was settled.","nullable":true,"type":"string"},"amountInUsd":{"description":"Exact amount of `originToken` equivalent in USD.","nullable":true,"type":"string"},"amountOut":{"description":"Exact amount of `destinationToken` after the trade was settled.","nullable":true,"type":"string"},"amountOutFormatted":{"description":"Exact amount of `destinationToken` in readable format after the trade was settled.","nullable":true,"type":"string"},"amountOutUsd":{"description":"Exact amount of `destinationToken` equivalent in USD.","nullable":true,"type":"string"},"slippage":{"description":"Actual slippage.","nullable":true,"type":"number"},"originChainTxHashes":{"type":"array","items":{"type":"object","properties":{"hash":{"type":"string","description":"Transaction hash."},"explorerUrl":{"type":"string","description":"Explorer URL for the transaction."}},"required":["hash","explorerUrl"],"additionalProperties":false},"description":"Hashes and explorer URLs for all transactions on the origin chain."},"destinationChainTxHashes":{"type":"array","items":{"type":"object","properties":{"hash":{"type":"string","description":"Transaction hash."},"explorerUrl":{"type":"string","description":"Explorer URL for the transaction."}},"required":["hash","explorerUrl"],"additionalProperties":false},"description":"Hashes and explorer URLs for all transactions on the destination chain."},"refundedAmount":{"description":"Amount of `originAsset` transferred to `refundTo`.","nullable":true,"type":"string"},"refundedAmountFormatted":{"description":"Refunded amount in readable format.","nullable":true,"type":"string"},"refundedAmountUsd":{"description":"Refunded amount equivalent in USD.","nullable":true,"type":"string"},"refundReason":{"description":"Reason for refund.","nullable":true,"type":"string"},"depositedAmount":{"description":"Amount deposited to `depositAddress` onchain.","nullable":true,"type":"string"},"depositedAmountFormatted":{"description":"Amount deposited in readable format.","nullable":true,"type":"string"},"depositedAmountUsd":{"description":"Amount deposited equivalent in USD.","nullable":true,"type":"string"},"referral":{"description":"Referral identifier.","nullable":true,"type":"string"}},"required":["intentHashes","nearTxHashes","originChainTxHashes","destinationChainTxHashes"],"additionalProperties":false,"description":"Details of actual swaps and withdrawals."}},"required":["correlationId","quoteResponse","status","updatedAt","swapDetails"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","enum":["KNOWN_DEPOSIT_TX","PENDING_DEPOSIT","INCOMPLETE_DEPOSIT","PROCESSING","SUCCESS","REFUNDED","FAILED"],"description":"Current execution status of the swap:\n\n- `KNOWN_DEPOSIT_TX` - a deposit transaction is known but not yet confirmed.\n\n- `PENDING_DEPOSIT` - waiting for the deposit to arrive at the deposit address.\n\n- `INCOMPLETE_DEPOSIT` - the deposit received is below the required amount.\n\n- `PROCESSING` - the deposit was received and the swap is being processed.\n\n- `SUCCESS` - the swap completed successfully.\n\n- `REFUNDED` - the deposit was refunded.\n\n- `FAILED` - the swap failed."}},"required":["status"],"additionalProperties":false,"description":"Status of a confidential swap (`quoteRequest.confidentiality` is `basic` or `advanced`).\n\nOnly the execution status is exposed; quote and swap details are withheld on the confidential rail."}],"description":"Swap execution status retrieved successfully"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"API error or invalid request parameters"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"500":{"description":"Internal Server Error - Failed to reach the status service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach the status service"}}}}}}}}}
```


# Get transactions history

## GET /api/transactions/{apiKey}

> Get recent transaction history

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/transactions/{apiKey}":{"get":{"tags":["transactions"],"description":"Get recent transaction history","parameters":[{"schema":{"default":10,"type":"integer","exclusiveMinimum":true,"maximum":1000},"in":"query","name":"numberOfTransactions","required":false,"description":"Number of transactions to return. Default: 10. Max: 1000. Min: 1. Ignored when `page`/`perPage` are supplied."},{"schema":{"type":"integer","exclusiveMinimum":true,"maximum":9007199254740991},"in":"query","name":"page","required":false,"description":"Legacy pagination: page number. Supplying `page` or `perPage` returns the paginated response shape. Default: 1. Min: 1."},{"schema":{"type":"integer","exclusiveMinimum":true,"maximum":1000},"in":"query","name":"perPage","required":false,"description":"Legacy pagination: number of transactions per page. Supplying `page` or `perPage` returns the paginated response shape. Default: 50. Max: 1000. Min: 1."},{"schema":{"type":"string"},"in":"query","name":"walletAddress","required":false,"description":"Wallet address used for the query."},{"schema":{"type":"string"},"in":"query","name":"lastDepositAddress","required":false,"description":"Cursor: the depositAddress of the last transaction from the previous page."},{"schema":{"type":"string"},"in":"query","name":"lastDepositMemo","required":false,"description":"Cursor: the depositMemo of the last transaction from the previous page."},{"schema":{"type":"string","enum":["next","prev"]},"in":"query","name":"direction","required":false,"description":"Pagination direction. \"next\" returns older transactions, \"prev\" newer ones. Default: next."},{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Transactions successfully retrieved","content":{"application/json":{"schema":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"originAsset":{"type":"string","description":"ID of the origin asset."},"destinationAsset":{"type":"string","description":"ID of the destination asset."},"depositAddress":{"type":"string","description":"Deposit address associated with the swap."},"depositMemo":{"nullable":true,"description":"Deposit memo associated with the deposit address, if required.","type":"string"},"depositAddressAndMemo":{"type":"string","description":"Combined deposit identifier composed of deposit address and memo when applicable."},"recipient":{"type":"string","description":"Recipient address for the swap output."},"status":{"type":"string","enum":["FAILED","INCOMPLETE_DEPOSIT","PENDING_DEPOSIT","PROCESSING","REFUNDED","SUCCESS"],"description":"Current status of the transaction."},"createdAt":{"type":"string","description":"Transaction creation timestamp in ISO 8601 format."},"createdAtTimestamp":{"type":"number","description":"Transaction creation timestamp as Unix time."},"intentHashes":{"type":"string","description":"Intent hash or hashes associated with the swap."},"referral":{"type":"string","description":"Referral identifier associated with the transaction."},"amountInFormatted":{"type":"string","description":"Human-readable formatted input amount."},"amountOutFormatted":{"type":"string","description":"Human-readable formatted output amount."},"appFees":{"type":"array","items":{"type":"object","properties":{"fee":{"type":"number","description":"Fee amount in basis points (1/100th of a percent)."},"recipient":{"type":"string","description":"Fee recipient identifier or address."}},"required":["fee","recipient"],"additionalProperties":false},"description":"Application fees applied to the transaction."},"nearTxHashes":{"type":"array","items":{"type":"string"},"description":"Related NEAR transaction hashes."},"originChainTxHashes":{"type":"array","items":{"type":"string"},"description":"Related transaction hashes on the origin chain."},"destinationChainTxHashes":{"type":"array","items":{"type":"string"},"description":"Related transaction hashes on the destination chain."},"amountIn":{"type":"string","description":"Input amount in the smallest unit of the origin asset."},"amountInUsd":{"type":"string","description":"Estimated USD value of the input amount."},"amountOut":{"type":"string","description":"Output amount in the smallest unit of the destination asset."},"amountOutUsd":{"type":"string","description":"Estimated USD value of the output amount."},"refundTo":{"type":"string","description":"Refund recipient address."},"senders":{"type":"array","items":{"type":"string"},"description":"Sender addresses associated with the transaction."},"refundReason":{"nullable":true,"description":"Reason for refund, if the transaction was refunded.","type":"string"},"refundFeeFormatted":{"nullable":true,"description":"Human-readable formatted refund fee.","type":"string"},"refundFee":{"nullable":true,"description":"Refund fee in the smallest unit of the asset.","type":"string"},"recipientType":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of recipient address."},"depositType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of deposit address."},"refundType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of refund address."}},"required":["originAsset","destinationAsset","depositAddress","depositMemo","depositAddressAndMemo","recipient","status","createdAt","createdAtTimestamp","intentHashes","referral","amountInFormatted","amountOutFormatted","appFees","nearTxHashes","originChainTxHashes","destinationChainTxHashes","amountIn","amountInUsd","amountOut","amountOutUsd","refundTo","senders","refundReason","refundFeeFormatted","refundFee","recipientType","depositType","refundType"],"additionalProperties":false},"description":"List of transactions."},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"originAsset":{"type":"string","description":"ID of the origin asset."},"destinationAsset":{"type":"string","description":"ID of the destination asset."},"depositAddress":{"type":"string","description":"Deposit address associated with the swap."},"depositMemo":{"nullable":true,"description":"Deposit memo associated with the deposit address, if required.","type":"string"},"depositAddressAndMemo":{"type":"string","description":"Combined deposit identifier composed of deposit address and memo when applicable."},"recipient":{"type":"string","description":"Recipient address for the swap output."},"status":{"type":"string","enum":["FAILED","INCOMPLETE_DEPOSIT","PENDING_DEPOSIT","PROCESSING","REFUNDED","SUCCESS"],"description":"Current status of the transaction."},"createdAt":{"type":"string","description":"Transaction creation timestamp in ISO 8601 format."},"createdAtTimestamp":{"type":"number","description":"Transaction creation timestamp as Unix time."},"intentHashes":{"type":"string","description":"Intent hash or hashes associated with the swap."},"referral":{"type":"string","description":"Referral identifier associated with the transaction."},"amountInFormatted":{"type":"string","description":"Human-readable formatted input amount."},"amountOutFormatted":{"type":"string","description":"Human-readable formatted output amount."},"appFees":{"type":"array","items":{"type":"object","properties":{"fee":{"type":"number","description":"Fee amount in basis points (1/100th of a percent)."},"recipient":{"type":"string","description":"Fee recipient identifier or address."}},"required":["fee","recipient"],"additionalProperties":false},"description":"Application fees applied to the transaction."},"nearTxHashes":{"type":"array","items":{"type":"string"},"description":"Related NEAR transaction hashes."},"originChainTxHashes":{"type":"array","items":{"type":"string"},"description":"Related transaction hashes on the origin chain."},"destinationChainTxHashes":{"type":"array","items":{"type":"string"},"description":"Related transaction hashes on the destination chain."},"amountIn":{"type":"string","description":"Input amount in the smallest unit of the origin asset."},"amountInUsd":{"type":"string","description":"Estimated USD value of the input amount."},"amountOut":{"type":"string","description":"Output amount in the smallest unit of the destination asset."},"amountOutUsd":{"type":"string","description":"Estimated USD value of the output amount."},"refundTo":{"type":"string","description":"Refund recipient address."},"senders":{"type":"array","items":{"type":"string"},"description":"Sender addresses associated with the transaction."},"refundReason":{"nullable":true,"description":"Reason for refund, if the transaction was refunded.","type":"string"},"refundFeeFormatted":{"nullable":true,"description":"Human-readable formatted refund fee.","type":"string"},"refundFee":{"nullable":true,"description":"Refund fee in the smallest unit of the asset.","type":"string"},"recipientType":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of recipient address."},"depositType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of deposit address."},"refundType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of refund address."}},"required":["originAsset","destinationAsset","depositAddress","depositMemo","depositAddressAndMemo","recipient","status","createdAt","createdAtTimestamp","intentHashes","referral","amountInFormatted","amountOutFormatted","appFees","nearTxHashes","originChainTxHashes","destinationChainTxHashes","amountIn","amountInUsd","amountOut","amountOutUsd","refundTo","senders","refundReason","refundFeeFormatted","refundFee","recipientType","depositType","refundType"],"additionalProperties":false},"description":"Paginated list of transactions."},"totalPages":{"type":"number","description":"Total number of available pages."},"page":{"type":"number","description":"Current page number."},"perPage":{"type":"number","description":"Number of transactions returned per page."},"total":{"type":"number","description":"Total number of transactions matching the query."},"nextPage":{"nullable":true,"description":"Next page number, or `null` if there is no next page.","type":"number"},"prevPage":{"nullable":true,"description":"Previous page number, or `null` if there is no previous page.","type":"number"}},"required":["data","totalPages","page","perPage","total","nextPage","prevPage"],"additionalProperties":false}],"description":"Transactions successfully retrieved"}}}},"404":{"description":"Application key is not assigned","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[404]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Application key is not assigned"}}}},"429":{"description":"Explorer rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"number","enum":[429]},"message":{"type":"string"}},"required":["statusCode","message"],"additionalProperties":false,"description":"Explorer rate limit exceeded"}}}},"500":{"description":"Internal Server Error - Failed to get transactions from explorer service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to get transactions from explorer service"}}}}}}}}}
```


# Confidential Swaps API Reference


# Authenticate user with signed data

## POST /api/auth/authenticate/{apiKey}

> Authenticate a user with signed data. Verifies the wallet signature and issues session tokens (access and refresh).

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/auth/authenticate/{apiKey}":{"post":{"tags":["auth"],"description":"Authenticate a user with signed data. Verifies the wallet signature and issues session tokens (access and refresh).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"signedData":{"anyOf":[{"type":"object","properties":{"standard":{"type":"string","enum":["nep413"],"description":"Message signing standard identifier."},"public_key":{"type":"string","description":"Base58-encoded Ed25519 public key (prefixed with `ed25519:`)."},"signature":{"type":"string","description":"Base58-encoded Ed25519 signature (prefixed with `ed25519:`)."},"payload":{"type":"object","properties":{"recipient":{"type":"string","description":"Intended recipient of the signed message."},"nonce":{"type":"string","description":"Base64-encoded nonce used for replay protection."},"message":{"type":"string","description":"Stringified JSON message that was signed, containing `deadline`, `signer_id`, and `intents`."},"callbackUrl":{"description":"Optional callback URL included in the signed payload.","nullable":true,"type":"string"}},"required":["recipient","nonce","message"],"description":"NEP-413 compliant payload that was signed."}},"required":["standard","public_key","signature","payload"]},{"type":"object","properties":{"standard":{"type":"string","enum":["erc191"],"description":"Message signing standard identifier."},"signature":{"type":"string","description":"Base58-encoded Secp256k1 signature (prefixed with `secp256k1:`). The public key is recovered from the signature via `ecrecover()`."},"payload":{"type":"string","description":"Stringified JSON ERC-191 message payload containing `signer_id`, `verifying_contract`, `nonce`, `deadline`, and `intents`."}},"required":["standard","signature","payload"]}],"description":"Signed message payload. Supports multiple signing standards (for example `nep413` and `erc191`), discriminated by the `standard` field."}},"required":["signedData"]}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Signature verification succeeded; tokens issued","content":{"application/json":{"schema":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT access token for authenticated API calls."},"refreshToken":{"type":"string","description":"JWT refresh token used to obtain new access tokens."},"expiresIn":{"type":"number","description":"Access token lifetime in seconds."},"refreshExpiresIn":{"type":"number","description":"Refresh token lifetime in seconds."}},"required":["accessToken","refreshToken","expiresIn","refreshExpiresIn"],"additionalProperties":false,"description":"Signature verification succeeded; tokens issued"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"Invalid signed data structure or encoding"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"401":{"description":"Signature verification failed","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[401]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Signature verification failed"}}}},"500":{"description":"Internal Server Error - Failed to reach the auth service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach the auth service"}}}}}}}}}
```


# Refresh access token

## POST /api/auth/refresh/{apiKey}

> Exchange a refresh token for a new access token.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/auth/refresh/{apiKey}":{"post":{"tags":["auth"],"description":"Exchange a refresh token for a new access token.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","description":"Refresh token obtained from the authenticate endpoint."}},"required":["refreshToken"]}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"New access token issued","content":{"application/json":{"schema":{"type":"object","properties":{"accessToken":{"type":"string","description":"New JWT access token."},"expiresIn":{"type":"number","description":"Access token lifetime in seconds."}},"required":["accessToken","expiresIn"],"additionalProperties":false,"description":"New access token issued"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"Invalid request parameters"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"401":{"description":"Invalid or expired refresh token","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[401]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Invalid or expired refresh token"}}}},"500":{"description":"Internal Server Error - Failed to reach the auth service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach the auth service"}}}}}}}}}
```


# Get user token balances

## GET /api/account/balances/{apiKey}

> Get token balances for the authenticated user from private balance sources. Requires the user session token (JWT) issued by the authenticate endpoint, passed as \`Authorization: Bearer \<token>\`.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/account/balances/{apiKey}":{"get":{"tags":["account"],"description":"Get token balances for the authenticated user from private balance sources. Requires the user session token (JWT) issued by the authenticate endpoint, passed as `Authorization: Bearer <token>`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"tokenIds","required":false,"description":"Comma-separated list of token IDs to query. If empty, returns all non-zero balances."},{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Token balances retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"balances":{"type":"array","items":{"type":"object","properties":{"tokenId":{"type":"string","description":"Token identifier."},"available":{"type":"string","description":"Available balance in the smallest unit (string for precision)."},"source":{"type":"string","enum":["private"],"description":"Balance source."}},"required":["tokenId","available","source"],"additionalProperties":false},"description":"List of token balances for the authenticated user."}},"required":["balances"],"additionalProperties":false,"description":"Token balances retrieved successfully"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"API error or invalid request parameters"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"401":{"description":"User session token is invalid or expired","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[401]}},"required":["message","statusCode"],"additionalProperties":false,"description":"User session token is invalid or expired"}}}},"500":{"description":"Internal Server Error - Failed to reach the account service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach the account service"}}}}}}}}}
```


# Get transaction history

## GET /api/account/history/{apiKey}

> Get paginated transaction history for the authenticated user. Requires the user session token (JWT) issued by the authenticate endpoint, passed as \`Authorization: Bearer \<token>\`. For the initial request, omit both cursors to retrieve the latest history; for subsequent requests, pass either \`nextCursor\` (newer) or \`prevCursor\` (older).

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/account/history/{apiKey}":{"get":{"tags":["account"],"description":"Get paginated transaction history for the authenticated user. Requires the user session token (JWT) issued by the authenticate endpoint, passed as `Authorization: Bearer <token>`. For the initial request, omit both cursors to retrieve the latest history; for subsequent requests, pass either `nextCursor` (newer) or `prevCursor` (older).","parameters":[{"schema":{"type":"string"},"in":"query","name":"prevCursor","required":false,"description":"Pass the `prevCursor` value from a previous response to fetch older items. Omit both cursors for the initial request. Do not pass together with `nextCursor`."},{"schema":{"type":"string"},"in":"query","name":"nextCursor","required":false,"description":"Pass the `nextCursor` value from a previous response to poll for newer items. Omit both cursors for the initial request. Do not pass together with `prevCursor`."},{"schema":{"type":"array","items":{"type":"string","enum":["PENDING_DEPOSIT","INCOMPLETE_DEPOSIT","PROCESSING","SUCCESS","REFUNDED","FAILED"]}},"in":"query","name":"status","required":true,"description":"Filter by one or more execution statuses."},{"schema":{"type":"integer","minimum":1,"maximum":100},"in":"query","name":"limit","required":false,"description":"Maximum number of items to return per page."},{"schema":{"type":"string"},"in":"query","name":"depositAddress","required":false,"description":"Filter by deposit address."},{"schema":{"type":"string"},"in":"query","name":"depositMemo","required":false,"description":"Filter by deposit memo. Only has effect when `depositAddress` is also provided."},{"schema":{"type":"string"},"in":"query","name":"search","required":false,"description":"Search by deposit address, recipient, sender, or tx hash."},{"schema":{"type":"array","items":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"]}},"in":"query","name":"depositType","required":true,"description":"Filter by one or more deposit types."},{"schema":{"type":"array","items":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"]}},"in":"query","name":"recipientType","required":true,"description":"Filter by one or more recipient types."},{"schema":{"type":"array","items":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"]}},"in":"query","name":"refundType","required":true,"description":"Filter by one or more refund types."},{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Transaction history retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string","enum":["PENDING_DEPOSIT","INCOMPLETE_DEPOSIT","PROCESSING","SUCCESS","REFUNDED","FAILED"],"description":"Execution status of the swap."},"depositType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of deposit address."},"recipientType":{"type":"string","enum":["DESTINATION_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of recipient address."},"createdAt":{"type":"string","description":"Creation timestamp in ISO 8601 format."},"depositAddress":{"type":"string","description":"Deposit address for the swap."},"depositMemo":{"description":"Deposit memo, when the deposit chain requires one.","nullable":true,"type":"string"},"originAsset":{"description":"ID of the origin asset.","type":"string"},"amountInFormatted":{"description":"Human-readable input amount.","type":"string"},"amountInUsd":{"description":"Estimated USD value of the input amount.","type":"string"},"destinationAsset":{"description":"ID of the destination asset.","type":"string"},"amountOutFormatted":{"description":"Human-readable output amount.","type":"string"},"amountOutUsd":{"description":"Estimated USD value of the output amount.","type":"string"},"recipient":{"description":"Recipient address.","type":"string"},"quoteTransactions":{"description":"Deposit transactions with sender and tx hash.","type":"array","items":{"type":"object","properties":{"sender":{"description":"Sender address of the deposit transaction.","type":"string"},"txHash":{"description":"Transaction hash of the deposit.","type":"string"}},"additionalProperties":false}},"refundTo":{"description":"Address used for refunds.","type":"string"},"refundType":{"type":"string","enum":["ORIGIN_CHAIN","INTENTS","CONFIDENTIAL_INTENTS"],"description":"Type of refund address."},"refundReason":{"description":"Reason for the refund, null when no refund occurred.","nullable":true,"type":"string"},"refundedAmountFormatted":{"description":"Human-readable refunded amount.","type":"string"},"refundedAmountUsd":{"description":"Estimated USD value of the refunded amount.","type":"string"},"refundFee":{"description":"Refund fee in the smallest unit of the origin asset.","nullable":true,"type":"string"},"refundFeeFormatted":{"description":"Human-readable refund fee.","type":"string"}},"required":["status","depositType","recipientType","createdAt","depositAddress","refundType"],"additionalProperties":false},"description":"Transaction history items for the current page."},"nextCursor":{"description":"Pass it back as `nextCursor` to poll for newer items.","type":"string"},"prevCursor":{"description":"Pass it back as `prevCursor` to fetch older items.","type":"string"}},"required":["items"],"additionalProperties":false,"description":"Transaction history retrieved successfully"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"API error or invalid request parameters"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"401":{"description":"User session token is invalid or expired","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[401]}},"required":["message","statusCode"],"additionalProperties":false,"description":"User session token is invalid or expired"}}}},"500":{"description":"Internal Server Error - Failed to reach the account service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach the account service"}}}}}}}}}
```


# Persistent Addresses API Reference


# Create persistent deposit address

## POST /api/persistent-deposit-address/{apiKey}

> Returns a chain-specific deposit address that funnels deposits into the recipient's account. The same address is returned for repeat calls with the same API key + recipient + sender + depositChain + destinationChain/destinationAsset, so users can safely save it externally. A different API key (or \`destinationAsset\`/\`destinationChain\`) yields a different deposit address and a separate underlying Intents account.\
> \
> \> \*\*EVM chains share one address.\*\* Across different EVM chains you only need to generate the deposit address \*\*once\*\* using any EVM chain as \`depositChain\`. The returned address works automatically for deposits on every other supported EVM chain — no need to call this endpoint again per EVM chain.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/persistent-deposit-address/{apiKey}":{"post":{"tags":["deposit"],"description":"Returns a chain-specific deposit address that funnels deposits into the recipient's account. The same address is returned for repeat calls with the same API key + recipient + sender + depositChain + destinationChain/destinationAsset, so users can safely save it externally. A different API key (or `destinationAsset`/`destinationChain`) yields a different deposit address and a separate underlying Intents account.\n\n> **EVM chains share one address.** Across different EVM chains you only need to generate the deposit address **once** using any EVM chain as `depositChain`. The returned address works automatically for deposits on every other supported EVM chain — no need to call this endpoint again per EVM chain.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"recipient":{"type":"string","minLength":1,"description":"Target address on the destination chain. Funds deposited to the returned address will ultimately route to this address."},"sender":{"type":"string","minLength":1,"description":"Identifier of the user the deposit address is generated for (e.g. their ID in the system). A unique deposit address is generated and persisted per (sender, recipient, depositChain), so incoming deposits can be attributed to a specific user. A single underlying Intents account is shared per (sender, recipient) across all chains; only the deposit address is chain-specific."},"depositChain":{"type":"string","enum":["eth","bera","base","gnosis","arb","bsc","avax","op","pol","monad","adi","plasma","scroll","xlayer","sui","xrp","btc","doge","tron","ton","near","sol","zec","ltc","cardano","stellar","aleo","bch","dash","starknet","coca","evm"],"description":"Chain the user deposits FROM (the chain of the returned deposit address). Short code matching the widget config Chains enum, e.g. \"arb\" for Arbitrum. The special value \"evm\" is accepted as a shortcut for any EVM chain — all EVM chains share one deposit address — and resolves to Base (\"base\"). For \"stellar\", the response includes a `memo` that deposits must carry."},"destinationChain":{"type":"string","enum":["eth","bera","base","gnosis","arb","bsc","avax","op","pol","monad","adi","plasma","scroll","xlayer","sui","xrp","btc","doge","tron","ton","near","sol","zec","ltc","cardano","stellar","aleo","bch","dash","starknet","coca"],"description":"Chain the user receives funds ON. Used to resolve `destinationAsset` when it is provided as a symbol. Short code matching the widget config Chains enum, e.g. \"base\" for Base."},"destinationAsset":{"type":"string","minLength":1,"description":"Asset the user wants to receive on `destinationChain`. Accepts either a token symbol (e.g. \"USDC\") or an asset ID (e.g. \"nep141:base-0x...omft.near\"). When a symbol is used and more than one token shares that symbol on `destinationChain`, the request is rejected with 400 and the caller must switch to the asset ID."}},"required":["recipient","sender","depositChain","destinationChain","destinationAsset"],"description":"To specify the asset the user receives, you can either:\n- Pass a token **symbol** in `destinationAsset` (e.g. \"USDC\") together with `destinationChain` (e.g. \"base\"). The API resolves the symbol to an asset ID on that chain.\n- Or pass an **asset ID** directly in `destinationAsset` (e.g. \"nep141:base-0x...omft.near\"). `destinationChain` is still required to locate it.\n\nWhen a symbol matches more than one token on the same `destinationChain`, the request fails with `400` and an error listing the matching asset IDs (\"Multiple tokens with symbol ... Use one of the asset IDs instead: ...\"). In that case, retry with the specific asset ID you want."}}},"required":true,"description":"To specify the asset the user receives, you can either:\n- Pass a token **symbol** in `destinationAsset` (e.g. \"USDC\") together with `destinationChain` (e.g. \"base\"). The API resolves the symbol to an asset ID on that chain.\n- Or pass an **asset ID** directly in `destinationAsset` (e.g. \"nep141:base-0x...omft.near\"). `destinationChain` is still required to locate it.\n\nWhen a symbol matches more than one token on the same `destinationChain`, the request fails with `400` and an error listing the matching asset IDs (\"Multiple tokens with symbol ... Use one of the asset IDs instead: ...\"). In that case, retry with the specific asset ID you want."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Persistent deposit address generated or fetched from cache","content":{"application/json":{"schema":{"type":"object","properties":{"depositAddress":{"type":"string","description":"Chain-specific deposit address. Sending any supported asset on this chain to this address will deposit funds into the NEAR Intents account associated with `recipient`."},"alreadyExists":{"type":"boolean","description":"True when this exact deposit address already existed and was already user-requested (`requested = true`) before this call — i.e. the call was a cache hit on a previously generated address rather than a new generation."},"memo":{"description":"Deposit memo required by some chains (currently only Stellar). Deposits to `depositAddress` on such chains MUST include this memo or the funds are not credited. Omitted for chains that do not use a memo.","type":"string"},"correlationId":{"description":"Trace identifier of the 1Click quote made to create this address, for debugging upstream latency and errors with the 1Click team. Only present when this call actually quoted 1Click — it is omitted when the address was served from an existing record, and 1Click does not always return one.","type":"string"}},"required":["depositAddress","alreadyExists"],"additionalProperties":false,"description":"Persistent deposit address generated or fetched from cache"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message returned by the upstream quote API."},"correlationId":{"type":"string","description":"Unique identifier for request tracing and debugging."},"timestamp":{"type":"string","description":"Timestamp in ISO format when the error response was generated."},"path":{"type":"string","description":"API path associated with the failed request."}},"required":["message","correlationId","timestamp","path"],"additionalProperties":false,"description":"API error, fee configuration is invalid, or `destinationAsset` symbol is ambiguous on `destinationChain`"},{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}]}}}},"404":{"description":"Application key is not assigned or no token matches `destinationAsset` on `destinationChain`","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[404]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Application key is not assigned or no token matches `destinationAsset` on `destinationChain`"}}}},"429":{"description":"A concurrent request is already creating this deposit address; retry","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false,"description":"A concurrent request is already creating this deposit address; retry"}}}},"500":{"description":"Internal Server Error - Failed to call upstream services","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to call upstream services"}}}}}}}}}
```


# Get persistent deposit status

## GET /api/persistent-deposit-status/{apiKey}

> Returns deposits for a persistent deposit address in a unified shape. \`type\` selects the source and status filter: \`received\`, \`success\`, \`failed\`. \`address\` is a deposit address returned by this API; the chain is resolved from the stored record.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/persistent-deposit-status/{apiKey}":{"get":{"tags":["deposit"],"description":"Returns deposits for a persistent deposit address in a unified shape. `type` selects the source and status filter: `received`, `success`, `failed`. `address` is a deposit address returned by this API; the chain is resolved from the stored record.","parameters":[{"schema":{"type":"string","enum":["received","success","failed"]},"in":"query","name":"type","required":true,"description":"Which deposits to return:\n- `received` — deposits that reached the Intents account.\n- `success` — completed outbound withdrawals to the recipient.\n- `failed` — failed outbound withdrawals to the recipient."},{"schema":{"type":"string","minLength":1},"in":"query","name":"address","required":true,"description":"A deposit address returned by this API. The chain is resolved internally from the stored record."},{"schema":{"type":"integer","exclusiveMinimum":true,"maximum":9007199254740991},"in":"query","name":"limit","required":false,"description":"Max deposits to return per page (POA pagination, `received`)."},{"schema":{"type":"integer","minimum":0,"maximum":9007199254740991},"in":"query","name":"offset","required":false,"description":"Number of deposits to skip (POA pagination, `received`)."},{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"Deposits in a unified shape","content":{"application/json":{"schema":{"type":"object","properties":{"deposits":{"type":"array","items":{"type":"object","properties":{"tx_hash":{"type":"string"},"fromChain":{"description":"Origin chain alias (e.g. \"arb\"). Set for `process` deposits.","nullable":true,"type":"string"},"destinationChain":{"description":"Destination chain alias (e.g. \"base\"). Set for `success`/`failed` withdrawals.","nullable":true,"type":"string"},"asset_id":{"nullable":true,"type":"string"},"decimals":{"nullable":true,"description":"Token decimals. Null when it cannot be resolved.","type":"number"},"amount":{"type":"string"},"from":{"type":"string"},"created_at":{"type":"string"},"intents_account":{"type":"string"},"deposit_address":{"type":"string","description":"Deposit address (generated by POA) where the user sent funds."},"recipient":{"type":"string","description":"Target end account that receives the funds."}},"required":["tx_hash","asset_id","decimals","amount","created_at","intents_account","deposit_address","recipient"],"additionalProperties":false}}},"required":["deposits"],"additionalProperties":false,"description":"Deposits in a unified shape"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}}}},"404":{"description":"Application key is not assigned or no record exists for the given address","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[404]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Application key is not assigned or no record exists for the given address"}}}},"500":{"description":"Internal Server Error - Failed to reach an upstream service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach an upstream service"}}}}}}}}}
```


# Get persistent address data

## GET /api/persistent-deposit-addresses/{apiKey}

> Returns a paginated, filterable, sortable list of the persistent deposit addresses created with this API key. Use \`/api/persistent-deposit-address-data/:apiKey\` to fetch a single address by its value.

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/persistent-deposit-addresses/{apiKey}":{"get":{"tags":["deposit"],"description":"Returns a paginated, filterable, sortable list of the persistent deposit addresses created with this API key. Use `/api/persistent-deposit-address-data/:apiKey` to fetch a single address by its value.","parameters":[{"schema":{"type":"string","minLength":1},"in":"query","name":"recipient","required":false,"description":"Filter by exact recipient (destination chain address)."},{"schema":{"type":"string","minLength":1},"in":"query","name":"sender","required":false,"description":"Filter by exact sender (the integrator-supplied user id)."},{"schema":{"type":"string","enum":["sui","xrp","btc","doge","tron","ton","near","sol","zec","ltc","cardano","stellar","aleo","bch","dash","starknet","coca","evm"]},"in":"query","name":"depositChain","required":false,"description":"Filter by deposit chain. A non-EVM shortcut (e.g. \"near\", \"sol\", \"btc\") matches that chain exactly; the special value \"evm\" matches addresses created on any EVM chain."},{"schema":{"type":"string","enum":["eth","bera","base","gnosis","arb","bsc","avax","op","pol","monad","adi","plasma","scroll","xlayer","sui","xrp","btc","doge","tron","ton","near","sol","zec","ltc","cardano","stellar","aleo","bch","dash","starknet","coca"]},"in":"query","name":"recipientChain","required":false,"description":"Filter by the chain the funds are received on (any shortcut, EVM chains separately). Resolved against the tokens list: matches addresses whose destination asset lives on this chain."},{"schema":{"type":"string","minLength":1},"in":"query","name":"destinationAsset","required":false,"description":"Filter by destination token SYMBOL (e.g. \"USDC\"). When a symbol maps to multiple asset ids, addresses for ALL of them are returned."},{"schema":{"default":"created","type":"string","enum":["created","used"]},"in":"query","name":"sort","required":false,"description":"Sort order (newest first). \"created\" sorts by created_at then last_time_used; \"used\" sorts by last_time_used then created_at."},{"schema":{"default":1,"type":"integer","exclusiveMinimum":true,"maximum":9007199254740991},"in":"query","name":"page","required":false,"description":"1-based page number. Default: 1."},{"schema":{"default":50,"type":"integer","exclusiveMinimum":true,"maximum":1000},"in":"query","name":"perPage","required":false,"description":"Items per page. Default: 50. Max: 1000."},{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"A paginated page of deposit address records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"depositAddress":{"type":"string","description":"Chain-specific deposit address."},"recipient":{"type":"string","description":"Target address on the destination chain."},"sender":{"type":"string","description":"Integrator-supplied user id the address was generated for."},"depositChain":{"nullable":true,"description":"Deposit chain in shortcut format. EVM chains share one address, so any EVM deposit chain is reported as the single value \"evm\".","type":"string"},"destinationSymbol":{"nullable":true,"description":"Symbol of the destination asset the address is bound to. Null for legacy rows whose asset could not be resolved.","type":"string"},"recipientChain":{"nullable":true,"description":"Chain the funds are received on (the chain the destination asset lives on), in shortcut format. Null for legacy rows whose asset could not be resolved.","type":"string"},"memo":{"description":"Deposit memo required by some chains (currently only Stellar). Deposits to this address on such chains MUST include it. Omitted for chains that do not use a memo.","type":"string"},"createdAt":{"type":"string","description":"When the address was first created (ISO 8601)."},"lastTimeUsed":{"type":"string","description":"When the address was last requested (ISO 8601)."}},"required":["depositAddress","recipient","sender","depositChain","destinationSymbol","recipientChain","createdAt","lastTimeUsed"],"additionalProperties":false},"description":"Page of deposit address records."},"page":{"type":"number","description":"Current page number."},"perPage":{"type":"number","description":"Items per page."},"total":{"type":"number","description":"Total records matching the query."},"totalPages":{"type":"number","description":"Total number of pages."},"nextPage":{"nullable":true,"description":"Next page number, or null if none.","type":"number"},"prevPage":{"nullable":true,"description":"Previous page number, or null if none.","type":"number"}},"required":["data","page","perPage","total","totalPages","nextPage","prevPage"],"additionalProperties":false,"description":"A paginated page of deposit address records"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}}}},"404":{"description":"Application key is not assigned","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[404]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Application key is not assigned"}}}},"500":{"description":"Internal Server Error - Failed to reach an upstream service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach an upstream service"}}}}}}}}}
```

## GET /api/persistent-deposit-address-data/{apiKey}

> Returns the details of a single persistent deposit address by its value, scoped to this API key (404 if the address was not created with this key).

```json
{"openapi":"3.0.3","info":{"title":"@aurora-is-near/intents-fee-service","version":"0.0.1"},"servers":[{"url":"https://intents-api.aurora.dev"}],"security":[],"paths":{"/api/persistent-deposit-address-data/{apiKey}":{"get":{"tags":["deposit"],"description":"Returns the details of a single persistent deposit address by its value, scoped to this API key (404 if the address was not created with this key).","parameters":[{"schema":{"type":"string","minLength":1},"in":"query","name":"address","required":true,"description":"The exact deposit address to look up. Must belong to this API key (otherwise 404)."},{"schema":{"type":"string"},"in":"path","name":"apiKey","required":true,"description":"API key generated at [https://studio.aurora.dev](https://studio.aurora.dev)"}],"responses":{"200":{"description":"The deposit address record","content":{"application/json":{"schema":{"type":"object","properties":{"depositAddress":{"type":"string","description":"Chain-specific deposit address."},"recipient":{"type":"string","description":"Target address on the destination chain."},"sender":{"type":"string","description":"Integrator-supplied user id the address was generated for."},"depositChain":{"nullable":true,"description":"Deposit chain in shortcut format. EVM chains share one address, so any EVM deposit chain is reported as the single value \"evm\".","type":"string"},"destinationSymbol":{"nullable":true,"description":"Symbol of the destination asset the address is bound to. Null for legacy rows whose asset could not be resolved.","type":"string"},"recipientChain":{"nullable":true,"description":"Chain the funds are received on (the chain the destination asset lives on), in shortcut format. Null for legacy rows whose asset could not be resolved.","type":"string"},"memo":{"description":"Deposit memo required by some chains (currently only Stellar). Deposits to this address on such chains MUST include it. Omitted for chains that do not use a memo.","type":"string"},"createdAt":{"type":"string","description":"When the address was first created (ISO 8601)."},"lastTimeUsed":{"type":"string","description":"When the address was last requested (ISO 8601)."}},"required":["depositAddress","recipient","sender","depositChain","destinationSymbol","recipientChain","createdAt","lastTimeUsed"],"additionalProperties":false,"description":"The deposit address record"}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number"}},"required":["message","statusCode"],"additionalProperties":false}}}},"404":{"description":"Application key is not assigned, or no address matches `address` for this key","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[404]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Application key is not assigned, or no address matches `address` for this key"}}}},"500":{"description":"Internal Server Error - Failed to reach an upstream service","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"error":{"type":"string"},"data":{"type":"object","additionalProperties":{}},"statusCode":{"type":"number","enum":[500]}},"required":["message","statusCode"],"additionalProperties":false,"description":"Internal Server Error - Failed to reach an upstream service"}}}}}}}}}
```


# Intents Connect API Reference


# List supported tokens

## List supported tokens

> Returns filtered input and output token lists fetched from 1click and cached by the API.

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/supported_tokens":{"get":{"description":"Returns filtered input and output token lists fetched from 1click and cached by the API.","parameters":[{"description":"Optional flow filter. Use outOperation to swap supported input/output token lists for EVM-origin out-operation flows.","in":"query","name":"flow","schema":{"enum":["inOperation","outOperation"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.tokenResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Gateway"}},"summary":"List supported tokens","tags":["Tokens"]}}},"components":{"schemas":{"controllers.tokenResponse":{"properties":{"result":{"$ref":"#/components/schemas/controllers.supportedTokensResult"}},"type":"object"},"controllers.supportedTokensResult":{"properties":{"in":{"items":{"$ref":"#/components/schemas/controllers.token"},"type":"array"},"out":{"items":{"$ref":"#/components/schemas/controllers.token"},"type":"array"}},"type":"object"},"controllers.token":{"properties":{"assetId":{"type":"string"},"blockchain":{"type":"string"},"contractAddress":{"type":"string"},"decimals":{"type":"integer"},"price":{"type":"number"},"priceUpdatedAt":{"type":"string"},"symbol":{"type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# Fetch executions

## GET /api/v1/executions/{wallet}

> List executions for a wallet

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/executions/{wallet}":{"get":{"parameters":[{"description":"Origin wallet identifier.","in":"path","name":"wallet","required":true,"schema":{"type":"string"}},{"description":"Optional execution UUID filter. Accepts a single UUID or comma-separated UUIDs.","in":"query","name":"id","schema":{"type":"string"}},{"description":"Optional execution status filter. Accepts a single status or comma-separated statuses.","in":"query","name":"status","schema":{"enum":["CREATED","DEPOSIT_PENDING","DEPOSIT_PROCESSING","OPERATION_PENDING","OPERATION_PROCESSING","SUCCESS","DEPOSIT_FAILED","OPERATION_FAILED","EXPIRED"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.executionListResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Internal Server Error"}},"summary":"List executions for a wallet","tags":["Executions"]}}},"components":{"schemas":{"controllers.executionListResponse":{"properties":{"result":{"items":{"$ref":"#/components/schemas/controllers.executionDoc"},"type":"array"}},"type":"object"},"controllers.executionDoc":{"properties":{"createdAt":{"type":"string"},"details":{"$ref":"#/components/schemas/controllers.executionDetailsDoc"},"executionMode":{"enum":["quote_with_steps","steps_only"],"type":"string"},"id":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"quote":{"$ref":"#/components/schemas/controllers.executionQuoteDoc"},"status":{"enum":["CREATED","DEPOSIT_PENDING","DEPOSIT_PROCESSING","OPERATION_PENDING","OPERATION_PROCESSING","SUCCESS","DEPOSIT_FAILED","OPERATION_FAILED","EXPIRED"],"type":"string"},"steps":{"description":"Steps are ExecutionStepEVM objects for EVM executions. For Solana executions\n(type=solana) each item is an executionStepSolanaDoc instead, echoed under this\nsame steps key.","items":{"$ref":"#/components/schemas/controllers.executionStepEVMDoc"},"type":"array"},"type":{"enum":["evm","solana"],"type":"string"},"version":{"type":"string"}},"type":"object"},"controllers.executionDetailsDoc":{"properties":{"estimatedTime":{"type":"string"},"intermediaryAddress":{"type":"string"},"messageSigned":{"type":"boolean"},"messageToSign":{"type":"string"},"networkFee":{"type":"string"},"payload":{"$ref":"#/components/schemas/controllers.signingPayloadDoc"},"signingStandard":{"enum":["raw_ed25519","nep413","erc191","tip191","sep53","ton_connect"],"type":"string"}},"type":"object"},"controllers.signingPayloadDoc":{"properties":{"payload_bytes_base64":{"type":"string"},"payload_json":{"type":"string"},"standard":{"enum":["raw_ed25519","nep413","erc191","tip191","sep53","ton_connect"],"type":"string"}},"type":"object"},"controllers.executionQuoteDoc":{"properties":{"amount":{"type":"string"},"amountIn":{"type":"string"},"amountInUsd":{"type":"string"},"amountOut":{"type":"string"},"amountOutUsd":{"type":"string"},"deadline":{"type":"string"},"depositAddress":{"type":"string"},"depositMemo":{"type":"string"},"destinationAsset":{"type":"string"},"minAmountIn":{"type":"string"},"minAmountOut":{"type":"string"},"originAsset":{"type":"string"},"recipient":{"type":"string"},"swapType":{"enum":["EXACT_INPUT","EXACT_OUTPUT"],"type":"string"}},"type":"object"},"controllers.executionStepEVMDoc":{"properties":{"functionSignature":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"parameters":{"items":{"type":"object"},"type":"array"},"to":{"type":"string"},"value":{"type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# Request an execution

## Create a quote-backed execution

> Creates or dry-runs a quote-backed execution. In non-dry mode the response includes the MPC intent payload that the frontend must sign. The destination type selects the step shape - EVM destinations send ExecutionStepEVM objects, Solana destinations (type=solana) send ExecutionStepSolana objects under the same steps key. The type field accepts only evm or solana and defaults to evm when omitted - any other value is rejected with 400. Bridge-in requests execute steps on the destination chain after the 1click deposit succeeds. Out-operation requests execute steps on the origin chain and transfer the resulting tokens to the 1click deposit address. Solana supports bridge-in and out-operation, plus a gasless SPL model where a relayer pays the network fee in the destination token. Step objects accept only their documented fields - a key from the other step shape, a differently cased spelling of a field, the same key twice, or any field not listed is rejected with 400. Anything else a client needs to carry belongs in the step metadata object, which the backend passes through untouched. Step payloads nested more than 15 containers deep are also rejected with 400, counting the steps array itself as the first level - the metadata object is exempt. A step functionSignature may nest tuples or arrays at most 6 levels deep and may be at most 1024 bytes. An EVM steps array may hold at most 30 steps and a Solana one at most 50. Request bodies are limited to 256 KB. For Stellar origins the response includes quote.depositMemo and the deposit transfer must include that memo or the bridge will not settle.

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/executions/{wallet}":{"post":{"description":"Creates or dry-runs a quote-backed execution. In non-dry mode the response includes the MPC intent payload that the frontend must sign. The destination type selects the step shape - EVM destinations send ExecutionStepEVM objects, Solana destinations (type=solana) send ExecutionStepSolana objects under the same steps key. The type field accepts only evm or solana and defaults to evm when omitted - any other value is rejected with 400. Bridge-in requests execute steps on the destination chain after the 1click deposit succeeds. Out-operation requests execute steps on the origin chain and transfer the resulting tokens to the 1click deposit address. Solana supports bridge-in and out-operation, plus a gasless SPL model where a relayer pays the network fee in the destination token. Step objects accept only their documented fields - a key from the other step shape, a differently cased spelling of a field, the same key twice, or any field not listed is rejected with 400. Anything else a client needs to carry belongs in the step metadata object, which the backend passes through untouched. Step payloads nested more than 15 containers deep are also rejected with 400, counting the steps array itself as the first level - the metadata object is exempt. A step functionSignature may nest tuples or arrays at most 6 levels deep and may be at most 1024 bytes. An EVM steps array may hold at most 30 steps and a Solana one at most 50. Request bodies are limited to 256 KB. For Stellar origins the response includes quote.depositMemo and the deposit transfer must include that memo or the bridge will not settle.","parameters":[{"description":"Origin wallet identifier. Supports EVM addresses, NEAR accounts, NEAR implicit accounts, Solana base58 public keys, Stellar G-addresses, TON user-friendly addresses (TON requires body.publicKey), and Tron base58 (T...) addresses.","in":"path","name":"wallet","required":true,"schema":{"type":"string"}},{"description":"API key generated at https://studio.aurora.dev","in":"header","name":"x-api-key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.createExecutionRequestDoc"}}},"description":"Execution request. For type=solana, each step is an executionStepSolanaDoc under the same steps key.","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.executionObjectResponse"}}},"description":"Dry-run execution quote."},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.executionObjectResponse"}}},"description":"Execution created and paired transaction prepared."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Missing or invalid api token"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Conflict"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Request body exceeds the configured size limit."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Gateway"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"No Solana durable nonce account available (bridge-in / out-op). Retry shortly."}},"summary":"Create a quote-backed execution","tags":["Executions"]}}},"components":{"schemas":{"controllers.createExecutionRequestDoc":{"properties":{"addressLookupTables":{"description":"AddressLookupTables are base58 Address Lookup Table account addresses\n(Solana destinations only). When set, the backend compresses matching step\naccounts into lookup indices and emits a v0 transaction so many-account\nactions fit under the packet size limit. Additive and ignored for EVM.","items":{"type":"string"},"type":"array"},"dry":{"type":"boolean"},"metadata":{"additionalProperties":{},"type":"object"},"outOperation":{"type":"boolean"},"publicKey":{"description":"PublicKey is the origin wallet's ed25519 key (ed25519:<base58>). Required for\nTON (address can't yield the pubkey) and must be the wallet's owner key. Ignored otherwise.","type":"string"},"quote":{"$ref":"#/components/schemas/controllers.createExecutionQuoteDoc"},"steps":{"description":"Steps are ExecutionStepEVM objects for EVM destinations. For Solana\ndestinations (type=solana) each step is an executionStepSolanaDoc instead,\nsent under this same steps key.","items":{"$ref":"#/components/schemas/controllers.executionStepEVMDoc"},"type":"array"},"type":{"description":"Type selects the step shape. Must be evm or solana, defaults to evm when omitted, any other value is rejected with 400.","enum":["evm","solana"],"type":"string"},"version":{"type":"string"}},"type":"object"},"controllers.createExecutionQuoteDoc":{"properties":{"amount":{"type":"string"},"deadline":{"type":"string"},"destinationAsset":{"type":"string"},"originAsset":{"type":"string"},"recipient":{"type":"string"},"slippageTolerance":{"type":"integer"},"swapType":{"enum":["EXACT_INPUT","EXACT_OUTPUT"],"type":"string"}},"type":"object"},"controllers.executionStepEVMDoc":{"properties":{"functionSignature":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"parameters":{"items":{"type":"object"},"type":"array"},"to":{"type":"string"},"value":{"type":"string"}},"type":"object"},"controllers.executionObjectResponse":{"properties":{"result":{"$ref":"#/components/schemas/controllers.executionDoc"}},"type":"object"},"controllers.executionDoc":{"properties":{"createdAt":{"type":"string"},"details":{"$ref":"#/components/schemas/controllers.executionDetailsDoc"},"executionMode":{"enum":["quote_with_steps","steps_only"],"type":"string"},"id":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"quote":{"$ref":"#/components/schemas/controllers.executionQuoteDoc"},"status":{"enum":["CREATED","DEPOSIT_PENDING","DEPOSIT_PROCESSING","OPERATION_PENDING","OPERATION_PROCESSING","SUCCESS","DEPOSIT_FAILED","OPERATION_FAILED","EXPIRED"],"type":"string"},"steps":{"description":"Steps are ExecutionStepEVM objects for EVM executions. For Solana executions\n(type=solana) each item is an executionStepSolanaDoc instead, echoed under this\nsame steps key.","items":{"$ref":"#/components/schemas/controllers.executionStepEVMDoc"},"type":"array"},"type":{"enum":["evm","solana"],"type":"string"},"version":{"type":"string"}},"type":"object"},"controllers.executionDetailsDoc":{"properties":{"estimatedTime":{"type":"string"},"intermediaryAddress":{"type":"string"},"messageSigned":{"type":"boolean"},"messageToSign":{"type":"string"},"networkFee":{"type":"string"},"payload":{"$ref":"#/components/schemas/controllers.signingPayloadDoc"},"signingStandard":{"enum":["raw_ed25519","nep413","erc191","tip191","sep53","ton_connect"],"type":"string"}},"type":"object"},"controllers.signingPayloadDoc":{"properties":{"payload_bytes_base64":{"type":"string"},"payload_json":{"type":"string"},"standard":{"enum":["raw_ed25519","nep413","erc191","tip191","sep53","ton_connect"],"type":"string"}},"type":"object"},"controllers.executionQuoteDoc":{"properties":{"amount":{"type":"string"},"amountIn":{"type":"string"},"amountInUsd":{"type":"string"},"amountOut":{"type":"string"},"amountOutUsd":{"type":"string"},"deadline":{"type":"string"},"depositAddress":{"type":"string"},"depositMemo":{"type":"string"},"destinationAsset":{"type":"string"},"minAmountIn":{"type":"string"},"minAmountOut":{"type":"string"},"originAsset":{"type":"string"},"recipient":{"type":"string"},"swapType":{"enum":["EXACT_INPUT","EXACT_OUTPUT"],"type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# Request steps execution

## Create a steps-only execution

> Creates or dry-runs an execution without a 1click quote. The intermediary is expected to already hold the destination token. Non-dry requests include a signing payload in result.details. The destination type selects the step shape - EVM destinations send ExecutionStepEVM objects, Solana destinations (type=solana) send ExecutionStepSolana objects under the same steps key. The type field accepts only evm or solana and defaults to evm when omitted - any other value is rejected with 400. Solana steps-only supports native SOL and SPL destinations, where the SPL path uses the gasless relayer model. Step objects accept only their documented fields - a key from the other step shape, a differently cased spelling of a field, the same key twice, or any field not listed is rejected with 400. Anything else a client needs to carry belongs in the step metadata object, which the backend passes through untouched. Step payloads nested more than 15 containers deep are also rejected with 400, counting the steps array itself as the first level - the metadata object is exempt. A step functionSignature may nest tuples or arrays at most 6 levels deep and may be at most 1024 bytes. An EVM steps array may hold at most 30 steps and a Solana one at most 50. Request bodies are limited to 256 KB.

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/executions/{wallet}/steps":{"post":{"description":"Creates or dry-runs an execution without a 1click quote. The intermediary is expected to already hold the destination token. Non-dry requests include a signing payload in result.details. The destination type selects the step shape - EVM destinations send ExecutionStepEVM objects, Solana destinations (type=solana) send ExecutionStepSolana objects under the same steps key. The type field accepts only evm or solana and defaults to evm when omitted - any other value is rejected with 400. Solana steps-only supports native SOL and SPL destinations, where the SPL path uses the gasless relayer model. Step objects accept only their documented fields - a key from the other step shape, a differently cased spelling of a field, the same key twice, or any field not listed is rejected with 400. Anything else a client needs to carry belongs in the step metadata object, which the backend passes through untouched. Step payloads nested more than 15 containers deep are also rejected with 400, counting the steps array itself as the first level - the metadata object is exempt. A step functionSignature may nest tuples or arrays at most 6 levels deep and may be at most 1024 bytes. An EVM steps array may hold at most 30 steps and a Solana one at most 50. Request bodies are limited to 256 KB.","parameters":[{"description":"Origin wallet identifier. Supports EVM addresses, NEAR accounts, NEAR implicit accounts, Solana base58 public keys, Stellar G-addresses, TON user-friendly addresses, and Tron base58 (T...) addresses.","in":"path","name":"wallet","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.createStepsExecutionRequestDoc"}}},"description":"Steps-only execution request. Steps items follow the destination type - executionStepEVMDoc for EVM, executionStepSolanaDoc for Solana (shown).","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.executionObjectResponse"}}},"description":"Dry-run steps-only execution estimate."},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.executionObjectResponse"}}},"description":"Steps-only execution created and paired transaction prepared."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"An execution for this wallet is already in progress."},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Request body exceeds the configured size limit."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Gateway"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"No Solana durable nonce account available (steps-only). Retry shortly."}},"summary":"Create a steps-only execution","tags":["Executions"]}}},"components":{"schemas":{"controllers.createStepsExecutionRequestDoc":{"properties":{"addressLookupTables":{"description":"AddressLookupTables are base58 Address Lookup Table account addresses for\nSolana destinations only (see createExecutionRequestDoc.AddressLookupTables).\nAdditive and ignored for EVM.","items":{"type":"string"},"type":"array"},"destinationAsset":{"type":"string"},"dry":{"type":"boolean"},"metadata":{"additionalProperties":{},"type":"object"},"publicKey":{"description":"PublicKey is the origin wallet's ed25519 key. Required for TON and ignored otherwise.","type":"string"},"steps":{"description":"Steps items follow the destination type. For Solana destinations\n(type=solana) each step is an executionStepSolanaDoc (shown here). For EVM\ndestinations each step is an executionStepEVMDoc, sent under this same steps\nkey.","items":{"$ref":"#/components/schemas/controllers.executionStepSolanaDoc"},"type":"array"},"type":{"description":"Type selects the step shape. Must be evm or solana, defaults to evm when omitted, any other value is rejected with 400.","enum":["evm","solana"],"type":"string"},"version":{"type":"string"}},"type":"object"},"controllers.executionStepSolanaDoc":{"properties":{"accounts":{"items":{"$ref":"#/components/schemas/controllers.solanaAccountMetaDoc"},"type":"array"},"args":{"items":{"$ref":"#/components/schemas/controllers.solanaArgDoc"},"type":"array"},"discriminator":{"description":"Discriminator is the hex-encoded instruction prefix: 8-byte Anchor\ndiscriminator or a 1-byte opcode. Omitted when the program takes none.","type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"programId":{"type":"string"}},"type":"object"},"controllers.solanaAccountMetaDoc":{"properties":{"isSigner":{"type":"boolean"},"isWritable":{"type":"boolean"},"pubkey":{"description":"Pubkey is a base58 account address or a placeholder sentinel\n({INTERMEDIARY}, {DEPOSIT_ADDRESS}) resolved before encoding.","type":"string"}},"type":"object"},"controllers.solanaArgDoc":{"properties":{"name":{"type":"string"},"type":{"enum":["u8","u16","u32","u64","u128","i8","i16","i32","i64","bool","pubkey","bytes","string"],"type":"string"},"value":{"description":"Value is the JSON literal for the arg (number, string, bool) or a\nplaceholder sentinel such as \"{INTERMEDIARY}\".","type":"object"}},"type":"object"},"controllers.executionObjectResponse":{"properties":{"result":{"$ref":"#/components/schemas/controllers.executionDoc"}},"type":"object"},"controllers.executionDoc":{"properties":{"createdAt":{"type":"string"},"details":{"$ref":"#/components/schemas/controllers.executionDetailsDoc"},"executionMode":{"enum":["quote_with_steps","steps_only"],"type":"string"},"id":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"quote":{"$ref":"#/components/schemas/controllers.executionQuoteDoc"},"status":{"enum":["CREATED","DEPOSIT_PENDING","DEPOSIT_PROCESSING","OPERATION_PENDING","OPERATION_PROCESSING","SUCCESS","DEPOSIT_FAILED","OPERATION_FAILED","EXPIRED"],"type":"string"},"steps":{"description":"Steps are ExecutionStepEVM objects for EVM executions. For Solana executions\n(type=solana) each item is an executionStepSolanaDoc instead, echoed under this\nsame steps key.","items":{"$ref":"#/components/schemas/controllers.executionStepEVMDoc"},"type":"array"},"type":{"enum":["evm","solana"],"type":"string"},"version":{"type":"string"}},"type":"object"},"controllers.executionDetailsDoc":{"properties":{"estimatedTime":{"type":"string"},"intermediaryAddress":{"type":"string"},"messageSigned":{"type":"boolean"},"messageToSign":{"type":"string"},"networkFee":{"type":"string"},"payload":{"$ref":"#/components/schemas/controllers.signingPayloadDoc"},"signingStandard":{"enum":["raw_ed25519","nep413","erc191","tip191","sep53","ton_connect"],"type":"string"}},"type":"object"},"controllers.signingPayloadDoc":{"properties":{"payload_bytes_base64":{"type":"string"},"payload_json":{"type":"string"},"standard":{"enum":["raw_ed25519","nep413","erc191","tip191","sep53","ton_connect"],"type":"string"}},"type":"object"},"controllers.executionQuoteDoc":{"properties":{"amount":{"type":"string"},"amountIn":{"type":"string"},"amountInUsd":{"type":"string"},"amountOut":{"type":"string"},"amountOutUsd":{"type":"string"},"deadline":{"type":"string"},"depositAddress":{"type":"string"},"depositMemo":{"type":"string"},"destinationAsset":{"type":"string"},"minAmountIn":{"type":"string"},"minAmountOut":{"type":"string"},"originAsset":{"type":"string"},"recipient":{"type":"string"},"swapType":{"enum":["EXACT_INPUT","EXACT_OUTPUT"],"type":"string"}},"type":"object"},"controllers.executionStepEVMDoc":{"properties":{"functionSignature":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"parameters":{"items":{"type":"object"},"type":"array"},"to":{"type":"string"},"value":{"type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# Fetch intermediary accounts

## GET /api/v1/executions/{wallet}/intermediary

> Derive intermediary addresses (EVM + Solana)

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/executions/{wallet}/intermediary":{"get":{"parameters":[{"description":"Origin wallet identifier. Supports EVM addresses, NEAR accounts, NEAR implicit accounts, Solana base58 public keys, Stellar G-addresses, TON user-friendly addresses, and Tron base58 (T...) addresses.","in":"path","name":"wallet","required":true,"schema":{"type":"string"}},{"description":"Origin wallet ed25519 public key (ed25519:<base58>). Required for TON wallets and must be the wallet's owner key (the address must re-derive from it). Ignored otherwise.","in":"query","name":"publicKey","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.intermediaryResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Gateway"}},"summary":"Derive intermediary addresses (EVM + Solana)","tags":["Executions"]}}},"components":{"schemas":{"controllers.intermediaryResponse":{"properties":{"result":{"$ref":"#/components/schemas/controllers.intermediaryResult"}},"type":"object"},"controllers.intermediaryResult":{"properties":{"evm":{"type":"string"},"originAccount":{"type":"string"},"originType":{"enum":["evm","near","solana","stellar","ton","tron"],"type":"string"},"solana":{"description":"Solana is the MPC-derived ed25519 Solana intermediary account. Null when\nSolana is disabled or the derivation failed.","type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# Delete an execution

## Delete an execution

> Removes an execution and its transactions. Allowed only for the wallet that owns the row, only in deletable statuses (CREATED, DEPOSIT\_PENDING, OPERATION\_PENDING, EXPIRED, DEPOSIT\_FAILED, OPERATION\_FAILED). SUCCESS rows cannot be deleted. Requires a signature over "delete\_execution:\<executionId>" valid for the route wallet.

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/executions/{wallet}/{executionId}":{"delete":{"description":"Removes an execution and its transactions. Allowed only for the wallet that owns the row, only in deletable statuses (CREATED, DEPOSIT_PENDING, OPERATION_PENDING, EXPIRED, DEPOSIT_FAILED, OPERATION_FAILED). SUCCESS rows cannot be deleted. Requires a signature over \"delete_execution:<executionId>\" valid for the route wallet.","parameters":[{"description":"Origin wallet identifier.","in":"path","name":"wallet","required":true,"schema":{"type":"string"}},{"description":"Execution UUID.","in":"path","name":"executionId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.deleteExecutionRequestDoc"}}},"description":"Signed delete request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.statusResultResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Conflict"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Internal Server Error"}},"summary":"Delete an execution","tags":["Executions"]}}},"components":{"schemas":{"controllers.deleteExecutionRequestDoc":{"properties":{"nep413":{"$ref":"#/components/schemas/controllers.deleteExecutionNEP413Doc"},"publicKey":{"type":"string"},"signature":{"type":"string"},"tonConnect":{"$ref":"#/components/schemas/controllers.tonConnectEnvelopeDoc"}},"type":"object"},"controllers.deleteExecutionNEP413Doc":{"properties":{"nonce":{"type":"string"},"recipient":{"type":"string"}},"type":"object"},"controllers.tonConnectEnvelopeDoc":{"properties":{"address":{"type":"string"},"domain":{"type":"string"},"timestamp":{"type":"integer"}},"type":"object"},"controllers.statusResultResponse":{"properties":{"result":{"$ref":"#/components/schemas/controllers.statusResult"}},"type":"object"},"controllers.statusResult":{"properties":{"status":{"type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# Submit digest

## Submit signed intent payload

> Verifies the frontend signature and stores the signed payload. If the execution is already OPERATION\_PENDING, the transaction moves to SIGNING. Otherwise it remains prepared until the deposit completes.

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/executions/{wallet}/submit":{"post":{"description":"Verifies the frontend signature and stores the signed payload. If the execution is already OPERATION_PENDING, the transaction moves to SIGNING. Otherwise it remains prepared until the deposit completes.","parameters":[{"description":"Origin wallet identifier.","in":"path","name":"wallet","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.submitSignatureRequestDoc"}}},"description":"Signed intent payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.statusResultResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Conflict"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Internal Server Error"}},"summary":"Submit signed intent payload","tags":["Executions"]}}},"components":{"schemas":{"controllers.submitSignatureRequestDoc":{"properties":{"executionId":{"type":"string"},"publicKey":{"type":"string"},"signature":{"type":"string"},"tonConnect":{"$ref":"#/components/schemas/controllers.tonConnectEnvelopeDoc"}},"type":"object"},"controllers.tonConnectEnvelopeDoc":{"properties":{"address":{"type":"string"},"domain":{"type":"string"},"timestamp":{"type":"integer"}},"type":"object"},"controllers.statusResultResponse":{"properties":{"result":{"$ref":"#/components/schemas/controllers.statusResult"}},"type":"object"},"controllers.statusResult":{"properties":{"status":{"type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# Submit deposit hash

## Record a deposit transaction hash

> Records the origin-chain deposit transaction hash and notifies 1click. For MEMO deposit-mode chains (e.g. Stellar) the memo is required: it identifies the execution together with the deposit address and is forwarded to 1click. The memo is returned in the create and list responses, and omitting it for a MEMO-mode deposit returns 404. SIMPLE-mode chains (unique deposit address) omit the memo.

```json
{"openapi":"3.0.3","info":{"title":"Intents Connect API","version":"1.0"},"servers":[{"url":"https://intents-connect-api.aurora.dev"}],"paths":{"/api/v1/executions/deposit/submit":{"post":{"description":"Records the origin-chain deposit transaction hash and notifies 1click. For MEMO deposit-mode chains (e.g. Stellar) the memo is required: it identifies the execution together with the deposit address and is forwarded to 1click. The memo is returned in the create and list responses, and omitting it for a MEMO-mode deposit returns 404. SIMPLE-mode chains (unique deposit address) omit the memo.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.submitDepositRequestDoc"}}},"description":"Deposit transaction","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.statusResultResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Bad Request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Conflict"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/controllers.errorResponse"}}},"description":"Internal Server Error"}},"summary":"Record a deposit transaction hash","tags":["Executions"]}}},"components":{"schemas":{"controllers.submitDepositRequestDoc":{"properties":{"depositAddress":{"type":"string"},"memo":{"description":"Memo identifies the execution together with depositAddress for MEMO deposit-mode chains (e.g. Stellar)","type":"string"},"txHash":{"type":"string"}},"type":"object"},"controllers.statusResultResponse":{"properties":{"result":{"$ref":"#/components/schemas/controllers.statusResult"}},"type":"object"},"controllers.statusResult":{"properties":{"status":{"type":"string"}},"type":"object"},"controllers.errorResponse":{"properties":{"error":{"type":"string"}},"type":"object"}}}}
```


# What is Swap Widget?

The **Intents Swap Widget** lets you integrate a fully functional, cross-chain swap interface into your application in just a few lines of code.

<figure><img src="/files/FZcYQUxiigb5vXKx7DvE" alt=""><figcaption></figcaption></figure>

## Features

### Networks

Select which blockchains are available in the widget.

* Supports multiple chains for cross-chain swaps
* Enable or disable networks with one click
* Network selection directly impacts routing and liquidity

Use this to focus the widget on specific ecosystems or supported chains.

### Tokens

Control which tokens users can trade.

* Define a shared token list across all selected networks
* Set a default sell token (e.g. USDT)
* Restrict tokens to simplify UX or guide usage

### Wallet Connection

Choose how users connect wallets.

* Standalone: built-in wallet support, works out of the box
* Dapp: uses your existing wallet connection

Use Standalone for simplicity, Dapp for full control.

### Fee Collection

Earn fees from swaps.

* Enable custom fees on top of protocol fees
* Configure per API key
* Automatically applied to each transaction

### Design

Styl&#x65;**:** Customise the visual appearance.

* Clean or Bold themes
* Adjustable colours (accent, background, states)

Layou&#x74;**:** Adjust structure and spacing.

* Corner radius options
* Toggle container wrapper

### Embedding

#### iFrame

* One-line integration using a generated link
* Works in any app, no framework required

#### React SDK

* Full control via code
* Configure behaviour, wallets, and UI dynamically

### API Keys

Manage widget instances and settings.

* Each key controls configuration and fees
* Create multiple keys for different use cases
* Safe to rotate or revoke

### Reports

Export and analyse usage.

* Download transaction history as CSV
* Filter by date range
* Track volume and fee revenue

## Next Steps

{% stepper %}
{% step %}

### Create API key

First, create an account and set up [API Keys & Fees](/intents-swap/api-keys-and-fees) for your integration.
{% endstep %}

{% step %}

### Integrate the widget

Go to [Widget integration](/intents-swap/widget-integration) page.
{% endstep %}
{% endstepper %}


# API Keys & Fees

{% hint style="info" %}
You can generate as many API keys as you need and use them across different distribution channels in [Intents Studio](https://studio.aurora.dev/).
{% endhint %}

Once you have created your API key, you can do [Widget integration](/intents-deposits/quickstart/widget-integration) or [API integration](/intents-deposits/quickstart/api-integration). The API key is not confidential, allowing its use in public-facing services such as websites.

### Fees

The collected fee is split 60/40 between the Integrator (60%) and Aurora (40%).

The minimum Aurora fee floor is 2 basis points, calculated as follows: Aurora Fee = max(2 bps, 40% of the Integrator fee).

The maximum fee set is 100 basis points.

You can modify fees related to each API key by navigating to the API keys tab and clicking Edit fees.

#### Examples

| Integrator Fee | Integrator Share (60%) | Aurora Fee Applied |
| -------------- | ---------------------- | ------------------ |
| 0 bps          | 0 bps                  | 2 bps              |
| 5 bps          | 3 bps                  | 2 bps              |
| 10 bps         | 6 bps                  | 4 bps              |
| 20 bps         | 12 bps                 | 8 bps              |

### Reports

In the Widget Studio, you can download a full report of the swaps that executed through your API keys. Log in, click Export code and go to Reports - you'll find Download CSV report there with a detailed breakdown and a full list of swaps linked to your API keys.


# Supported Chains

Our API is powered by NEAR Intents, enabling cross-chain asset discovery, routing, and execution through a unified interface.

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="50.65234375">‎</th><th>CHAIN</th><th>SOURCE</th><th>DESTINATION</th></tr></thead><tbody><tr><td><img src="/files/3TMJHPLDv9jPeaKoBCpq" alt="" data-size="line"></td><td>ADI</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/5MdN9CaOPOc5gL65WowM" alt="" data-size="line"></td><td>Aleo</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/Y4jp8lJt3VAmAhcZXlvS" alt="" data-size="line"></td><td>Arbitrum</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/PgR51HT4CoiG3hxGD1eE" alt="" data-size="line"></td><td>Aurora</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/dbRLOxgWBb5TKyHNfoRk" alt="" data-size="line"></td><td>Avalanche</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/eQCNu5AT5BDgRfnxLPof" alt="" data-size="line"></td><td>Base</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/SG75T9nLhPXLWVNHG7tA" alt="" data-size="line"></td><td>Bera</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/eV6tbERf5hoiw3OnclnS" alt="" data-size="line"></td><td>Binance Smart Chain</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/KyvtFMxJVw7U4iOtZS3v" alt="" data-size="line"></td><td>Bitcoin</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/xHrIedtsjNmnd2bSxdrn" alt="" data-size="line"></td><td>Bitcoin Cash</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/YSLVZFeIEeHfvCaiZJjO" alt="" data-size="line"></td><td>Cardano</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/uivorID6vldc3OnzIqiF" alt="" data-size="line"></td><td>Dash</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/MI0F9k03AHjvfu7k0CJ0" alt="" data-size="line"></td><td>Dogecoin</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/9GMNeb7aSeyjAl4BZAlm" alt="" data-size="line"></td><td>Ethereum</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/9To32s13yHKCpV4ocoQP" alt="" data-size="line"></td><td>Gnosis</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/KIz44F0jBmAIO57BpbRH" alt="" data-size="line"></td><td>Hyperliquid</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/FVO5fls0pdWU9xz4eEVB" alt="" data-size="line"></td><td>Litecoin</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/C5flpvND0b50ZVPXXoo5" alt="" data-size="line"></td><td>Monad</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/R1KCSwtvWIdUog0usGKv" alt="" data-size="line"></td><td>NEAR</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/hGMywzPPsG8DtLEpyY2b" alt="" data-size="line"></td><td>Optimism</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/EIAQtyX84IdcqBFNlRHr" alt="" data-size="line"></td><td>Plasma</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/qqth9R8ovyroAS1uKLj1" alt="" data-size="line"></td><td>Polygon</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/dpPqAgrtn7jhyvGsy50n" alt="" data-size="line"></td><td>Scroll</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/ga9kAWhqZXqPZU93WOC1" alt="" data-size="original"></td><td>Solana</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/kTiQWNy7oEyVHCLYT3kd" alt="" data-size="line"></td><td>Starknet</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/shBCBJE9ub6pvsJgC4hm" alt="" data-size="line"></td><td>Stellar</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/18ZAvVYV1TGltUgv2qMG" alt="" data-size="line"></td><td>Sui</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/scqD9LQbaRLO4ks8574N" alt="" data-size="line"></td><td>TON</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/cJsy0zs1wZ0h49sVZQRS" alt="" data-size="line"></td><td>Tron</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/JyErasHvx1dxp16FMg65" alt="" data-size="line"></td><td>XLayer</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/AZj68Bm65FieD7mV7NPl" alt="" data-size="line"></td><td>XRP</td><td>✅ Supported</td><td>✅ Supported</td></tr><tr><td><img src="/files/FKIbuoo07NQdpVADeUwF" alt="" data-size="line"></td><td>Zcash</td><td>✅ Supported</td><td>✅ Supported</td></tr></tbody></table>


# Widget integration

You can embed ready-to-be used **Swap Widget** directly into your app using an **iframe** or a **React component**.

{% stepper %}
{% step %}

### Create Intents Studio account

Navigate to [Widget Studio](https://studio.aurora.dev/) and log in with your email using the button in the top-right corner of the interface.
{% endstep %}

{% step %}

### Configure the widget

Make sure to use **Swap** Widget mode.

In the UI, you can configure settings such as the displayed Networks and Tokens, and how the Wallet Connection is handled.
{% endstep %}

{% step %}

### Configure the design

You can also customise the design to match your branding with different Styles, Accents, or Layouts. Reach out to our team for more advanced styling options.
{% endstep %}

{% step %}

### Embed the widget

Click the **Embed in your app** button on the top right of the interface, either through:

* An iframe by using **Generate a new link to embed** section
* Or React component using **Use React code snippet**
  {% endstep %}

{% step %}

### (Optional) Use advanced settings of the widget

If you decide to embed a React component, you can use advanced settings, such as your own wallet connection or use widget hooks. Check the detailed docs on the [Widget Configuration](/intents-swap/widget-configuration).
{% endstep %}
{% endstepper %}


# Widget Configuration


# Get started

The **Intents Swap Widget** is highly configurable, allowing developers to tailor its appearance and behavior to fit their application.

Configuration is handled via the `<WidgetConfigProvider>` component, which wraps your widget(s) and provides global settings.

### Setup

To configure the widget, pass a `config` object to the `WidgetConfigProvider`. This object defines core parameters such as the app identifier, default tokens, theme, and optional partner fee settings.

#### Example

```tsx
import {
  type WidgetConfig,
  WidgetConfigProvider,
  Widget,
} from '@aurora-is-near/intents-swap-widget';

const config: WidgetConfig = {
  connectedWallets: {
    default: '0x...',
    ton: 'ABC123',
  },
  appFees: [
    {
      recipient: 'your.account.near',
      fee: 25,
    },
  ],
};

export default function App() {
  return (
    <WidgetConfigProvider config={config}>
      <Widget />
    </WidgetConfigProvider>
  );
}
```

### Core options

The following options are available if you are not using standalone mode (i.e. you installed `@aurora-is-near/intents-swap-widget`).

#### `connectedWallets`

A map of connected wallet addresses keyed by chain. Used to determine which accounts can send or receive tokens on each network.

#### `providers`

The provider(s) for interacting with the `connectedWallets`. Used for signing messages.

#### `plugins`

At the time of writing, this property is used to provide network plugins, one per chain you want to support for native transfers.

**Example**

```tsx
import { evm } from '@aurora-is-near/intents-swap-widget-evm';
import { sol } from '@aurora-is-near/intents-swap-widget-solana';
import { stellar } from '@aurora-is-near/intents-swap-widget-stellar';

const config = {
  plugins: { evm, sol, stellar },
};
```

Install only the sibling packages for chains you actually use.

Note that NEAR support is built in and does not require a plugin.

See Wallet Connection for full examples.

#### `onWalletSignin`

Used to trigger wallet connection for main action button. If this function is not provided the button will have the label "Connect wallet" and not be clickable.

#### `onWalletSignout`

Used to sign out user's wallet. Currently used for compatibility check modal if a wallet is incompatible. NB: some wallets don't support programmatic logout make sure you guide user accordingly if required on your side.

#### `showProfileButton`

Show a profile button at the top of the widget via which you can connect (by calling `onWalletSignin`) or disconnect (by calling `onWalletSignout`).

### Common Options

The following options are available for all widget packages (i.e. you installed `@aurora-is-near/intents-swap-widget` or `@aurora-is-near/intents-swap-widget-standalone`):

#### `apiKey`

Your widget integration API Key. Visit [Intents Widget Studio](https://studio.aurora.dev) to obtain.

#### `referral`

Your application name that is used as an alias for quotes. Optional.

#### `enableAccountAbstraction`

Used to allow uses to deposit to and withdraw from your app's internal Intents account.

#### `walletSupportedChains`

A list of blockchain networks supported by the connected wallet(s).

If this is not provided we will attempt to establish the supported chains based on the format of the wallet address.

#### `sendAddress`

Optional fixed destination wallet. If not specified the widget will use the source wallet address as the receiver, by default.

#### `slippageTolerance`

The slippage tolerance for a transfer.

This value is defined in basis points (1/100th of a percent). For example, 100 for 1% slippage, or 50 for 0.5% slippage.

#### `enableAutoTokensSwitching`

When enabled, the widget automatically rotates the source and target tokens if the user selects the same token on both sides.

#### `refetchQuoteInterval`

The interval in milliseconds at which new quotes are fetched automatically. Useful for keeping market prices updated in volatile conditions.

#### `allowedTokensList`

Specifies the available tokens by their NEAR intents asset IDs or token symbols. It will only be possible to select tokens from this list in both the source and the target inputs.

#### `allowedSourceTokensList`

Specifies the available **source** tokens by their NEAR intents asset IDs or token symbols. It will only be possible to select tokens from this list in the source input.

#### `allowedTargetTokensList`

Specifies the available **target** tokens by their NEAR intents asset IDs or token symbols. It will only be possible to select tokens from this list in the target input.

#### `filterTokens`

A filter function applied to tokens in both the source and target lists. Return `true` to include the token, or `false` to exclude it.

This can be useful when we want to exclude, rather than include particular tokens. If you want to include particular tokens the `allowedTokensList` option might be more suitable.

#### `defaultSourceToken`

Predefine the default source token. Can be set to `null` to keep it not selected.

#### `defaultTargetToken`

Predefine the default target token. Can be set to `null` to keep it not selected.

#### `chainsOrder`

Defines the order in which supported chains are displayed.

Can be used to bring particular chains to the top of the list. Any chains not specified here will be sorted according to a default order.

**Example**

```ts
const config = {
  chainsOrder: ['eth', 'btc', 'near'],
}
```

#### `topChainShortcuts`

Defines top chain shortcuts that are visible to a user for quick access in tokens modal.

You can specify different lists of chains based on user's account type (wallet connected). Four chains must be specified to keep layout clean. If the following configuration attributes are set: `allowedChainsList`, `allowedSourceChainsList` or `allowedTargetChainsList` and a chain is not in those lists it's shortcut won't be displayed, be careful specifying multiple chain filters.

**Example**

```ts
const config = {
  // this is the default behaviour
  topChainShortcuts: (intentsAccountType) => {
    switch (intentsAccountType) {
      case 'evm':
        return ['eth', 'arb', 'avax', 'base'] as const;
      case 'sol':
        return ['sol', 'eth', 'btc', 'near'] as const;
      case 'near':
        return ['near', 'sol', 'eth', 'btc'] as const;
      default:
        return ['eth', 'btc', 'sol', 'near'] as const;
    }
  },
}
```

#### `allowedChainsList`

Restricts which chains that can be used when selecting source or target tokens.

**Example**

```ts
const config = {
  allowedChainsList: ['base', 'eth', 'ton'],
}
```

#### `allowedSourceChainsList`

Restricts which chains can be used when selecting **source** tokens.

**Example**

```ts
const config = {
  allowedSourceChainsList: ['base', 'eth'],
}
```

#### `allowedTargetChainsList`

Restricts which chains can be used when selecting **target** tokens.

**Example**

```ts
const config = {
  allowedTargetChainsList: ['ton'],
}
```

#### `chainsFilter`

Specify high-level categories of chains that should be displayed when selecting the source or target token.

You will probably find the internal defaults sufficient in most cases, which are driven by the `enableAccountAbstraction` option and whether or not there are any `connectedWallets`. However, this option exists for the case where you want more fine-grained control, for example, when building your own custom widget using our components.

**Example**

```ts
const config = {
  chainsFilter: {
    source: { external: 'wallet-supported', intents: 'none' },
    target: { external: 'all', intents: 'none' },
  },
};
```

#### `priorityAssets`

Defines the order in which tokens with no balance are displayed on top of the list.

If you want to promote certain tokens or just show most used on top of the list you can add them here and they will be displayed in a given order. Tokens that have balance always stay on top regardless. Tokens with no balance and not included in `priorityAssets` are sorted alphabetically at the bottom of the list.

The option accepts an array of arrays, where each child array defines either the chain ID and token symbol, or the asset ID.

**Example**

```ts
const config = {
  priorityAssets: [
    ['eth', 'ETH'],
    ['eth', 'USDT'],
  ],
};
```

or use asset IDs:

```ts
const config = {
  priorityAssets: [
    'nep141:eth.omft.near',
    'nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near',
  ],
};
```

or mix:

```ts
const config = {
  priorityAssets: [
    'nep141:eth.omft.near',
    ['eth', 'USDT'],
  ],
};
```

#### `fetchQuote`

A function used to implement custom quote fetching behaviour, overriding the default of calling the [1Click API quote endpoint](https://docs.near-intents.org/near-intents/integration/distribution-channels/1click-api#post-v0-quote).

For example, you might want to use this proxy quotes via your own API endpoint and insert some additional data based on your backend logic.

**Example**

```ts
const config = {
  fetchQuote: async (data, { signal }) => {
    const res = await axios.post(
    'https://my.proxy.com/quote',
      data,
      { signal },
    );

    return res.data;
  },
}
```

#### `fetchSourceTokens`

A function used to fetch a list of custom **source** tokens.

For example, you might want to make a call to some API endpoint.

**Example**

```ts
const config = {
  fetchSourceTokens: async () => {
    const res = await fetch('https://example.com/tokens');

    return res.json();
  },
}
```

#### `fetchTargetTokens`

A function used to fetch a list of custom **target** tokens.

For example, you might want to make a call to some API endpoint.

**Example**

```ts
const config = {
  fetchTargetTokens: async () => {
    const res = await fetch('https://example.com/tokens');

    return res.json();
  },
}
```

#### `appFees`

A list of recipients and their associated fees that will be applied to each swap or transfer.

**Properties**

* **`recipient` \[string]** Account ID within Intents to which this fee will be transferred.
* **`fee` \[number]** Fee for this recipient as part of amountIn in basis points (1/100th of a percent), for example, 100 for a 1% fee.

**Example**

```ts
const config = {
  appFees: [
    {
      recipient: 'recipient.near',
      fee: 100,
    }
  ]
}
```

#### `alchemyApiKey`

An API key for integrating with Alchemy.

This is useful for enabling more reliable balance fetching for EVM chains.

#### `tonCenterApiKey`

An API key for integrating with [TON Center](https://toncenter.com/).

This is useful for fetching balances for the TON chain.

#### `hideSendAddress`

Used to hide the send address when swapping or withdrawing.

#### `hideTokenInputHeadings`

Used to hide the headings on the token input boxes.

#### `themeParentElementSelector`

HTML element that defines CSS theming variables. If not set, the `body` element is used.

#### `lockSwapDirection`

By default, when using the swap widget, we can click the arrow in the middle to switch the swap direction. This option disables that feature.

#### `showTransactionHistory`

Enables swaps transaction history for a user's wallet. Disabled by default.

#### `disabledInternalBalanceTokens`

Filters out some tokens from internal balance list.

#### `showConversionPreview`

Enables live swap conversion preview in the tokens list on hover.

#### `extraQuoteParameters`

Allows you to pass extra attributes for each of the 1Click quote request. Includes: `virtualChainRecipient`, `virtualChainRefundRecipient`, `sessionId`. More information: [1Click API Documentation](https://docs.near-intents.org/api-reference/oneclick/request-a-swap-quote#body-virtual-chain-recipient)

#### `confidentialMode`

Allows you to configure how the widget supports [confidential intents](https://docs.intents.aurora.dev/confidential-intents). Allowed values: `public` - no confidential swaps, `confidential` - all swaps are confidential, `user-choice` - user may toggle confidential mode by themselves.


# Theming

The **Intents Swap Widget** can be themed in two main ways.

## Theme Object (Recommended)

The easiest way to theme the widget is by providing a `theme` object to the `WidgetConfigProvider`.

This automatically generates a full theme based on a small set of core colors.

### Example

```tsx
import { WidgetConfigProvider, Widget } from '@aurora-is-near/intents-swap-widget';

export default function App() {
  return (
    <WidgetConfigProvider
      config={{
        appName: 'My App',
        theme: {
          colorScheme: 'dark',
          accentColor: '#0098EA',
          backgroundColor: '#1E2337',
        },
      }}
    >
      <Widget />
    </WidgetConfigProvider>
  );
}
```

### Properties

| Property          | Type                             | Description                                                                                               |
| ----------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `colorScheme`     | `'light' \| 'dark'`              | Sets the overall color scheme                                                                             |
| `accentColor`     | `string`                         | Main accent color used for highlights, buttons, and active states                                         |
| `backgroundColor` | `string`                         | Colors used for backgrounds, text and other secondary elements (ignored when using the bold style preset) |
| `successColor`    | `string`                         | The color used for any success messages                                                                   |
| `warningColor`    | `string`                         | The color used for any warning messages                                                                   |
| `errorColor`      | `string`                         | The color used for any error messages                                                                     |
| `stylePreset`     | `'clean' \| 'bold'`              | Defines the way in which the colours are used to theme the widget                                         |
| `borderRadius`    | `'none' \| 'sm' \| 'md' \| 'lg'` | The size of the border radii used throughout the widget                                                   |
| `showContainer`   | `true \| false`                  | Swap a container around the widget                                                                        |

### Use our theme outside of widget

If you use Tailwind and want to apply the package styles for your custom app's elements you will need to add the `sw` class to some wrapping element within your app, for example:

```tsx
<div className="sw">
  <h1 className="text-sw-gray-500">My Amazing Widget</h1>
  <Widget />
</div>
```

You also need to import non-bundled theme files as not all the CSS classes are prebuilt.

```css
/* @import '@aurora-is-near/intents-swap-widget/styles.css'; */
@import '@aurora-is-near/intents-swap-widget/tailwind.css';
```

By default all theme variables are set to `body` element. You can change it by using `themeParentElementSelector` configuration option.

## CSS Variable Overrides (Advanced)

If you want to get even more granular, you can override the widget’s CSS variables directly. This method gives you complete control over colors, typography, spacing, borders, and fonts.

Our package uses Tailwind, but your app doesn't have to. The package exposes CSS variables to control styling, with each variable and its corresponding Tailwind token using the `sw-` prefix to avoid conflicts with your app's theme and variables.

To adjust theme, override these CSS variables in your app's stylesheet. The full list of available variables can be found at [`packages/intents-swap-widget/src/theme.css`](https://github.com/aurora-is-near/intents-swap-widget/blob/main/packages/intents-swap-widget/src/theme.css)

### Example

```css
@import '@aurora-is-near/intents-swap-widget/styles.css';

:root {
  /* Colors */
  --sw-gray-50: #ebedf5;
  --sw-gray-100: #b2b5c1;

  /* Spacing */
  --sw-space-xs: 2px;
  --sw-space-s: 4px;

  /* Border radius */
  --sw-radius-s: 4px;
  --sw-radius-m: 8px;

  /* Typography */
  --sw-font-sans: 'Inter', sans-serif;
  --sw-font-mono: 'JetBrains Mono', monospace;
}
```

#### Tailwind & CSS reset

If you use global CSS reset e.g.:

```css
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
```

It will break widget's styles due to Tailwind layering system. If you face that issue and see no paddings, margins or other style issues in a widget please scope your CSS resetting styles with a `@layer base` as below:

```css
@layer base {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}
```


# Wallet Connection

The **Intents Swap Widget** needs access to a wallet to sign transactions and fetch balances. You choose how that wallet connection happens: either the widget manages it for you, or you handle it yourself and pass the details in.

### Two Approaches

|                      | Built-in                                                                             | External                                                               |
| -------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| **How it works**     | Widget opens its own wallet modal (via AppKit)                                       | You connect the wallet and pass addresses + providers to the widget    |
| **Enable with**      | Install `@aurora-is-near/intents-swap-widget-standalone`                             | Pass `connectedWallets`, `providers`, and `plugins` (default behavior) |
| **Supported chains** | EVM (Ethereum, Arbitrum, Polygon, BSC, Optimism, Avalanche, Base) + Solana + Stellar | Any chain the widget supports, including NEAR and TON                  |
| **Setup effort**     | Minimal                                                                              | More code, but more control                                            |

### Built-in Wallet Connection

Install `@aurora-is-near/intents-swap-widget-standalone` (instead of `@aurora-is-near/intents-swap-widget`) and the widget will render its own connect-wallet button.

Under the hood it uses [AppKit by Reown](https://docs.reown.com/appkit/overview) to present a wallet modal supporting 50+ wallets via WalletConnect, plus Phantom and Solflare for Solana, [Stellar Wallets Kit](https://stellarwalletskit.dev/) for Stellar, and [NEAR Connect](https://www.npmjs.com/package/@hot-labs/near-connect) for NEAR.

#### Supported chains

Ethereum, Arbitrum, Polygon, BSC, Optimism, Avalanche, Base, Solana, Stellar, and NEAR.

#### Code example

```tsx
import {
  WidgetConfigProvider,
  Widget,
} from '@aurora-is-near/intents-swap-widget-standalone';

export default function App() {
  return (
    <WidgetConfigProvider>
      <Widget />
    </WidgetConfigProvider>
  );
}
```

That's it. No wallet hooks, no provider wiring, no connect button to build. The standalone package wires up the network plugins for EVM, Solana, and Stellar internally.

#### When to use

* You want the fastest path to a working swap widget.
* Your app doesn't already have wallet infrastructure.
* You're building a standalone page or prototype.

#### Limitations

* **No TON wallet support.** If your users need to swap from TON wallets, use external mode.
* **Less UI control.** The wallet modals are provided by AppKit, Stellar Wallets Kit, and NEAR Connect and you can't replace them with your own.
* **The `connectedWallets` prop is ignored.** In standalone mode, the widget uses the address from its own wallet connection.

### External Wallet Connection

This is the default mode when installing the `@aurora-is-near/intents-swap-widget` package. You manage the wallet connection on your side (using whatever library you prefer) and pass the connected addresses, raw wallet providers, and chain network plugins to the widget.

#### Key props

* **`connectedWallets`** — A map of wallet addresses keyed by chain (e.g. `{ default: '0x...', ton: 'UQ...' }`). The widget looks up the address for the selected token's chain, falling back to the `default` key.
* **`providers`** — The signing providers the widget uses to execute transactions. Accepts `evm`, `sol`, `stellar` and `near` keys.
* **`plugins`** — A network plugin per chain you want to support for transfers.
* **`onWalletSignin`** — Called when the user taps the action button while disconnected. Use this to trigger your own connect flow.
* **`onWalletSignout`** — Called when the widget needs to disconnect the wallet (e.g. during an incompatible-wallet check).

See the Configuration page for the full reference on each prop.

#### EVM example

```tsx
import {
  WidgetConfigProvider,
  Widget,
} from '@aurora-is-near/intents-swap-widget';
import { evm } from '@aurora-is-near/intents-swap-widget-evm';

export default function App() {
  const { address, connect, disconnect } = useYourEvmWallet();

  return (
    <WidgetConfigProvider
      config={{
        connectedWallets: { default: address },
        providers: { evm: window.ethereum },
        plugins: { evm },
        onWalletSignin: connect,
        onWalletSignout: disconnect,
      }}
    >
      <Widget />
    </WidgetConfigProvider>
  );
}
```

#### Solana example

```tsx
import { sol } from '@aurora-is-near/intents-swap-widget-solana';

const { publicKey, signMessage, signTransaction } = useYourSolanaWallet();

const config = {
  connectedWallets: { default: publicKey?.toBase58() },
  providers: {
    sol: { publicKey, signMessage, signTransaction },
  },
  plugins: { sol },
  onWalletSignin: connect,
  onWalletSignout: disconnect,
};
```

#### Solana example with Privy

[Privy](https://docs.privy.io/) wallets don't expose `signMessage` and `signTransaction` in the same shape the widget expects, so you need a small adapter. Here's a complete example:

```tsx
import { type Providers } from '@aurora-is-near/intents-swap-widget';
import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js';

function solanaProviderFromPrivy(
  privyWallet: PrivySolanaWallet,
): NonNullable<Providers['sol']> {
  const account = privyWallet.standardWallet.accounts.find(
    (a) => a.address === privyWallet.address,
  );

  return {
    publicKey: account?.publicKey
      ? new PublicKey(account.publicKey)
      : undefined,

    signMessage: async (message) => {
      const result = await privyWallet.signMessage({ message });
      return result.signature;
    },

    signTransaction: async (transaction) => {
      if (transaction instanceof VersionedTransaction) {
        const result = await privyWallet.signTransaction({
          transaction: transaction.serialize(),
        });
        return VersionedTransaction.deserialize(result.signedTransaction);
      }

      const result = await privyWallet.signTransaction({
        transaction: transaction.serialize({
          requireAllSignatures: false,
          verifySignatures: false,
        }),
      });
      return Transaction.from(result.signedTransaction);
    },
  } as NonNullable<Providers['sol']>;
}
```

Then pass it into the config along with the network plugin:

```tsx
const config = {
  connectedWallets: { default: privyWallet.address },
  providers: {
    sol: solanaProviderFromPrivy(privyWallet),
  },
  plugins: { sol },
};
```

#### Stellar example

```tsx
import { stellar } from '@aurora-is-near/intents-swap-widget-stellar';

const stellarWallet = useYourStellarWallet();

const config = {
  connectedWallets: { default: stellarWallet.address },
  providers: {
    stellar: {
      publicKey: stellarWallet.address,
      signMessage: stellarWallet.signMessage,
      signTransaction: stellarWallet.signTransaction,
    },
  },
  plugins: { stellar },
  onWalletSignin: connect,
  onWalletSignout: disconnect,
};
```

#### NEAR example

```tsx
const nearWallet = useYourNearWallet();

const config = {
  connectedWallets: { default: nearWallet.accountId },
  providers: {
    near: () => nearWallet,
  },
  onWalletSignin: connect,
  onWalletSignout: disconnect,
};
```

#### Multi-chain example

If your app supports multiple chains at once, pass all connected addresses and providers together:

```tsx
const config = {
  connectedWallets: {
    default: evmAddress,
    sol: solanaAddress,
    near: nearAccountId,
    ton: tonAddress,
  },
  providers: {
    evm: window.ethereum,
    sol: { publicKey, signMessage, signTransaction },
    near: () => nearWallet,
  },
  onWalletSignin: openWalletModal,
  onWalletSignout: disconnect,
};
```

The widget resolves which address to use based on the selected token's chain. If a chain-specific address isn't found, it falls back to `default`.

#### When to use

* Your app already manages wallet connections (e.g. via AppKit, Privy, TonConnect, or a custom setup).
* You need TON wallet support.
* You want full control over the connect/disconnect UI.
* You're building a multi-chain app where different wallets cover different chains.
* You want to keep your bundle small by only including support for the chains you actually support.

### Choosing the Right Approach

**Start with built-in** if you just want a working widget with minimal code and don't need TON wallets. You can always switch to external later.

**Use external** if any of these apply:

* You already have a wallet connection flow in your app.
* You need TON chain support.
* You want to control which wallet modal appears and when.
* You're connecting multiple wallets for different chains.
* You want fine-grained control over which chain SDKs are bundled.

Both approaches use the same widget components — the only difference is who manages the wallet lifecycle.


# Troubleshooting

This guide covers common issues when integrating the Intents Swap Widget.

If you can't find an answer here, please [open an issue](https://github.com/aurora-is-near/intents-swap-widget/issues) or reach out to the team.

### Table of Contents

* Balance Loading Issues
* Dependency Conflicts
* Wallet Connection Problems
* Configuration Errors

### Balance Loading Issues

#### Balances load infinitely

**Causes & Solutions:**

1. **Missing API key** - The widget will try to use a set of RPCs by default, but Alchemy is more reliable and you can have better control with Alchemy API key.

   ```tsx
   <SwapWidget
     alchemyApiKey="your-alchemy-api-key"
     // ...other props
   />
   ```
2. **API rate limits** - Alchemy free tier has request limits. Check your Alchemy dashboard for quota usage.
3. **TON balances not loading** - TON requires a separate API key.

   ```tsx
   <SwapWidget
     tonCenterApiKey="your-toncenter-api-key"
     // ...other props
   />
   ```
4. **RPC endpoint issues** - The widget retries failed RPC calls twice before giving up. If balances still fail, check network connectivity and RPC availability.

### Dependency Conflicts

#### Conflicting package versions

**Cause:** The widget uses several libraries that your project may also use. When versions differ, bundlers may include multiple versions causing conflicts.

**Solution:** Add resolutions to your `package.json` to lock versions, example:

```json
"resolutions": {
  "valtio": "2.1.7",
  "valtio-fsm": "1.0.0",
  "@noble/curves": "^1.6.0",
  "@noble/hashes": "^1.5.0",
  "strip-ansi": "6.0.1",
  "@reown/appkit": "1.8.17",
  "@reown/appkit-common": "1.8.17",
  "@reown/appkit-controllers": "1.8.17",
  "@reown/appkit-pay": "1.8.17",
  "@reown/appkit-polyfills": "1.8.17",
  "@reown/appkit-scaffold-ui": "1.8.17",
  "@reown/appkit-ui": "1.8.17",
  "@reown/appkit-utils": "1.8.17",
  "@reown/appkit-wallet": "1.8.17",
  "@solana/addresses": "5.5.1",
  "@solana/codecs-core": "5.5.1",
  "@solana/errors": "5.5.1",
  "@solana/keys": "5.5.1"
}
```

For **Yarn**, resolutions work as shown above. For **npm** or **bun**, use `overrides` instead:

```json
"overrides": {
  "valtio": "2.1.7"
}
```

Please refer to your package manager documentation for ways of doing this:

* [npm](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides)
* [yarn](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/)
* [pnpm](https://pnpm.io/9.x/package_json#resolutions)

### Wallet Connection Problems

#### Wallet not connecting

**Solutions:**

1. **Check provider configuration** - Ensure you're passing the correct provider for your account type:

   ```tsx
   // For EVM wallets
   providers={{ evm: window.ethereum }}

   // For Solana wallets
   providers={{ sol: solanaWallet }}

   // For Stellar wallets
   providers={{ stellar: stellarWallet }}

   // For NEAR wallets
   providers={{ near: () => nearWallet }}
   ```
2. **Add the matching network plugin** - For EVM, Solana, and Stellar swaps, you also need to install the relevant sibling package and register its network plugin via the `plugins` property.

   ```tsx
   import { evm } from '@aurora-is-near/intents-swap-widget-evm';
   import { sol } from '@aurora-is-near/intents-swap-widget-solana';
   import { stellar } from '@aurora-is-near/intents-swap-widget-stellar';

   <WidgetConfigProvider
     config={{
       plugins: { evm, sol, stellar },
       // ...
     }}
   >
   ```

Errors like `No EVM transfer configured` mean the plugin is missing:

Note that NEAR is built into widget core and does not need a plugin.

1. **Verify `walletSupportedChains`** - We will attempt to establish the supported chains based on the format of the wallet address, however, you may want to include chains your wallet supports:

   ```tsx
   walletSupportedChains={['eth', 'base', 'arb']}
   ```

### Configuration Errors

#### No styles/broken design

**Cause:** Missing CSS imports.

**Solution:** Import required CSS files:

```tsx
import '@aurora-is-near/intents-swap-widget/styles.css';
```

Check our detailed theming documentation.

#### Tailwind & CSS reset

If you use global CSS reset e.g.:

```css
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
```

It will break widget's styles due to Tailwind layering system. If you face that issue and see no paddings, margins or other style issues in a widget please scope your CSS resetting styles with a `@layer base` as below:

```css
@layer base {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}
```

### Still Having Issues?

If this guide didn't solve your problem:

1. Check the [GitHub Issues](https://github.com/aurora-is-near/intents-swap-widget/issues) for similar problems
2. Open a new issue with:
   * Widget version
   * Your configuration (remove sensitive keys)
   * Error messages from the console
   * Steps to reproduce


# Localisation

The **Intents Swap Widget** allows you to override any piece of text used within the interface. This lets you customise copy for your app’s tone of voice, or translate labels into other languages.

To change the copy used inside the widget, pass a `localisation` object to the `WidgetConfigProvider`. Each key corresponds to a text label or message within the widget.

The full list of available variables can be found at [`packages/intents-swap-widget/src/types/localisation.ts`](https://github.com/aurora-is-near/intents-swap-widget/blob/main/packages/intents-swap-widget/src/types/localisation.ts)

### Example

```tsx
import { WidgetConfigProvider, WidgetSwap } from '@aurora-is-near/intents-swap-widget';

export default function App() {
  return (
    <WidgetConfigProvider
      localisation={{
        'quote.result.maxSlippage.label': 'MAX',
        'submit.active.swap': 'Swap now',
      }}
    >
      <WidgetSwap />
    </WidgetConfigProvider>
  );
}
```


# Widgets

The **Intents Swap Widget** package exports multiple pre-built widget components, `Widget`, `WidgetSwap`, `WidgetWithdraw` and `WidgetDeposit`.

The `Widget` component is the one you will want to use in most cases. The others being used when you want to build more specific UIs.

By default `Swap` mode is active when widget is rendered. You can change this by passing the `defaultMode` property to the `Widget` component.

### Usage

In their most basic form, all of these components can be rendered with no additional properties, for example:

```tsx
import { Widget } from '@aurora-is-near/intents-swap-widget';

<Widget />
```

As well as the global configuration properties provided via the `WidgetConfigProvider` (see Configuration), the widgets each accept a number of properties.

### Making transfers

The package will make EVM and Solana transfers by default. If you want to implement your own custom transfer logic you can pass in a `makeTransfer` function as widget property.

The function is called with an object that contains details about the transaction, which is typed like this:

```ts
export type MakeTransferArgs = {
  amount: string;
  decimals: number;
  address: string;
  tokenAddress?: string;
  chain: Chains;
  evmChainId: number | null;
  isNativeEvmTokenTransfer: boolean;
  sourceAssetId: string;
  targetAssetId: string;
};
```

Your `makeTransfer` function should return the transaction `hash` and a `transactionLink`, which are used when displaying the transfer success screen.

#### Example

```tsx
  <Widget
    makeTransfer={(args) => {
      const hash = performTonSwap(args);

      return {
        hash: hash,
        transactionLink: `https://tonviewer.com/transaction/${hash}`,
      }
    }}
  />
```

The `makeTransfer` function is called with a second argument that defines the widget type. This can be useful if you have enabled account abstraction and need to modify the transfer behaviour in some way, or log analytics events, based on the widget type (i.e. swap, deposit or withdraw).

### Listen to events

Each `Widget` component exposes `onMsg` prop that allows you to listen to some events in the transfer pipeline. E.g. widget deposit mode component has the following events exposed that can be used like:

```tsx
<Widget onMsg={msg => {
    switch (msg.type) {
        case 'on_select_token':
        case 'on_change_deposit_type':
        case 'on_tokens_modal_toggled':
            break
        case 'on_transfer_success':
            // you can access event's data here e.g. transaction hash
            console.log(msg.hash)
            break;
        default:
            break;
    }
}} />
```

For events of other widget types please refer to their `Props` type.


# Confidential Swaps

Confidential Swaps route your swap through a private shard of NEAR. The origin wallet and the routing trail are hidden from the outside world. The destination transaction settles publicly as normal.

There is no separate API and no new integration. If you have already called the [Swap API Reference](/api-reference/swap-api-reference), you enable confidentiality by enabling confidentiality mode in the quote request.

{% hint style="info" %}
Confidentiality covers only the origin of the assets. See [Confidential Intents](/confidential-intents) for the full scope.
{% endhint %}

{% stepper %}
{% step %}

### **Get your API key**

Navigate to [Widget Studio](https://studio.aurora.dev/) and generate an API key. Confidential Swaps use the same key as your standard swaps.
{% endstep %}

{% step %}

### **Request a confidential quote**

Call the quote endpoint exactly as you do today, then add `confidentiality: "advanced"` to the request body. Everything else stays the same.

```bash
curl -X POST https://intents-api.aurora.dev/api/quote/{$YOUR_API_KEY} \
  -H "Content-Type: application/json" \
  -d '{
    "dry": false,
    "confidentiality": "advanced",
    "swapType": "EXACT_INPUT",
    "amount": "1000000",
    "originAsset": "<originAssetId>",
    "destinationAsset": "<destinationAssetId>",
    "depositType": "ORIGIN_CHAIN",
    "recipient": "<recipientAddress>",
    "recipientType": "DESTINATION_CHAIN",
    "refundTo": "<refundAddress>",
    "refundType": "ORIGIN_CHAIN",
    "slippageTolerance": 100,
    "deadline": "2026-07-06T12:00:00.000Z"
  }'
```

Or in JavaScript:

{% hint style="info" %}
Fetch `originAsset` and `destinationAsset` IDs from [Get supported tokens](/api-reference/swap-api-reference/get-supported-tokens). Never construct them manually.
{% endhint %}

```javascript
const res = await fetch(
  `https://intents-api.aurora.dev/api/quote/${API_KEY}`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      dry: false,
      confidentiality: "advanced",
      swapType: "EXACT_INPUT",
      amount: "1000000",
      originAsset: originAssetId,
      destinationAsset: destinationAssetId,
      depositType: "ORIGIN_CHAIN",
      recipient: recipientAddress,
      recipientType: "DESTINATION_CHAIN",
      refundTo: refundAddress,
      refundType: "ORIGIN_CHAIN",
      slippageTolerance: 100,
      deadline: new Date(Date.now() + 10 * 60_000).toISOString(),
    }),
  }
);

const { quote } = await res.json();
// quote.depositAddress is the confidential deposit address
```

{% endstep %}

{% step %}

### **Deposit and complete the swap**

The response returns a `depositAddress` as usual. Send the origin asset to that address, then poll for status with [Get swap status](/api-reference/swap-api-reference/get-swap-status). The flow is identical to a standard swap.

```javascript
// Notify the API after depositing to speed up processing
await fetch(`https://intents-api.aurora.dev/api/status/${API_KEY}?depositAddress=0x76b4c56085ED136a8744D52bE956396624a730E8`, {
  method: "GET",
});

// Poll until terminal: SUCCESS, FAILED, REFUNDED
```

**That is it**

The only difference from a standard swap is the `confidentiality` mode. Deposit addresses, status polling, refunds, and your configured fees all behave exactly as they do today.
{% endstep %}

{% step %}

### (Optional) Read Confidential Intents balance

Use [Authenticate user with signed data](/api-reference/confidential-swaps-api-reference/authenticate-user-with-signed-data) to authenticate your account and read your balance using [Get user token balances](/api-reference/confidential-swaps-api-reference/get-user-token-balances).

{% hint style="info" %}
This is related only to your Confidential Intents balances, a private layer of NEAR Intents.
{% endhint %}
{% endstep %}
{% endstepper %}


