♒
Aquarius Guide
  • 👋Welcome to Aquarius
  • Developers
    • Integrating with Aquarius
    • Aquarius Soroban Functions
    • Code Examples
      • Prerequisites & Basics
      • Executing Swaps Through Optimal Path
      • Executing Swaps Through Specific Pool
      • Deposit Liquidity
      • Withdraw Liquidity
      • Get Pools Info
      • Claim LP Rewards
      • Add Fees To Swap
        • Deploying a New Fee Collector
        • Executing Swaps with Provider Fees
        • Claiming & Swapping Accumulated Fees
  • Ecosystem Overview
    • 🌐What is Stellar?
      • What are Lumens (XLM)?
      • What are Anchors?
      • What are Trustlines?
      • How much are network fees on Stellar?
      • What are network reserves?
      • Where to trade Stellar assets?
    • 🧮What is Soroban?
  • AQUA tokens
    • ♒What are AQUA tokens?
      • AQUAnomics
      • AQUA Wallets
      • Where can I buy AQUA?
  • ICE
    • 🧊ICE tokens: locking AQUA and getting benefits
    • ICE boosts - how to maximize LP rewards
  • Aquarius AMMs
    • 💱What are Aquarius AMMs?
      • Pools
        • Creating a Pool
        • Deposit & Withdraw Liquidity
      • Swap
      • System limitations
        • Aquarius AMM: Limitations in Support for Fee-on-Transfer, Rebasing, and Deflationary Tokens
        • Aquarius AMM: Token Address Migration Limitations and Mitigation Strategy
  • My Aquarius
    • 👤My Aquarius
      • Main Overview
      • Balances
      • My Liquidity
      • SDEX Rewards
      • Liquidity Votes
      • Governance Votes
      • Airdrop #2
      • ICE Locks
      • Payments History
  • Aquarius AQUA Rewards
    • 🗳️Aquarius voting
      • Aquarius voting: asset Flag Restrictions
    • 🪙SDEX Rewards
    • 🤖Aquarius AMM Rewards
  • Bribes
    • 🎁What are bribes?
      • What are the advantages of protocol level bribes?
  • Aquarius Governance
    • 🧑‍⚖️Aquarius Governance: Community-Led Decision Making
  • Airdrops
    • 1️⃣The Initial Airdrop
      • Am I Eligible For the Initial Airdrop?
      • How can I see if I am eligible?
      • What are Claimable Balances?
      • How is the Initial airdrop distributed?
      • Where can I find more information?
    • 🌠Airdrop #2
      • How could I have been eligible for Airdrop #2?
      • How can I see if I am eligible?
      • When was the Airdrop #2 snapshot?
      • Were there any CEX's taking part?
      • How big was Airdrop #2?
      • How will the airdrop be distributed and for how long?
      • Could I have increased my potential reward?
      • Where can I find more information?
  • Signers Guild
    • 📜What is the signers guild?
      • What percentage of the AQUA supply will be controlled by the Signers Guild?
      • Who will be in the Signers Guild?
      • How does the Signing process work?
      • What will be expected from a guild member?
      • How can I sign up for this position?
      • What are wallets that Guild members will manage?
      • How can I learn more about this?
  • Guides
    • ❔How to use AQUA Locker tool and get ICE tokens
    • ❔How to vote for markets on Aquarius
    • How to create bribes
    • ❔How to use Aquarius Governance
      • How to make a governance vote
      • How to create a proposal
    • ❔How to earn SDEX rewards
    • ❔How to earn AMM rewards
  • Technical Documents
    • 📜Audits
    • 🪲Bug Bounties
    • 🛄Claimable Balances
    • 🗳️The Aquarius Voting Mechanism
    • 🎁SDEX v2 proposal & algorithm
    • ⏩ICE Boost Formula
  • Useful Links
    • Aquarius Home
    • Liquidity Voting
    • Liquidity Rewards
    • Aquarius Bribes
    • ICE locker
    • Aquarius Governance
    • Airdrop #2
Powered by GitBook
On this page
  • Option 1: Claim & Immediately Swap
  • Option 2: Claim Raw Fees
  1. Developers
  2. Code Examples
  3. Add Fees To Swap

Claiming & Swapping Accumulated Fees

