Executing Swaps

Executing swaps is the most common use of Aquarius protocol. This article will explain how to prepare swap, find the best path and execute swaps with Python and Javascript.

In this example we will swap 1 XLM to AQUA with Aquarius AMM. Scroll here to see the complete code.

Executing Swap: Step by Step Guide

To perform a swap, you need to follow these steps:

1. Specify user public key, input token, output token and input token amount: You need to specify the addresses of the input and output tokens and the amount of the input token you want to swap in stroops.

public_key = "GD...Q"
token_in_address = 'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA'
token_out_address = 'CAUIKL3IYGMERDRUN6YSCLWVAKIFG5Q4YJHUKM4S4NJZQIA3BAS6OJPK'
amount = 1_0000000

2. Call Find Path API to calculate swap chain and XDR: Send a POST request to the find-path endpoint (/api/external/v1/find-path/). The request body should be a JSON object with the following fields:

  • token_in_address: The address of the token you want to swap from.

  • token_out_address: The address of the token you want to swap to.

  • amount: The amount of the input token you want to swap.

data = {
    'token_in_address': token_in_address,
    'token_out_address': token_out_address,
    'amount': amount
}
response = requests.post(f'{base_api}/find-path/', json=data)
swap_result = response.json()
assert swap_result['success']

3. (Optional) Get pools involved in the swap chain: If you need to display the pools chain in the UI, a separate API call to fetch their info is necessary. Send a GET request to the pools endpoint (/api/external/v1/pools/) with a query parameter address__in containing the addresses of the pools involved in the swap chain, separated with commas.

The api documentation is available here: https://amm-api.aqua.network/api/schema/redoc/#tag/pools/operation/pools_list

Example of rendered pools chain:

pools_addresses = ','.join(swap_result['pools'])
response = requests.get(f'{base_api}/pools/?address__in={pools_addresses}')
pools = response.json()
print('Pools:', ' -> '.join([pool['address'] for pool in pools['results']]))

4. Make AMM Router smart contract call of the method swap_chained using XDR from Find Path API: Use the XDR from the Find Path API to make a smart contract call to the swap_chained method in the AMM Router smart contract.

from stellar_sdk import scval, SorobanServer, TransactionBuilder, Network
from stellar_sdk.xdr import SCVal

router_contract_id = "CBQDHNBFBZYE4MKPWBSJOPIYLW4SFSXAXUTSXJN76GNKYVYPCKWC6QUK"
server = SorobanServer("https://soroban-rpc-server.example.com:443/")
# No need to generate swaps_chain manually, use value received from find-path api
swaps_chain = SCVal.from_xdr(swap_result['swap_chain_xdr'])
token_in = scval.to_address(token_in_address)
amount = scval.to_uint128(amount)
account = server.load_account(public_key)
tx = (
    TransactionBuilder(
        server.load_account(public_key),
        network_passphrase=Network.PUBLIC_NETWORK_PASSPHRASE,
        base_fee=10000
    ).set_timeout(300)
    .append_invoke_contract_function_op(
        contract_id=router_contract_id,
        function_name="swap_chained",
        parameters=[
            scval.to_address(public_key),
            swaps_chain,
            token_in,
            amount,
            scval.to_uint128(int(swap_result['amount'] * 0.99)),  # apply 1% slippage
        ],
    )
    .build()
)
tx = server.prepare_transaction(tx)
print(tx.to_xdr())

Complete Code Examples

Copy the full code Python
import requests

base_api = 'https://amm-api.aqua.network/api/external/v1'


# Step 1: Specify user public key, input token, output token and input token amount
public_key = "GD...Q"
token_in_address = 'CB...Q'
token_out_address = 'CD...C'
amount = 1_0000000


# Step 2: Call Find Path API to calculate swap chain and XDR
data = {
    'token_in_address': token_in_address,
    'token_out_address': token_out_address,
    'amount': amount
}
response = requests.post(f'{base_api}/find-path/', json=data)
swap_result = response.json()
assert swap_result['success']


# Step 3 (Optional): Get pools involved in the swap chain
pools_addresses = ','.join(swap_result['pools'])
response = requests.get(f'{base_api}/pools/?address__in={pools_addresses}')
pools = response.json()
print('Pools:', ' -> '.join([pool['address'] for pool in pools['results']]))

# Step 4: generate smart contact call
from stellar_sdk import scval, SorobanServer, TransactionBuilder, Network
from stellar_sdk.xdr import SCVal

router_contract_id = "CBQDHNBFBZYE4MKPWBSJOPIYLW4SFSXAXUTSXJN76GNKYVYPCKWC6QUK"
server = SorobanServer("https://soroban-rpc-server.example.com:443/")
# No need to generate swaps_chain manually, use value received from find-path api
swaps_chain = SCVal.from_xdr(swap_result['swap_chain_xdr'])
token_in = scval.to_address(token_in_address)
amount = scval.to_uint128(amount)
account = server.load_account(public_key)
tx = (
    TransactionBuilder(
        server.load_account(public_key),
        network_passphrase=Network.PUBLIC_NETWORK_PASSPHRASE,
        base_fee=10000
    ).set_timeout(300)
    .append_invoke_contract_function_op(
        contract_id=router_contract_id,
        function_name="swap_chained",
        parameters=[
            scval.to_address(public_key),
            swaps_chain,
            token_in,
            amount,
            scval.to_uint128(int(swap_result['amount'] * 0.99)),  # apply 1% slippage
        ],
    )
    .build()
)
tx = server.prepare_transaction(tx)
print(tx.to_xdr())


