# Quick Start

Complete your first cross-chain trade with WheelX API in a few minutes.

This guide walks you through a cross-chain trade from USDC on Ethereum to USDT on Soneium, from quote to final status.

### Before You Start

Base URL:

```
https://api.wheelx.fi/v1
```

Requirements:

* Node.js `18+`
* an Ethereum RPC URL
* an EVM wallet address
* a signer or wallet client that can send transactions on Ethereum
* a wallet that holds USDC on Ethereum mainnet
* a small amount of ETH for gas

### System Flow

This is the execution flow for a standard WheelX cross-chain trade.

```mermaid
sequenceDiagram
  participant Script
  participant WheelX API
  participant Wallet
  participant Ethereum

  Script->>WheelX API: POST /quote
  WheelX API-->>Script: request_id + tx + approve
  Script->>Wallet: approve ERC20 if needed
  Wallet->>Ethereum: approval transaction
  Script->>Wallet: send tx.to + tx.data + tx.value
  Wallet->>Ethereum: source-chain transaction
  Script->>WheelX API: GET /order/{request_id}
  WheelX API-->>Script: Open -> Filled / Failed
```

### Try It Step by Step

{% stepper %}
{% step %}

#### Install the dependencies

This example uses `axios` to call the API and `viem` to check allowance, approve the token, and send the transaction. `viem` is not required by WheelX itself. It is included here because the sample script uses it end to end.

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

```bash
npm install axios viem
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add axios viem
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add axios viem
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

#### Configure your environment

Set the RPC endpoint and the wallet address before running the script.

```bash
export RPC_URL="https://your-ethereum-rpc"
export WALLET_ADDRESS="0xyourwalletaddress"
```

{% endstep %}

{% step %}

#### Request a quote

This example performs a bridge-and-swap flow from Ethereum USDC to Soneium USDT.

```json
{
  "from_chain": 1,
  "to_chain": 1868,
  "from_token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "to_token": "0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35",
  "from_address": "0x1111111111111111111111111111111111111111",
  "to_address": "0x1111111111111111111111111111111111111111",
  "amount": "100000000",
  "affiliation": null,
  "to_platform_id": 0,
  "quote_only": true
}
```

You can use the placeholder address above to try the quote request first. Replace both address fields with your own EVM address before sending a real transaction. In the full script below, this value is read from `WALLET_ADDRESS`.

<details>

<summary>View the same request as CURL</summary>

```bash
curl --request POST 'https://api.wheelx.fi/v1/quote' \
  --header 'Content-Type: application/json' \
  --data '{
    "from_chain": 1,
    "to_chain": 1868,
    "from_token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "to_token": "0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35",
    "from_address": "0x1111111111111111111111111111111111111111",
    "to_address": "0x1111111111111111111111111111111111111111",
    "amount": "100000000",
    "affiliation": null,
    "to_platform_id": 0,
    "quote_only": true
  }'