Swap fees are accumulated on the fee collector contract and should be claimed by the operator set on deployment of the fee collector.

Keep in mind that the fees are taken in the tokens being swapped so to claim all these tokens to the fee_destination address the latter must have all the trustlines.

There is a method to claim and immediately swap everything in 1 token, then settle to fee_destination. "Raw" claiming is also available though.

Below there are examples of these operations. Claims can be executed as frequently as necessary.

Option 1: Claim & Immediately Swap

  • Function: claim_fees_and_swap(e, operator: Address, swaps_chain: Vec<…>, token: Address, out_min: u128) -> u128

  • Effect:

    1. Claims all of token.

    2. Swaps via your router along swaps_chain.

    3. Sends resulting tokens to fee_destination.

  • Returns: Final output amount in the target asset.

  • Function: claim_fees_and_swap(e, operator: Address, swaps_chain: Vec<…>, token: Address, out_min: u128) -> u128

  • Effect:

    1. Claims all of token.

    2. Swaps via your router along swaps_chain.

    3. Sends resulting tokens to fee_destination.

  • Returns: Final output amount in the target asset.

import requests
from stellar_sdk import Asset, Keypair, Network, scval, Server, SorobanServer, TransactionBuilder
from stellar_sdk.xdr import Int128Parts, SCVal, TransactionMeta, UInt128Parts

# =========================================
# Configuration & Setup
# =========================================

# swap provider contract operator
operator_secret_key = "SA..........."
keypair = Keypair.from_secret(operator_secret_key)

# Input and output tokens
token_to_claim = Asset("AQUA", "GBNZILSTVQZ4R7IKQDGHYGY2QXL5QOFJYQMXPKWRRM5PAV7Y4M67AQUA")
token_out = Asset.native()  # XLM
slippage = "0.005"  # 0.5% slippage

# swap provider contract for AMM swaps on mainnet
# IMPORTANT: Replace with your deployed contract ID. This contract address is only for test purposes.
provider_swap_contract_id = "CDJCVXFIT2UIVNLC22OWCHABTWKXUSYYLPKKBLJW67ISMIWX56YJBGLS"

# Soroban and Horizon servers
soroban_server = SorobanServer("https://mainnet.sorobanrpc.com")
horizon_server = Server("https://horizon.stellar.org/")
network = Network.PUBLIC_NETWORK_PASSPHRASE

# AQUA AMM API endpoint
base_api = 'https://amm-api.aqua.network/api/external/v1'


# =========================================
# Utility Function
# =========================================

def u128_to_int(value: UInt128Parts) -> int:
    """Convert Uint128Parts to Python int."""
    return (value.hi.uint64 << 64) + value.lo.uint64


def i128_to_int(value: Int128Parts) -> int:
    """Convert int128Parts to Python int."""
    return (value.hi.int64 << 64) + value.lo.uint64


# =========================================
# Functions
# =========================================

def find_swap_path(base_api: str, token_in_address: str, token_out_address: str, amount: int, is_send: bool) -> (int, str):
    """
    Call the Find Path API to retrieve the swap chain and estimated amount.
    """
    print("Requesting swap path from AMM API...")
    data = {
        'token_in_address': token_in_address,
        'token_out_address': token_out_address,
        'amount': amount,
        'slippage': slippage,
    }
    endpoint = '/find-path/' if is_send else '/find-path-strict-receive/'
    response = requests.post(f'{base_api}{endpoint}', json=data)
    swap_result = response.json()
    print(swap_result)
    """
        {
          'success': True,
          'swap_chain_xdr': 'AAAAEAAAAAEAAAABAAAAEAAAAAEAAAADAAAAEAAAAAEAAAACAAAAEgAAAAEltPzYWa7C+mNIQ4xImzw8EMmLbSG+T9PLMMtolT75dwAAABIAAAABKIUvaMGYSI40b7EhLtUCkFN2HMJPRTOS41OYIBsIJecAAAANAAAAILLgL8/KbJb4rVy9hOd4Snd7NtnJaiRZQCxPRYRiqrfwAAAAEgAAAAEohS9owZhIjjRvsSEu1QKQU3Ycwk9FM5LjU5ggGwgl5w==',
          'pools': [
            'CDE57N6XTUPBKYYDGQMXX7E7SLNOLFY3JEQB4MULSMR2AKTSAENGX2HC'
          ],
          'tokens': [
            'native',
            'AQUA:GBNZILSTVQZ4R7IKQDGHYGY2QXL5QOFJYQMXPKWRRM5PAV7Y4M67AQUA'
          ],
          'amount': 3627808902,
          'amount_with_fee': 3627808002,
        }
    """

    if not swap_result.get('success', False):
        raise Exception("Failed to retrieve swap path from the API.")

    print("Swap path retrieved. Estimated amount:", swap_result['amount'])
    print("Estimated amount with fee:", swap_result['amount_with_fee'])
    return int(swap_result['amount_with_fee']), swap_result['swap_chain_xdr']