# Step 5: Sign and submit the transaction. Use stellar laboratory to test the transaction manually
# https://laboratory.stellar.org/#txsigner?network=public
Copy the full code JavaScript
// Step 1: Specify Your public key, your server, input token, output token and input token amount
const userPublicKey = 'G...';
const tokenInAddress = 'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA'; //XLM
const tokenOutAddress = 'CAUIKL3IYGMERDRUN6YSCLWVAKIFG5Q4YJHUKM4S4NJZQIA3BAS6OJPK'; //AQUA
const amount = 1000000; // amount in stroops

// Common constants
const sorobanServer = 'https://soroban-rpc-server.example.com:443/';
const baseApi = 'https://amm-api.aqua.network/api/external/v1';
const routerContractId = "CBQDHNBFBZYE4MKPWBSJOPIYLW4SFSXAXUTSXJN76GNKYVYPCKWC6QUK"

// Step 2: Call Find Path API to calculate swap chain and XDR

async function findPath() {
    const headers = { 'Content-Type': 'application/json' };
    const body = JSON.stringify({
        token_in_address: tokenInAddress,
        token_out_address: tokenOutAddress,
        amount: amount.toString(),
    });

    const estimateResponse = await fetch(`${baseApi}/find-path/`, { method: 'POST', body, headers });
    const estimateResult = await estimateResponse.json();

    console.log(estimateResult);
    // {
    //   success: true,
    //   swap_chain_xdr: 'AAAAEAAAAAE...SEu1QKQU3Ycwk9FM5LjU5ggGwgl5w==',
    //   pools: [ 'CDE57N6XTUPBKYYDGQMXX7E7SLNOLFY3JEQB4MULSMR2AKTSAENGX2HC' ],
    //   tokens: [
    //     'native',
    //     'AQUA:GBNZILSTVQZ4R7IKQDGHYGY2QXL5QOFJYQMXPKWRRM5PAV7Y4M67AQUA'
    //   ],
    //   amount: 1724745895
    // }

    if (!estimateResult.success) {
        throw new Error('Estimate failed');
    }

    return estimateResult;
}


// Step 3 (Optional): Get pools involved in the swap chain

async function getPools(estimateResult) {
    const poolsAddresses = estimateResult.pools.join(',');

    const poolsResponse = await fetch(`${baseApi}/pools/?address__in=${poolsAddresses}`)
    const pools = await poolsResponse.json();
    console.log(pools);
    //{
    //  count: 1,
    //  next: null,
    //  previous: null,
    //  results: [
    //    {
    //      index: 'b2e02fcfca6c96f8ad5cbd84e7784a777b36d9c96a2459402c4f458462aab7f0',
    //      address: 'CDE57N6XTUPBKYYDGQMXX7E7SLNOLFY3JEQB4MULSMR2AKTSAENGX2HC',
    //      tokens_addresses: [
    //          'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA',
    //          'CAUIKL3IYGMERDRUN6YSCLWVAKIFG5Q4YJHUKM4S4NJZQIA3BAS6OJPK'
    //      ]
    //      tokens_str: [Array],
    //      pool_type: 'constant_product',
    //      fee: '0.0010',
    //      a: null
    //    }
    //  ]
    //}
    return pools;
}

// Step 4: generate smart contact call

async function executeSwap(estimateResult) {
    const StellarSdk = require('@stellar/stellar-sdk');
    const {
        xdr,
        Address,
        StrKey,
        XdrLargeInt,
        Networks,
        TransactionBuilder,
        SorobanRpc,
        BASE_FEE,
        TimeoutInfinite,
    } = StellarSdk;

    const server = new SorobanRpc.Server(sorobanServer);

    // No need to generate swapsChain manually, use value received from find-path api
    const swapsChain = xdr.ScVal.fromXDR(estimateResult.swap_chain_xdr, 'base64');
    const tokenIn = Address.contract(StrKey.decodeContract(tokenInAddress)).toScVal()
    const amountU128 = new XdrLargeInt('u128', amount.toFixed()).toU128();
    const amountWithSlippage = amount * 0.99; // slippage 1%
    const amountWithSlippageU128 = new XdrLargeInt('u128', amountWithSlippage.toFixed()).toU128();

    const account = await server.getAccount(userPublicKey);
    const tx = new TransactionBuilder(account, {
        fee: BASE_FEE,
        networkPassphrase: Networks.PUBLIC,
    })
        .addOperation(
            new StellarSdk.Contract(routerContractId).call(
                "swap_chained",
                xdr.ScVal.scvAddress(Address.fromString(userPublicKey).toScAddress()),
                swapsChain,
                tokenIn,
                amountU128,
                amountWithSlippageU128
            )
        )
        .setTimeout(TimeoutInfinite)
        .build();

    const preparedTx = await server.prepareTransaction(tx);

    console.log(preparedTx.toXDR());

    // AAAAAgAAAACtQVH5XtxEKlw.....gbmQAAAAA=

    return preparedTx.toXDR();
}

// Entry point

findPath().then(estimated => {
    getPools(estimated);
    return executeSwap(estimated);
})

// Step 5: Sign and submit the transaction. Use stellar laboratory to test the transaction manually
// https://laboratory.stellar.org/#txsigner?network=public

Swap Example Transactions

Last updated