```

</details>
{% endstep %}

{% step %}

#### Review the response

A successful quote returns the transaction payload, the approval requirement, and the order identifier you will use later for status tracking.

```json
{
  "request_id": "0x00000000000000000000000000000000019ce54ecf0a7cd3a9cc98cc2b805241",
  "amount_out": "99324911",
  "tx": {
    "to": "0x7eC9672678509a574F6305F112a7E3703845a98b",
    "value": "0",
    "data": "0xb3c8c6da..."
  },
  "approve": {
    "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "spender": "0x7eC9672678509a574F6305F112a7E3703845a98b",
    "amount": 100000000
  },
  "min_receive": "94358665",
  "estimated_time": 10.0,
  "router": "Pancakeswap + WheelX + Uniswap"
}
```

Key fields to check:

* `request_id`: save this value for order polling
* `tx.to`: contract address for the source-chain transaction
* `tx.data`: calldata to submit
* `tx.value`: native token value to include with the transaction
* `approve`: approval instructions for the input token; if not `null`, approve before the main transaction
* `amount_out`: quoted destination amount
* `min_receive`: minimum receive amount after slippage
* `estimated_time`: rough route duration
* `router`: readable route summary

{% hint style="info" %}
The quote already contains the transaction payload needed for execution. You do not need to build calldata yourself.
{% endhint %}

<details>

<summary>View a longer response example</summary>

```json
{
  "request_id": "0x00000000000000000000000000000000019ce54ecf0a7cd3a9cc98cc2b805241",
  "amount_out": "99324911",
  "fee": "0.22898808463509798380",
  "tx": {
    "to": "0x7eC9672678509a574F6305F112a7E3703845a98b",
    "value": "0",
    "data": "0xb3c8c6da...",
    "chainId": 1,
    "gas": null,
    "maxFeePerGas": null,
    "maxPriorityFeePerGas": null
  },
  "approve": {
    "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "spender": "0x7eC9672678509a574F6305F112a7E3703845a98b",
    "amount": 100000000
  },
  "slippage": 500,
  "min_receive": "94358665",
  "estimated_time": 10.0,
  "recipient": "0xac50fab2bf7a921a73298a691dc047a6fc2ad34a",
  "router_type": "swap",
  "price_impact": {
    "bridge_fee": "0.22544245870336198380",
    "swap_fee": "0",
    "dst_gas_fee": "0.00354562593173600000"
  },
  "router": "Pancakeswap + WheelX + Uniswap",
  "created_at": "2026-03-13T03:47:54.969734",
  "points": "22.54",
  "quote_message": null,
  "routes": [
    { "name": "Pancakeswap" },
    { "name": "WheelX" },
    { "name": "Uniswap" }
  ]
}
```

</details>
{% endstep %}

{% step %}

#### Execute the trade

Create a file named `wheelx-quickstart.mjs`, paste the script below, and connect your own signer before running it. The script focuses on the WheelX flow and leaves wallet setup to your application.

<details>

<summary>View the full Node.js script</summary>

```js
import axios from 'axios'
import {
  createPublicClient,
  erc20Abi,
  http
} from 'viem'
import { mainnet } from 'viem/chains'

const API_BASE_URL = 'https://api.wheelx.fi/v1'