def get_contract_balance(
        network: str,
        soroban_rpc_server: SorobanServer,
        keypair: Keypair,
        contract_address: str,
        token_address: str,
) -> int:
    """
    Retrieves the balance of a specific token for the given account.
    """
    tx = (
        TransactionBuilder(
            source_account=soroban_rpc_server.load_account(keypair.public_key),
            network_passphrase=network,
            base_fee=10000
        )
        .set_timeout(300)
        .append_invoke_contract_function_op(
            contract_id=token_address,
            function_name="balance",
            parameters=[
                scval.to_address(contract_address),
            ],
        )
        .build()
    )
    simulation = soroban_rpc_server.simulate_transaction(tx)
    return i128_to_int(SCVal.from_xdr(simulation.results[0].xdr).i128)


def execute_claim_with_swap(
        network: str,
        soroban_rpc_server: SorobanServer,
        horizon_server: Server,
        keypair: Keypair,
        contract_id: str,
        token_in_address: str,
        amount_with_slippage: int,
        swap_path: str,
) -> int:
    """
    Executes the chained swap transaction on Soroban and returns the final amount out.
    """
    print("Preparing and building swap transaction...")
    source_account = horizon_server.load_account(keypair.public_key)

    # Build the transaction to invoke `swap_chained`
    tx = (
        TransactionBuilder(
            source_account=source_account,
            network_passphrase=network,
            base_fee=10000
        )
        .set_timeout(300)
        .append_invoke_contract_function_op(
            contract_id=contract_id,
            function_name='claim_fees_and_swap',
            parameters=[
                scval.to_address(keypair.public_key),
                SCVal.from_xdr(swap_path),
                scval.to_address(token_in_address),
                scval.to_uint128(amount_with_slippage),
            ],
        )
        .build()
    )

    # Prepare transaction to get Soroban-specific data (footprint, etc.)
    print("Preparing transaction on Soroban...")
    prepared_tx = soroban_rpc_server.prepare_transaction(tx)

    # Sign the prepared transaction
    print("Signing transaction...")
    prepared_tx.sign(keypair)

    # Submit the transaction to Horizon
    print("Submitting transaction to Horizon...")
    submit_response = horizon_server.submit_transaction(prepared_tx)

    if not submit_response.get('successful', False):
        raise Exception("Transaction failed: " + str(submit_response))

    print("Transaction submitted successfully. Fetching result...")

    # Get the transaction result from Soroban server to access Soroban metadata
    tx_info = soroban_server.get_transaction(submit_response['id'])
    if not tx_info or not tx_info.result_meta_xdr:
        raise Exception("No transaction metadata found.")

    # Extract the result from the Soroban metadata
    transaction_meta = TransactionMeta.from_xdr(tx_info.result_meta_xdr)
    return_val = transaction_meta.v3.soroban_meta.return_value
    final_amount = u128_to_int(return_val.u128)
    print("Swap executed successfully.")
    return final_amount


# =========================================
# Entry Point
# =========================================

print("Starting claim process...")
print(f"Swapping {token_to_claim.code} for {token_out.code}...")

# 0. Get the balance of the token to claim
amount = get_contract_balance(
    network,
    soroban_server,
    keypair,
    provider_swap_contract_id,
    token_to_claim.contract_id(network),
)

# 1. Find the swap path and estimated output
amount_with_slippage, swap_path_xdr = find_swap_path(
    base_api,
    token_to_claim.contract_id(network),
    token_out.contract_id(network),
    amount,
    True,
)

