# 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 %}
