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:
Claims all of
token
.Swaps via your router along
swaps_chain
.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:
Claims all of
token
.Swaps via your router along
swaps_chain
.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 tofee_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);
}
})();
Last updated