# 2. Execute the claim
amount = execute_claim_with_swap(
    network,
    soroban_server,
    horizon_server,
    keypair,
    provider_swap_contract_id,
    token_to_claim.contract_id(network),
    amount_with_slippage,
    swap_path_xdr,
)

print("Claim with swap completed successfully!")
print(f"Amount out: {amount / 10 ** 7} {token_out.code}")
const {
    Horizon,
    Keypair,
    Asset,
    Networks,
    TransactionBuilder,
    nativeToScVal,
    Address,
    Operation,
    xdr,
    rpc
} = require('@stellar/stellar-sdk');

// =========================================
// Configuration
// =========================================

// swap provider contract operator
const operatorSecretKey = 'SA...........'; // Replace with actual secret
const keypair = Keypair.fromSecret(operatorSecretKey);
const publicKey = keypair.publicKey();

// Input and output tokens
const tokenToClaim = new Asset('AQUA', 'GBNZILSTVQZ4R7IKQDGHYGY2QXL5QOFJYQMXPKWRRM5PAV7Y4M67AQUA');
const tokenOut = Asset.native(); // XLM
const slippage = '0.005';


// swap provider contract for AMM swaps on mainnet
// IMPORTANT: Replace with your deployed contract ID. This contract address is only for test purposes.
const providerSwapContractId = 'CDJCVXFIT2UIVNLC22OWCHABTWKXUSYYLPKKBLJW67ISMIWX56YJBGLS';

// Soroban and Horizon servers
const sorobanServer = new rpc.Server('https://mainnet.sorobanrpc.com');
const horizonServer = new Horizon.Server('https://horizon.stellar.org');
const networkPassphrase = Networks.PUBLIC;

// AQUA AMM API endpoint
const baseApi = 'https://amm-api.aqua.network/api/external/v1';

// =========================================
// Helpers
// =========================================

function u128ToInt(value) {
    const result = (BigInt(value.hi()._value) << 64n) + BigInt(value.lo()._value);
    if (result <= BigInt(Number.MAX_SAFE_INTEGER)) {
        return Number(result);
    } else {
        console.warn("Value exceeds JS safe integer range");
        return result;
    }
}

// Retrieves the balance of a specific token for the given account.
async function getContractTokenBalance(tokenAddress) {
    const sourceAccount = await horizonServer.loadAccount(publicKey);
    const tx = new TransactionBuilder(sourceAccount, {
        fee: '10000',
        networkPassphrase
    })
        .setTimeout(300)
        .addOperation(
            Operation.invokeContractFunction({
                contract: tokenAddress,
                function: 'balance',
                args: [nativeToScVal(new Address(providerSwapContractId))],
            })
        )
        .build();

    const sim = await sorobanServer.simulateTransaction(tx);
    return u128ToInt(sim.result.retval.value());
}

// Call the Find Path API to retrieve the swap chain and estimated amount.
async function findSwapPath(tokenIn, tokenOut, amount) {
    const endpoint = `${baseApi}/find-path/`;
    const response = await fetch(endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            token_in_address: tokenIn,
            token_out_address: tokenOut,
            amount,
            slippage
        })
    });

    const data = await response.json();
    if (!data.success) throw new Error('Failed to retrieve swap path');
    return {
        amountWithFee: data.amount_with_fee,
        swapPathXDR: data.swap_chain_xdr
    };
}

// Executes the chained swap transaction on Soroban and returns the final amount out.
async function executeClaimAndSwap(tokenIn, amountWithSlippage, swapPathXDR) {
    const account = await horizonServer.loadAccount(publicKey);
    
    // Build the transaction to invoke `swap_chained`
    const tx = new TransactionBuilder(account, {
        fee: '10000',
        networkPassphrase
    })
        .setTimeout(300)
        .addOperation(
            Operation.invokeContractFunction({
                contract: providerSwapContractId,
                function: 'claim_fees_and_swap',
                args: [
                    nativeToScVal(new Address(publicKey)),
                    xdr.ScVal.fromXDR(swapPathXDR, 'base64'),
                    nativeToScVal(new Address(tokenIn)),
                    nativeToScVal(amountWithSlippage, { type: 'u128' })
                ]
            })
        )
        .build();

    // Prepare transaction to get Soroban-specific data (footprint, etc.)
    const preparedTx = await sorobanServer.prepareTransaction(tx);
    // Sign the prepared transaction
    preparedTx.sign(keypair);

    // Submit the transaction to Horizon
    const submitResponse = await horizonServer.submitTransaction(preparedTx);
    if (!submitResponse.successful) {
        throw new Error('Transaction submission failed: ' + JSON.stringify(submitResponse));
    }

    // Get the transaction result from Soroban server to access Soroban metadata
    const txInfo = await sorobanServer.getTransaction(submitResponse.hash);
    const metaXdr = txInfo.resultMetaXdr;
    if (!metaXdr) throw new Error('No metadata');

    // Extract the result from the Soroban metadata
    const returnVal = metaXdr.v3().sorobanMeta().returnValue();
    return u128ToInt(returnVal.u128());
}

