# Integration Guide

This guide walks through integrating the XOXNO Aggregator API into an application, from fetching quotes to submitting on-chain transactions.

## Prerequisites

* MultiversX wallet integration (xPortal, DeFi Wallet, or Web Wallet)
* Familiarity with MultiversX transaction structure
* Node.js or equivalent HTTP client

## Overview

The integration flow consists of three main steps:

1. **Validate the pair** — Check token support and retrieve decimal metadata
2. **Get a quote** — Fetch optimal routing and transaction data
3. **Build, sign, and submit** — Construct the transaction and broadcast it

***

## Token Amounts

All amounts in the API are expressed in the token's **smallest unit** (integer, no decimals).

To convert a human-readable amount to smallest units:

```javascript
function toSmallestUnits(amount, decimals) {
  // Use BigInt to avoid floating-point precision loss
  return (BigInt(Math.round(amount * 1e9)) * BigInt(10 ** decimals) / BigInt(1e9)).toString();
}

// 1 WEGLD (18 decimals) → "1000000000000000000"
toSmallestUnits(1, 18);

// 100 USDC (6 decimals) → "100000000"
toSmallestUnits(100, 6);
```

To convert back to a human-readable amount:

```javascript
function fromSmallestUnits(amount, decimals) {
  const divisor = BigInt(10 ** decimals);
  const whole = BigInt(amount) / divisor;
  const remainder = BigInt(amount) % divisor;
  return Number(whole) + Number(remainder) / 10 ** decimals;
}
```

{% hint style="warning" %}
Always retrieve token decimals from the [pair-config endpoint](/developers/aggregator-api/pair-config.md) rather than hardcoding them. Pass amounts as strings (not numbers) to avoid integer overflow with large values.
{% endhint %}

***

## Step 1: Validate the Pair

Before requesting a quote, verify that the pair is supported and retrieve decimal metadata:

```javascript
const BASE_URL = 'https://swap.xoxno.com';

async function validatePair(tokenIn, tokenOut) {
  const response = await fetch(
    `${BASE_URL}/api/v1/pair-config?from=${tokenIn}&to=${tokenOut}`
  );
  const config = await response.json();

  if (!config.supported) {
    throw new Error(`Pair not supported: ${config.error}`);
  }

  return config; // { decimalsIn, decimalsOut, minAmountIn, ... }
}
```

***

## Step 2: Get a Quote

```javascript
async function getQuote(params) {
  const queryParams = new URLSearchParams({
    from: params.tokenIn,
    to: params.tokenOut,
    amountIn: params.amountIn,
    slippage: (params.slippage || 0.01).toString()
  });

  const response = await fetch(`${BASE_URL}/api/v1/quote?${queryParams}`);
  const data = await response.json();

  if (data.error) {
    throw new Error(data.error);
  }

  return data;
}

const quote = await getQuote({
  tokenIn: 'WEGLD-bd4d79',
  tokenOut: 'USDC-c76f1f',
  amountIn: '1000000000000000000', // 1 WEGLD in smallest units
  slippage: 0.01
});

console.log(`Expected output: ${quote.amountOutShort} USDC`);
console.log(`Minimum output: ${quote.amountOutMinShort} USDC`);
console.log(`Price impact: ${(quote.priceImpact * 100).toFixed(3)}%`);
```

***

## Step 3: Build the Transaction

### EGLD / WEGLD Swaps

```javascript
import {
  Transaction,
  Address,
  TransactionPayload
} from '@multiversx/sdk-core';

const AGGREGATOR_CONTRACT = 'erd1qqqqqqqqqqqqqpgq...'; // Aggregator contract address

function buildSwapTransaction(quote, senderAddress) {
  return new Transaction({
    receiver: new Address(AGGREGATOR_CONTRACT),
    sender: new Address(senderAddress),
    gasLimit: calculateGasLimit(quote),
    chainID: '1',
    data: new TransactionPayload(quote.txData),
    value: quote.amountIn // Send EGLD value with the transaction
  });
}
```

### ESDT Token Swaps

For non-native tokens, wrap the payload in an `ESDTTransfer` call:

