> For the complete documentation index, see [llms.txt](https://docs.fly.trade/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fly.trade/developers/api-reference/solana-swap-integration-guide.md).

# Solana Swap Integration Guide

This guide shows how to execute a swap on **Solana** using the Fly Trade's API in 2 steps:

1. **Get a quote** (pricing + route + constraints)
2. **Fetch transaction payload**, then **build, sign, and send** the Solana transaction

## Prerequisites

* A Solana RPC connection
* A wallet that can sign Solana transactions (Phantom / Backpack / Solflare / or a backend Keypair)
* Token mint addresses for the assets you want to swap

## How the Flow Works

Magpie supports two ways to execute a swap on Solana. Regardless of the method, the high-level flow is:

* Call `/aggregator/quote` to receive a `quoteId` and estimated output.
* Call `/aggregator/transaction` with the `quoteId` to receive the transaction payload.
* Depending on your integration, either:
  * Deserialize the provided serialized transaction (recommended), or
  * Construct the transaction manually from `rawData`, `accounts`, LUTs, and compute budget fields.
* Sign the transaction with the user wallet (or fee payer).
* Send it to the Solana network.

## Step 1 — Get a Quote

### Endpoint

* **Method:** `GET`
* **Path:** `/aggregator/quote`

### Required parameters

| Parameter          | Description                                                                                                                                     |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `network`          | `solana`                                                                                                                                        |
| `fromTokenAddress` | Token mint to swap from. For native SOL use `11111111111111111111111111111111`                                                                  |
| `toTokenAddress`   | Token mint to swap to. For native SOL use `11111111111111111111111111111111`                                                                    |
| `sellAmount`       | Amount in the smallest unit (e.g., lamports for SOL, for 1 SOL write 1000000000)                                                                |
| `slippage`         | Allowed slippage (e.g., `0.005` for 0.5%)                                                                                                       |
| `fromAddress`      | Wallet address initiating the swap                                                                                                              |
| `toAddress`        | Wallet address receiving the swapped tokens                                                                                                     |
| `gasless`          | Please use `false` on Solana. Setting `true` will be overridden to `false`for now, gasless support for Solana will be added in a future release |

### Optional Notes

#### **Affiliate Fee**

Affiliate fees are not supported on Solana. This feature is available only on EVM chains. If you need affiliate fee logic on Solana, you must implement it on your side (e.g., adjusting balances before/after the swap).

#### **feePayer (PDA Flow)**

You can use a PDA (Program Derived Address) as the `fromAddress` as long as a normal wallet address is supplied as the `feePayer`.

* The `feePayer` must be a *real wallet*, not a PDA.
* The `feePayer` must be whitelisted by Fly Trade before use.\
  Contact us with the address to whitelist it.

This allows advanced workflows where a PDA owns the source tokens, but another wallet pays the transaction fees.

#### **Using a Non-ATA Token Account**

If the source token is stored in a **non-ATA (Associated Token Account)**, you must explicitly specify **which token account** should be used for the swap.

To do this, set the `fromAddress` parameter to:

```
<owner_address>-<token_account_address>
```

This tells us which SPL token account to read from.

**Example**

```
fromAddress=7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE-3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa
```

Where:

* `7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE` → the owner wallet
* `3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa` → the specific SPL token account (non-ATA)

If your user has multiple token accounts for the same mint, this ensures the correct one is used during the swap.

### Example request

```http
GET /aggregator/quote?network=solana&fromTokenAddress=11111111111111111111111111111111&toTokenAddress=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&fromAddress=TCKx8giNDmWRJQ6Q6WZw56NcXFQmaUMnqUfvpphoxN8&toAddress=TCKx8giNDmWRJQ6Q6WZw56NcXFQmaUMnqUfvpphoxN8&sellAmount=1000000000&slippage=0.005&gasless=false
```

### Example response

{% code overflow="wrap" expandable="true" %}

```json
{
    "id": "fce0bda2-4e2a-4d28-ab47-1cc7f2dcce03",
    "amountOut": "7267983",
    "targetAddress": "2DAtv2URAcb9ZHVMHEB8E3TFBTk9PoNSYknjq8DVr69c",
    "fees": [
        {
            "type": "gas",
            "value": "0.0333"
        }
    ],
    "resourceEstimate": {
        "computeUnitLimit": "236700"
    },
    "typedData": {
        "types": {
            "Swap": [
                {
                    "name": "router",
                    "type": "address"
                },
                {
                    "name": "sender",
                    "type": "address"
                },
                {
                    "name": "recipient",
                    "type": "address"
                },
                {
                    "name": "fromAsset",
                    "type": "address"
                },
                {
                    "name": "toAsset",
                    "type": "address"
                },
                {
                    "name": "deadline",
                    "type": "uint256"
                },
                {
                    "name": "amountOutMin",
                    "type": "uint256"
                },
                {
                    "name": "swapFee",
                    "type": "uint256"
                },
                {
                    "name": "amountIn",
                    "type": "uint256"
                }
            ]
        },
        "domain": {
            "name": "Magpie Router",
            "version": "3",
            "chainId": "900",
            "verifyingContract": "2DAtv2URAcb9ZHVMHEB8E3TFBTk9PoNSYknjq8DVr69c"
        },
        "message": {
            "router": "2DAtv2URAcb9ZHVMHEB8E3TFBTk9PoNSYknjq8DVr69c",
            "sender": "TCKx8giNDmWRJQ6Q6WZw56NcXFQmaUMnqUfvpphoxN8",
            "recipient": "TCKx8giNDmWRJQ6Q6WZw56NcXFQmaUMnqUfvpphoxN8",
            "fromAsset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
            "toAsset": "11111111111111111111111111111111",
            "deadline": "1765371635",
            "amountOutMin": "7231643",
            "swapFee": "0",
            "amountIn": "1000000"
        }
    }
}
```

{% endcode %}

Save the `id` as **`quoteId`** for Step 2.

## Step 2 — Get Transaction Payload

### Endpoint

* **Method:** `GET`
* **Path:** `/aggregator/transaction`

### Required parameters

| Parameter | Description                         |
| --------- | ----------------------------------- |
| `quoteId` | The quote `id` returned from Step 1 |

### Example request

```http
GET /aggregator/transaction?quoteId=fce0bda2-4e2a-4d28-ab47-1cc7f2dcce03
```

The response contains:

* `rawData` (base64 instruction data)
* `accounts` (account metas)
* `addressLookupTables` (LUTs)
* `gasLimit` / `gasPrice` (compute budget hints)

### Example response

{% code overflow="wrap" expandable="true" %}

```json
{
    "from": "TCKx8giNDmWRJQ6Q6WZw56NcXFQmaUMnqUfvpphoxN8",
    "to": "2DAtv2URAcb9ZHVMHEB8E3TFBTk9PoNSYknjq8DVr69c",
    "data": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAFCQa16IDB9cvg17zCAT1LaDskbNvRZEpm3p5sfql9U+vVfXdJvRhcmF4jMlskIDdyWUaEFL3TT618VBpWUT6/KK1Gp8F8NM8KD0t3H5nbFja2HYuGKK4ti+Fcu3aUAwgYTydlJkVbMxbUWi7UY8Md9WRXRtGaExjrfC6sFV2xokdpAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAR+dhT4TMou0pDyV43T7ClH69cy/8FNeAne8lX3UwKT4QCuZOVQBJghwsIVTHU2DGwgWy94EnbO+XLt73nTSIuBHGbeZ/fKizkT6tVFsVuxkH1pX/d90Af0BDNiWOdcMYGp9UXGSxcUSGMyUw9SvF/WNruCJuh/UTj29mKAAAAAGtYHMeDEQKbyXO9PrVeCUIc9M7+n++moxVhaCjaCR0fAwQABQJOBQMABAAJAxm3AAAAAAAABRYQDAECDQ4AAAMFBRESExQGDwkKCwcIXvjGnpHhdYfIUgAAADAAAAWP5m4Fm1huCAVAQg8BAUBCDwcB/////////////////////wEBAwwABBcAEAAKACcAMgASDQ4PBAMQEQIBAAIBABIJCgwTBgIAAgACBQICEQqC/DKXf/8+H+lN61IKG7rQ3+3ixipxg+IdBMZQtzcDaGZwA28baQMiYqJeZwMAv7rN5ynqIMDvxQvD/ZHQ0IdvBdatwbGYASYFJwIDBAU=",
    "chainId": 900,
    "gasLimit": "236700",
    "gasPrice": "46873",
    "value": "0",
    "rawData": "+MaekeF1h8hSAAAAMAAABY/mbgWbWG4IBUBCDwEBQEIPBwH/////////////////////AQEDDAAEFwAQAAoAJwAyABINDg8EAxARAgEAAgEAEgkKDBMGAgACAAIFAg==",
    "accounts": [
        {
            "pubkey": "Gdkvc4gBWuHgnsGrzXFDJwv9qd7HQmu9tWhqtxSyPFh1",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "DJxat3ybx6UbEZgaQrTfV2ov78RecoffPTiPZmokrS6",
            "isWritable": true,
            "isSigner": false
        },
        {
            "pubkey": "9SmTkNajPiDWjd6u3aAsC8Bg2a2ytSSxYma49GuoBTiC",
            "isWritable": true,
            "isSigner": false
        },
        {
            "pubkey": "5kouNSQxM2rXkrkCNiyv2DDJWkECWcFi8RdbymcVNdUz",
            "isWritable": true,
            "isSigner": false
        },
        {
            "pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "So11111111111111111111111111111111111111112",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "TCKx8giNDmWRJQ6Q6WZw56NcXFQmaUMnqUfvpphoxN8",
            "isWritable": true,
            "isSigner": true
        },
        {
            "pubkey": "TCKx8giNDmWRJQ6Q6WZw56NcXFQmaUMnqUfvpphoxN8",
            "isWritable": true,
            "isSigner": false
        },
        {
            "pubkey": "3enMj6EPjroS68MB2Go1rxKELYBhYw7N327MdZiivfRi",
            "isWritable": true,
            "isSigner": false
        },
        {
            "pubkey": "2DAtv2URAcb9ZHVMHEB8E3TFBTk9PoNSYknjq8DVr69c",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "2DAtv2URAcb9ZHVMHEB8E3TFBTk9PoNSYknjq8DVr69c",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "11111111111111111111111111111111",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
            "isWritable": false,
            "isSigner": false
        },
        {
            "pubkey": "9tKE7Mbmj4mxDjWatikzGAtkoWosiiZX9y6J4Hfm2R8H",
            "isSigner": false,
            "isWritable": false
        },
        {
            "pubkey": "BE5YRQ6N6LCw7UL3JwzVp317EWa4mzJY6JKDaudcXu7A",
            "isSigner": false,
            "isWritable": false
        },
        {
            "pubkey": "8GmMLucoAARw9X6g2aUVBdzRpxF6egeHMDtLJen2Kkq7",
            "isSigner": false,
            "isWritable": true
        },
        {
            "pubkey": "3wgma9BjSfFKNZnWXB4YtyxFwCZ94teWJ2XbjKa7WKtp",
            "isSigner": false,
            "isWritable": true
        },
        {
            "pubkey": "GKobg2LGwECPuTw8D65upawRm5EEX9ih3yzfh1wUtf3W",
            "isSigner": false,
            "isWritable": true
        },
        {
            "pubkey": "JM78XNzeQRmZXDAP4DSq88ZdErbuSXSLE6fkRsVDKSu",
            "isSigner": false,
            "isWritable": false
        },
        {
            "pubkey": "SysvarRent111111111111111111111111111111111",
            "isSigner": false,
            "isWritable": false
        }
    ],
    "addressLookupTables": [
        "29XEC4vp5fJCa7MnD4E21AswiMSXBNrMjUcVPVV3yZUz",
        "DEdkVVtBXdxTQRKnLyvKHPggtTf9wP2XAKaWZd5KDyh"
    ]
}
```

{% endcode %}

## Step 3 — Execute the Swap Transaction

`GET /aggregator/transaction` returns everything you need to execute the swap on Solana. There are two valid ways to construct the transaction:

* Method A (Recommended): deserialize the returned `data` directly into a `VersionedTransaction`
* Method B (Manual construction): build the v0 message yourself from `rawData`, `accounts`, LUTs, and compute budget settings

### Method A (Recommended): Deserialize `data` → sign → send

The `/aggregator/transaction` response includes:

* `data`: a base64-encoded, fully-formed Solana VersionedTransaction
* `from`: user / fee payer (informational)
* `addressLookupTables`: optional metadata (not required for this method)

#### Example (TypeScript)

{% code overflow="wrap" expandable="true" %}

```ts
import {
  Connection,
  VersionedTransaction,
} from "@solana/web3.js";

type TxResponse = {
  data: string; // base64 VersionedTransaction
};

export async function executeSwapFromSerializedTx(params: {
  connection: Connection;
  walletAdapter: {
    signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
  };
  txResponse: TxResponse; // response from GET /aggregator/transaction
}) {
  const { connection, walletAdapter, txResponse } = params;

  // 1) Deserialize to VersionedTransaction
  const vTx = VersionedTransaction.deserialize(
    Buffer.from(txResponse.data, "base64")
  );

  // 2) Sign with the user's wallet / fee payer
  const signed = await walletAdapter.signTransaction(vTx);

  // 3) Send + confirm
  const sig = await connection.sendTransaction(signed);
  await connection.confirmTransaction(sig, "confirmed");

  return sig;
}
```

{% endcode %}

#### Usage example

{% code overflow="wrap" expandable="true" %}

```ts
// 1) Fetch tx payload from Magpie
const txResponse = await getTransactionPayload(quoteId); // GET /aggregator/transaction

// 2) Execute (frontend wallet)
const signature = await executeSwapFromSerializedTx({
  connection,
  walletAdapter,
  txResponse,
});

console.log("Swap submitted:", signature);
```

{% endcode %}

### Method B (Manual): Build v0 transaction from `rawData` + `accounts` + LUTs

Use this method if you need to:

* add custom instructions
* override compute budget settings
* or construct the tx explicitly for advanced flows

This is the “manual build” approach: fetch LUT accounts, add compute budget ixs, create the router instruction from `rawData`, then compile to v0.

#### Example (TypeScript)

{% code overflow="wrap" expandable="true" %}

```ts
import {
  Connection,
  PublicKey,
  ComputeBudgetProgram,
  TransactionInstruction,
  TransactionMessage,
  VersionedTransaction,
} from "@solana/web3.js";

type TxAccountMeta = { pubkey: string; isWritable: boolean; isSigner: boolean };

type TxPayload = {
  to: string; // router program id
  gasLimit: string;
  gasPrice: string;
  rawData: string; // base64 instruction payload
  accounts: TxAccountMeta[];
  addressLookupTables: string[];
};

export async function buildSwapTxManually(params: {
  connection: Connection;
  feePayer: PublicKey;
  txPayload: TxPayload;
}) {
  const { connection, feePayer, txPayload } = params;

  // 1) Resolve LUT accounts
  const lutAccounts = await Promise.all(
    txPayload.addressLookupTables.map(async (lut) => {
      const { value } = await connection.getAddressLookupTable(new PublicKey(lut));
      if (!value) throw new Error(`Missing address lookup table: ${lut}`);
      return value;
    })
  );

  // 2) Compute budget + priority fee
  const computeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: Number(txPayload.gasLimit),
  });
  const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: Number(txPayload.gasPrice),
  });

  // 3) Router instruction
  const routerIx = new TransactionInstruction({
    keys: txPayload.accounts.map((a) => ({
      pubkey: new PublicKey(a.pubkey),
      isWritable: a.isWritable,
      isSigner: a.isSigner,
    })),
    programId: new PublicKey(txPayload.to),
    data: Buffer.from(txPayload.rawData, "base64"),
  });

  // 4) Fresh blockhash
  const { blockhash } = await connection.getLatestBlockhash();

  // 5) Build v0 transaction
  const messageV0 = new TransactionMessage({
    payerKey: feePayer,
    recentBlockhash: blockhash,
    instructions: [computeUnitLimitIx, priorityFeeIx, routerIx],
  }).compileToV0Message(lutAccounts);

  return new VersionedTransaction(messageV0);
}
```

{% endcode %}

#### Sign + send

```ts
const vTx = await buildSwapTxManually({
  connection,
  feePayer: walletAdapter.publicKey,
  txPayload,
});

const signed = await walletAdapter.signTransaction(vTx);
const sig = await connection.sendTransaction(signed);
await connection.confirmTransaction(sig, "confirmed");
```

### End-to-End (Recommended): Quote → Transaction → Deserialize → Sign → Send

{% code overflow="wrap" expandable="true" %}

```ts
import { Connection, VersionedTransaction } from "@solana/web3.js";

export async function swapEndToEndRecommended(params: {
  connection: Connection;
  walletAdapter: {
    publicKey: { toBase58: () => string };
    signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
  };
  apiBaseUrl: string;
  fromTokenAddress: string;
  toTokenAddress: string;
  amount: string; // smallest unit
  slippage: string; // e.g. "0.005"
  toAddress?: string;
}) {
  const {
    connection,
    walletAdapter,
    apiBaseUrl,
    fromTokenAddress,
    toTokenAddress,
    amount,
    slippage,
  } = params;

  const fromAddress = walletAdapter.publicKey.toBase58();
  const toAddress = params.toAddress ?? fromAddress;

  // 1) Quote
  const quoteUrl = new URL(`${apiBaseUrl}/aggregator/quote`);
  quoteUrl.searchParams.set("network", "solana");
  quoteUrl.searchParams.set("fromTokenAddress", fromTokenAddress);
  quoteUrl.searchParams.set("toTokenAddress", toTokenAddress);
  quoteUrl.searchParams.set("sellAmount", amount); // or "amount" depending on your API
  quoteUrl.searchParams.set("slippage", slippage);
  quoteUrl.searchParams.set("fromAddress", fromAddress);
  quoteUrl.searchParams.set("toAddress", toAddress);
  quoteUrl.searchParams.set("gasless", "false");

  const quoteRes = await fetch(quoteUrl.toString());
  if (!quoteRes.ok) throw new Error(await quoteRes.text());
  const quote = await quoteRes.json();

  // 2) Transaction payload
  const txUrl = new URL(`${apiBaseUrl}/aggregator/transaction`);
  txUrl.searchParams.set("quoteId", quote.id);

  const txRes = await fetch(txUrl.toString());
  if (!txRes.ok) throw new Error(await txRes.text());
  const txPayload = await txRes.json(); // includes `data`

  // 3) Deserialize VersionedTransaction from `data`
  const vTx = VersionedTransaction.deserialize(
    Buffer.from(txPayload.data, "base64")
  );

  // 4) Sign + send
  const signed = await walletAdapter.signTransaction(vTx);
  const sig = await connection.sendTransaction(signed);
  await connection.confirmTransaction(sig, "confirmed");

  return { signature: sig, quoteId: quote.id, amountOut: quote.amountOut };
}
```

{% endcode %}


---

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

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

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

```
GET https://docs.fly.trade/developers/api-reference/solana-swap-integration-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