// =========================================
// Main
// =========================================

(async () => {
    try {
        console.log(`Swapping ${tokenToClaim.code} for ${tokenOut.code}...`);

        const tokenInAddress = tokenToClaim.contractId(networkPassphrase);
        const tokenOutAddress = tokenOut.contractId(networkPassphrase);

        // 1. Get the balance of the token to claim
        const balance = await getContractTokenBalance(tokenInAddress);
        console.log(`Token balance to claim: ${balance / 1e7} ${tokenToClaim.code}`);

        // 2. Find the swap path and estimated output
        const { amountWithFee, swapPathXDR } = await findSwapPath(tokenInAddress, tokenOutAddress, balance);
        console.log(`Swap path found. Final amount with slippage: ${amountWithFee / 1e7}`);

        // 3. Execute the claim
        const resultAmount = await executeClaimAndSwap(tokenInAddress, amountWithFee, swapPathXDR);
        console.log(`Claim + swap successful. Amount received: ${resultAmount / 1e7} ${tokenOut.code}`);
    } catch (err) {
        console.error('Error:', err.message || err);
    }
})();

Option 2: Claim Raw Fees

  • Function: claim_fees(e, operator: Address, token: Address) -> u128

  • Effect: Transfers entire token balance from the contract to fee_destination.

from stellar_sdk import Asset, Keypair, Network, scval, Server, SorobanServer, TransactionBuilder
from stellar_sdk.xdr import Int128Parts, TransactionMeta, UInt128Parts

# =========================================
# Configuration & Setup
# =========================================

# swap provider contract operator
operator_secret_key = "SA..........."
keypair = Keypair.from_secret(operator_secret_key)

# Input and output tokens
token_to_claim = Asset.native()

# swap provider contract for AMM swaps on mainnet
# IMPORTANT: Replace with your deployed contract ID. This contract address is only for test purposes.
provider_swap_contract_id = "CDJCVXFIT2UIVNLC22OWCHABTWKXUSYYLPKKBLJW67ISMIWX56YJBGLS"

# Soroban and Horizon servers
soroban_server = SorobanServer("https://mainnet.sorobanrpc.com")
horizon_server = Server("https://horizon.stellar.org/")
network = Network.PUBLIC_NETWORK_PASSPHRASE


# =========================================
# Utility Function
# =========================================

def u128_to_int(value: UInt128Parts) -> int:
    """Convert Uint128Parts to Python int."""
    return (value.hi.uint64 << 64) + value.lo.uint64


def i128_to_int(value: Int128Parts) -> int:
    """Convert int128Parts to Python int."""
    return (value.hi.int64 << 64) + value.lo.uint64


# =========================================
# Entry Point
# =========================================

print("Starting claim process...")
print(f"Claiming {token_to_claim.code}...")

tx = (
    TransactionBuilder(
        source_account=horizon_server.load_account(keypair.public_key),
        network_passphrase=network,
        base_fee=10000
    )
    .set_timeout(300)
    .append_invoke_contract_function_op(
        contract_id=provider_swap_contract_id,
        function_name='claim_fees',
        parameters=[
            scval.to_address(keypair.public_key),
            scval.to_address(token_to_claim.contract_id(network)),
        ],
    )
    .build()
)

# Prepare transaction to get Soroban-specific data (footprint, etc.)
print("Preparing transaction on Soroban...")
prepared_tx = soroban_server.prepare_transaction(tx)

# Sign the prepared transaction
print("Signing transaction...")
prepared_tx.sign(keypair)