async function getWalletConnection() {
  // Replace this with your own wallet setup.
  // Return an object in this shape:
  // {
  //   account: { address: '0x...' },
  //   walletClient: yourViemWalletClient
  // }
  throw new Error('Replace getWalletConnection() with your own wallet setup.')
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

async function fetchQuote(accountAddress) {
  const response = await axios.post(`${API_BASE_URL}/quote`, {
    from_chain: 1,
    to_chain: 1868,
    from_token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    to_token: '0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35',
    from_address: accountAddress,
    to_address: accountAddress,
    amount: '100000000',
    affiliation: null,
    to_platform_id: 0,
    quote_only: true
  })

  if (!response.data.tx) {
    throw new Error('Quote returned without an executable tx payload.')
  }

  return response.data
}

async function ensureApproval({ quote, account, walletClient, publicClient }) {
  if (!quote.approve) return

  const approveAmount = BigInt(quote.approve.amount)

  const allowance = await publicClient.readContract({
    address: quote.approve.token,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [account.address, quote.approve.spender]
  })

  if (allowance >= approveAmount) {
    console.log('✅ Approval already sufficient.')
    return
  }

  console.log('📝 Sending approval transaction...')

  const approvalHash = await walletClient.writeContract({
    account,
    address: quote.approve.token,
    abi: erc20Abi,
    functionName: 'approve',
    args: [quote.approve.spender, approveAmount],
    chain: mainnet
  })

  await publicClient.waitForTransactionReceipt({ hash: approvalHash })
  console.log('✅ Approval confirmed:', approvalHash)
}

async function sendMainTransaction({ quote, account, walletClient, publicClient }) {
  console.log('🚀 Sending source-chain transaction...')

  const txHash = await walletClient.sendTransaction({
    account,
    chain: mainnet,
    to: quote.tx.to,
    data: quote.tx.data,
    value: BigInt(quote.tx.value)
  })

  await publicClient.waitForTransactionReceipt({ hash: txHash })
  console.log('✅ Source transaction confirmed:', txHash)
}

async function pollOrder(requestId) {
  while (true) {
    try {
      const response = await axios.get(`${API_BASE_URL}/order/${requestId}`)
      const order = response.data

      console.log('⏳ Current order status:', order.status)

      if (order.status === 'Filled' || order.status === 'Failed') {
        return order
      }
    } catch (error) {
      if (axios.isAxiosError(error) && error.response?.status === 404) {
        console.log('⌛ Order not indexed yet. Retrying...')
        await sleep(3000)
        continue
      }

      throw error
    }

    await sleep(3000)
  }
}

async function main() {
  const rpcUrl = process.env.RPC_URL
  const walletAddress = process.env.WALLET_ADDRESS

  if (!rpcUrl) {
    throw new Error('Missing RPC_URL')
  }

  if (!walletAddress) {
    throw new Error('Missing WALLET_ADDRESS')
  }

  const { account, walletClient } = await getWalletConnection()

  if (!account?.address) {
    throw new Error('Missing account.address from getWalletConnection()')
  }

  if (!walletClient) {
    throw new Error('Missing walletClient from getWalletConnection()')
  }

  if (walletAddress.toLowerCase() !== account.address.toLowerCase()) {
    throw new Error('WALLET_ADDRESS must match the connected wallet address')
  }

  const publicClient = createPublicClient({
    chain: mainnet,
    transport: http(rpcUrl)
  })

  const quote = await fetchQuote(walletAddress)

  console.log('🆔 request_id:', quote.request_id)
  console.log('👛 wallet:', walletAddress)
  console.log('🧭 router:', quote.router)
  console.log('💰 amount_out:', quote.amount_out)
  console.log('🛡️ min_receive:', quote.min_receive)

  await ensureApproval({
    quote,
    account,
    walletClient,
    publicClient
  })

  await sendMainTransaction({
    quote,
    account,
    walletClient,
    publicClient
  })

  const finalOrder = await pollOrder(quote.request_id)
  console.log('🎉 Final order:', finalOrder)
}

main().catch((error) => {
  console.error('❌ Execution failed:', error)
  process.exit(1)
})
```

</details>

<details>

<summary>View a local testing version that uses PRIVATE_KEY</summary>

If you prefer to run this flow locally without adding wallet connection code first, you can use a private key from an environment variable.

```bash
export PRIVATE_KEY="0xyourprivatekey"
```

```js
import axios from 'axios'
import {
  createPublicClient,
  createWalletClient,
  erc20Abi,
  http
} from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'

const API_BASE_URL = 'https://api.wheelx.fi/v1'

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

async function fetchQuote(accountAddress) {
  const response = await axios.post(`${API_BASE_URL}/quote`, {
    from_chain: 1,
    to_chain: 1868,
    from_token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    to_token: '0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35',
    from_address: accountAddress,
    to_address: accountAddress,
    amount: '100000000',
    affiliation: null,
    to_platform_id: 0,
    quote_only: true
  })

  if (!response.data.tx) {
    throw new Error('Quote returned without an executable tx payload.')
  }

  return response.data
}

async function ensureApproval({ quote, account, walletClient, publicClient }) {
  if (!quote.approve) return

  const approveAmount = BigInt(quote.approve.amount)

  const allowance = await publicClient.readContract({
    address: quote.approve.token,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [account.address, quote.approve.spender]
  })

  if (allowance >= approveAmount) {
    console.log('✅ Approval already sufficient.')
    return
  }

  console.log('📝 Sending approval transaction...')

  const approvalHash = await walletClient.writeContract({
    account,
    address: quote.approve.token,
    abi: erc20Abi,
    functionName: 'approve',
    args: [quote.approve.spender, approveAmount],
    chain: mainnet
  })

  await publicClient.waitForTransactionReceipt({ hash: approvalHash })
  console.log('✅ Approval confirmed:', approvalHash)
}

async function sendMainTransaction({ quote, account, walletClient, publicClient }) {
  console.log('🚀 Sending source-chain transaction...')

  const txHash = await walletClient.sendTransaction({
    account,
    chain: mainnet,
    to: quote.tx.to,
    data: quote.tx.data,
    value: BigInt(quote.tx.value)
  })

  await publicClient.waitForTransactionReceipt({ hash: txHash })
  console.log('✅ Source transaction confirmed:', txHash)
}

async function pollOrder(requestId) {
  while (true) {
    try {
      const response = await axios.get(`${API_BASE_URL}/order/${requestId}`)
      const order = response.data

      console.log('⏳ Current order status:', order.status)

      if (order.status === 'Filled' || order.status === 'Failed') {
        return order
      }
    } catch (error) {
      if (axios.isAxiosError(error) && error.response?.status === 404) {
        console.log('⌛ Order not indexed yet. Retrying...')
        await sleep(3000)
        continue
      }

      throw error
    }

    await sleep(3000)
  }
}

async function main() {
  const rpcUrl = process.env.RPC_URL
  const privateKey = process.env.PRIVATE_KEY
  const walletAddress = process.env.WALLET_ADDRESS

  if (!rpcUrl) {
    throw new Error('Missing RPC_URL')
  }

  if (!privateKey) {
    throw new Error('Missing PRIVATE_KEY')
  }

  if (!walletAddress) {
    throw new Error('Missing WALLET_ADDRESS')
  }

  const account = privateKeyToAccount(privateKey)

  if (walletAddress.toLowerCase() !== account.address.toLowerCase()) {
    throw new Error('WALLET_ADDRESS must match the address derived from PRIVATE_KEY')
  }

  const publicClient = createPublicClient({
    chain: mainnet,
    transport: http(rpcUrl)
  })

  const walletClient = createWalletClient({
    account,
    chain: mainnet,
    transport: http(rpcUrl)
  })

  const quote = await fetchQuote(walletAddress)

  console.log('🆔 request_id:', quote.request_id)
  console.log('👛 wallet:', walletAddress)
  console.log('🧭 router:', quote.router)
  console.log('💰 amount_out:', quote.amount_out)
  console.log('🛡️ min_receive:', quote.min_receive)

  await ensureApproval({
    quote,
    account,
    walletClient,
    publicClient
  })

  await sendMainTransaction({
    quote,
    account,
    walletClient,
    publicClient
  })

  const finalOrder = await pollOrder(quote.request_id)
  console.log('🎉 Final order:', finalOrder)
}

main().catch((error) => {
  console.error('❌ Execution failed:', error)
  process.exit(1)
})
```

</details>

Run it:

```bash
node wheelx-quickstart.mjs
```

{% endstep %}

{% step %}

#### Check the order status

For this flow, the order status is simple:

* `Open`: the order has started and is still in progress
* `Filled`: the cross-chain trade is complete
* `Failed`: the order did not complete successfully

Use this rule in your application:

1. Submit the source-chain transaction
2. Keep the `request_id`
3. Poll `/order/{request_id}`
4. Mark the trade as complete only when the order reaches `Filled`

{% hint style="warning" %}
For cross-chain flows, the source-chain transaction receipt is not the final completion signal. Use the order status.
{% endhint %}
{% endstep %}
{% endstepper %}


---

# Agent Instructions: Querying This Documentation

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

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

```
GET https://docs.wheelx.fi/api/quick-start.md?ask=<question>
```

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

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