```javascript
function buildEsdtSwapTransaction(quote, senderAddress) {
  const tokenHex = Buffer.from(quote.from).toString('hex');
  const amountHex = BigInt(quote.amountIn).toString(16).padStart(2, '0');
  const payloadHex = Buffer.from(quote.txData).toString('hex');

  const data = `ESDTTransfer@${tokenHex}@${amountHex}@${payloadHex}`;

  return new Transaction({
    receiver: new Address(AGGREGATOR_CONTRACT),
    sender: new Address(senderAddress),
    gasLimit: calculateGasLimit(quote),
    chainID: '1',
    data: new TransactionPayload(data),
    value: '0'
  });
}
```

***

## Step 4: Sign and Submit

```javascript
import { ApiNetworkProvider } from '@multiversx/sdk-core';

const networkProvider = new ApiNetworkProvider('https://api.multiversx.com');

async function executeSwap(quote, wallet) {
  const tx = buildSwapTransaction(quote, wallet.address);

  const account = await networkProvider.getAccount(new Address(wallet.address));
  tx.setNonce(account.nonce);

  const signedTx = await wallet.signTransaction(tx);
  const txHash = await networkProvider.sendTransaction(signedTx);

  return txHash;
}
```

***

## Gas Estimation

The API provides `estimatedBuiltinCalls` to assist gas estimation. Use the following formula as a baseline:

| Route Type            | Estimated Gas |
| --------------------- | ------------- |
| Single-hop (1 pool)   | \~8M gas      |
| Multi-hop (2–3 pools) | \~16–24M gas  |
| Split route (complex) | \~50M gas     |

```javascript
function calculateGasLimit(quote) {
  const BASE_GAS = 8_000_000;            // Base overhead
  const PER_HOP_GAS = 8_000_000;        // Per pool swap
  const PER_CALL_GAS = 5_000_000;       // Per additional SC call

  const calls = quote.estimatedBuiltinCalls || 3;

  // Generous ceiling: base + per-hop contribution + extra SC calls
  return BASE_GAS + (calls * PER_HOP_GAS) + (calls * PER_CALL_GAS);
}
```

{% hint style="info" %}
For split routes with `maxSplits > 4`, add an additional buffer of 10–20M gas. MultiversX charges refunds on unused gas, so over-estimating is safe.
{% endhint %}

***

## Error Handling

### Retry Logic

Apply exponential backoff for transient errors (429, 503):

```javascript
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (response.status === 429 || response.status === 503) {
        // Transient — back off and retry
        const delay = 200 * Math.pow(2, attempt); // 200ms, 400ms, 800ms
        await new Promise(resolve => setTimeout(resolve, delay));
        lastError = new Error(`HTTP ${response.status}`);
        continue;
      }

      return response;
    } catch (err) {
      lastError = err;
      const delay = 200 * Math.pow(2, attempt);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError;
}
```

### Error Classification

```javascript
class SwapError extends Error {
  constructor(type, message, details = {}) {
    super(message);
    this.type = type;
    this.details = details;
  }
}

async function safeGetQuote(params) {
  const response = await fetchWithRetry(
    `${BASE_URL}/api/v1/quote?${new URLSearchParams(params)}`
  );
  const data = await response.json();

  if (data.error) {
    if (data.error.includes('unknown token')) {
      throw new SwapError('INVALID_TOKEN', data.error);
    }
    if (data.error.includes('no path')) {
      throw new SwapError('NO_ROUTE', 'No trading route available for this pair');
    }
    if (data.error.includes('amount')) {
      throw new SwapError('INVALID_AMOUNT', data.error);
    }
    throw new SwapError('QUOTE_ERROR', data.error);
  }

  if (!response.ok) {
    throw new SwapError('HTTP_ERROR', `Request failed with status ${response.status}`);
  }

  return data;
}
```