# Submit the transaction to Horizon
print("Submitting transaction to Horizon...")
submit_response = horizon_server.submit_transaction(prepared_tx)

if not submit_response.get('successful', False):
    raise Exception("Transaction failed: " + str(submit_response))

print("Transaction submitted successfully. Fetching result...")

# Get the transaction result from Soroban server to access Soroban metadata
tx_info = soroban_server.get_transaction(submit_response['id'])
if not tx_info or not tx_info.result_meta_xdr:
    raise Exception("No transaction metadata found.")

# Extract the result from the Soroban metadata
transaction_meta = TransactionMeta.from_xdr(tx_info.result_meta_xdr)
return_val = transaction_meta.v3.soroban_meta.return_value
final_amount = u128_to_int(return_val.u128)

print("Claim completed successfully!")
print(f"Amount out: {final_amount / 10 ** 7} {token_to_claim.code}")
const {
    Horizon,
    Keypair,
    Asset,
    Networks,
    TransactionBuilder,
    nativeToScVal,
    Address,
    Operation,
    xdr,
    rpc
} = require('@stellar/stellar-sdk');

// =========================================
// Config & Setup
// =========================================

const operatorSecretKey = 'SA...........'; // your real key
const keypair = Keypair.fromSecret(operatorSecretKey);

const tokenToClaim = Asset.native();

// swap provider contract for AMM swaps on mainnet
// IMPORTANT: Replace with your deployed contract ID. This contract address is only for test purposes.
const providerSwapContractId = 'CDJCVXFIT2UIVNLC22OWCHABTWKXUSYYLPKKBLJW67ISMIWX56YJBGLS';

const sorobanServer = new rpc.Server('https://mainnet.sorobanrpc.com');
const horizonServer = new Horizon.Server('https://horizon.stellar.org');
const networkPassphrase = Networks.PUBLIC;

// =========================================
// Helpers
// =========================================

function u128ToInt(value) {
    const result = (BigInt(value.hi()._value) << 64n) + BigInt(value.lo()._value);
    if (result <= BigInt(Number.MAX_SAFE_INTEGER)) {
        return Number(result);
    } else {
        console.warn("Value exceeds JavaScript's safe integer range");
        return null;
    }
}

// =========================================
// Main
// =========================================

(async () => {
    try {
        console.log(`Claiming ${tokenToClaim.code} fees...`);

        const account = await horizonServer.loadAccount(keypair.publicKey());

        // Build transaction
        const tx = new TransactionBuilder(account, {
            fee: '10000',
            networkPassphrase
        })
            .setTimeout(300)
            .addOperation(
                Operation.invokeContractFunction({
                    contract: providerSwapContractId,
                    function: 'claim_fees',
                    args: [
                        nativeToScVal(new Address(keypair.publicKey())),
                        nativeToScVal(new Address(tokenToClaim.contractId(networkPassphrase)))
                    ],
                })
            )
            .build();

        // Prepare transaction to get Soroban-specific data (footprint, etc.)
        console.log('Preparing transaction...');
        const preparedTx = await sorobanServer.prepareTransaction(tx);

        // Sign the prepared transaction
        console.log('Signing...');
        preparedTx.sign(keypair);

        // Submit the transaction to Horizon
        console.log('Submitting...');
        const submitResponse = await horizonServer.submitTransaction(preparedTx);

        if (!submitResponse.successful) {
            throw new Error('Transaction failed: ' + JSON.stringify(submitResponse));
        }

        console.log('Transaction submitted. Fetching result...');

        // Get the transaction result from Soroban server to access Soroban metadata
        const txInfo = await sorobanServer.getTransaction(submitResponse.hash);
        const metaXdr = txInfo.resultMetaXdr;

        if (!metaXdr) {
            throw new Error('No result metadata.');
        }

        // Extract the result from the Soroban metadata
        const returnVal = metaXdr.v3().sorobanMeta().returnValue();
        const u128 = returnVal.u128();
        const finalAmount = u128ToInt(u128);

        console.log('Claim successful!');
        console.log(`Amount claimed: ${finalAmount / 1e7} ${tokenToClaim.code}`);
    } catch (err) {
        console.error('Error:', err.message || err);
    }
})();

PreviousExecuting Swaps with Provider FeesNextWhat is Stellar?

Last updated 5 days ago