See [Error Reference](https://github.com/XOXNO/docs/blob/main/developers/aggregator-api/error-reference.md) for a complete list of error codes, HTTP status codes, and resolution guidance.

***

## Complete Example

```javascript
import {
  Transaction,
  Address,
  TransactionPayload,
  ApiNetworkProvider
} from '@multiversx/sdk-core';

const AGGREGATOR_API = 'https://swap.xoxno.com';
const AGGREGATOR_CONTRACT = 'erd1qqqqqqqqqqqqqpgq...';
const networkProvider = new ApiNetworkProvider('https://api.multiversx.com');

class XoxnoAggregator {
  async getQuote(tokenIn, tokenOut, amountIn, options = {}) {
    const params = new URLSearchParams({
      from: tokenIn,
      to: tokenOut,
      amountIn: amountIn.toString(),
      slippage: (options.slippage || 0.01).toString(),
      includePaths: (options.includePaths ?? true).toString()
    });

    const response = await fetchWithRetry(`${AGGREGATOR_API}/api/v1/quote?${params}`);
    const data = await response.json();

    if (data.error) {
      throw new Error(data.error);
    }

    return data;
  }

  async buildTransaction(quote, senderAddress) {
    const isNative = quote.from === 'EGLD' || quote.from.startsWith('WEGLD');

    const tx = new Transaction({
      receiver: new Address(AGGREGATOR_CONTRACT),
      sender: new Address(senderAddress),
      gasLimit: calculateGasLimit(quote),
      chainID: '1',
      data: new TransactionPayload(quote.txData),
      value: isNative ? quote.amountIn : '0'
    });

    const account = await networkProvider.getAccount(new Address(senderAddress));
    tx.setNonce(account.nonce);

    return tx;
  }

  async executeSwap(tokenIn, tokenOut, amountIn, wallet, options = {}) {
    const quote = await this.getQuote(tokenIn, tokenOut, amountIn, options);

    console.log(`Input:        ${quote.amountInShort} ${tokenIn}`);
    console.log(`Output:       ${quote.amountOutShort} ${tokenOut}`);
    console.log(`Min output:   ${quote.amountOutMinShort} ${tokenOut}`);
    console.log(`Price impact: ${(quote.priceImpact * 100).toFixed(3)}%`);

    const tx = await this.buildTransaction(quote, wallet.address);
    const signedTx = await wallet.signTransaction(tx);
    const txHash = await networkProvider.sendTransaction(signedTx);

    console.log(`Transaction: ${txHash}`);
    return { txHash, quote };
  }
}

// Usage
const aggregator = new XoxnoAggregator();

const result = await aggregator.executeSwap(
  'WEGLD-bd4d79',
  'USDC-c76f1f',
  '1000000000000000000',
  userWallet,
  { slippage: 0.01 }
);
```

***

## Best Practices

### Quote Freshness

Quotes reflect pool state at the moment of the request. Re-fetch the quote immediately before constructing the transaction to avoid executing against stale prices:

```javascript
async function executeWithFreshQuote(params, wallet) {
  const quote = await getQuote(params);

  if (quote.priceImpact > 0.05) {
    throw new Error(`Price impact too high: ${(quote.priceImpact * 100).toFixed(2)}%`);
  }

  return executeSwap(quote, wallet);
}
```

### Slippage Guidelines

| Trade size (USD) | Recommended slippage |
| ---------------- | -------------------- |
| < $100           | 0.5%                 |
| $100 – $1,000    | 1%                   |
| $1,000 – $10,000 | 2%                   |
| > $10,000        | 3%                   |

### High-Frequency Polling

For applications that poll prices frequently (e.g., price displays):

* Use `includePaths=false` to reduce response payload and server-side work
* Respect the 100 req/s rate limit per IP; implement a local token bucket or queue
* Handle HTTP 429 with exponential backoff (see [Error Handling](#error-handling))

***

## Related Topics

* [Quote Endpoint](/developers/aggregator-api/quote.md) — Full parameter and response reference
* [Pair Configuration](/developers/aggregator-api/pair-config.md) — Token validation and decimal metadata
* [Error Reference](https://github.com/XOXNO/docs/blob/main/developers/aggregator-api/error-reference.md) — Complete error codes and resolution guidance


---

# 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.xoxno.com/developers/aggregator-api/integration-guide.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.
