Executing Swaps Through Specific Pool
This article explains how to prepare and execute swap through specific pool.
In this example we will swap 1 XLM to AQUA with Aquarius AMM. The swap will be executed by calling corresponding method of specific liquidity pool.
Direct swap through specific pool is good for contract sub-invocations as it requires less resources than chained path swap. But direct swap may lead to less optimal result.
โ๏ธ Please make sure account has established trustline for asset to receive. Otherwise, swap will fail until trustline is created. For more information please refer to documentation on Stellar.org.
Scroll here to see the complete code.
Executing Swap: Step by Step Guide
To perform a swap, you need to follow these steps:
1. Identify pool address: This part was covered earlier, please check corresponding article.
2. Specify user secret key, pool address, amount in, input and output token indices: You need to specify pool address, the amount of the input token you want to swap in stroops and swap direction by in and out tokens.
# Your Secret Key (keep it secure!)
user_secret_key = "S......."
# XLM/AQUA pool address
pool_address = "CCY2PXGMKNQHO7WNYXEWX76L2C5BH3JUW3RCATGUYKY7QQTRILBZIFWV"
# Amount in (0.1 XLM), in stroops (1 XLM = 10^7 stroops)
amount_in = 1000000
# tokens are [XLM, AQUA], so XLM index is 0
in_idx = 0
out_idx = 1
# slippage percent
slippage_percent = 1// Your Secret Key (keep it secure!)
const userSecretKey = "S.......";
// XLM/AQUA pool address
const poolAddress = "CCY2PXGMKNQHO7WNYXEWX76L2C5BH3JUW3RCATGUYKY7QQTRILBZIFWV";
// 0.1 XLM in stroops
const amountIn = 1000000;
// tokens are [XLM, AQUA], so XLM index is 0
const inIdx = 0;
const outIdx = 1;
// slippage percent
const slippagePercent = 1;3. Calculate minimum amount of token to receive: This can be achieved by simulating estimate_swap pool method.
def estimate_swap():
print("Estimating swap amount...")
soroban_server = SorobanServer(soroban_server_url)
keypair = Keypair.from_secret(user_secret_key)
source_account = soroban_server.load_account(keypair.public_key)
tx = (
TransactionBuilder(
source_account=source_account,
network_passphrase=Network.PUBLIC_NETWORK_PASSPHRASE,
base_fee=100
)
.append_invoke_contract_function_op(
contract_id=pool_address,
function_name="estimate_swap",
parameters=[
scval.to_uint32(in_idx),
scval.to_uint32(out_idx),
scval.to_uint128(amount_in),
]
)
.set_timeout(300)
.build()
)
print("Simulating 'estimate_swap' transaction...")
sim_result = soroban_server.simulate_transaction(tx)
if not sim_result or sim_result.error:
print("Simulation failed.", sim_result.error if sim_result else "")
return math.nan
retval = xdr.SCVal.from_xdr(sim_result.results[0].xdr)
estimated = u128_to_int(retval.u128)
print(f"Estimated result: {estimated / 1e7}")
return estimatedasync function estimateSwap() {
const sorobanServer = new rpc.Server(sorobanServerUrl);
const amount = new XdrLargeInt("u128", amountIn.toFixed()).toU128();
const inIdxSCVal = xdr.ScVal.scvU32(inIdx);
const outIdxSCVal = xdr.ScVal.scvU32(outIdx);
const keypair = Keypair.fromSecret(userSecretKey);
// Load the user account information from Soroban server
const account = await sorobanServer.getAccount(keypair.publicKey());
const contract = new Contract(poolAddress);
// Build the deposit transaction
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
// Append the invoke_contract_function operation for deposit
.addOperation(
contract.call(
"estimate_swap",
inIdxSCVal,
outIdxSCVal,
amount,
),
)
.setTimeout(TimeoutInfinite)
.build();
// Simulate the result
const simulateResult = await sorobanServer.simulateTransaction(tx);
if (!simulateResult.result) {
console.log(simulateResult.error);
console.log("Unable to simulate transaction");
return NaN;
}
const result = u128ToInt(simulateResult.result.retval.value());
console.log(`Estimated result: ${result}`);
return result;
}4. Perform swap operation by submitting corresponding transaction.
def execute_swap():
print("Executing swap...")
soroban_server = SorobanServer(soroban_server_url)
horizon_server = Server(horizon_server_url)
keypair = Keypair.from_secret(user_secret_key)
estimated_result = estimate_swap()
if math.isnan(estimated_result):
print("Estimation failed. Cannot proceed with swap.")
return
# Calculate minimum out after slippage
slippage_coefficient = (100 - slippage_percent) / 100.0
minimum_out = math.floor(estimated_result * slippage_coefficient)
# Build swap transaction
source_account = soroban_server.load_account(keypair.public_key)
tx = (
TransactionBuilder(
source_account=source_account,
network_passphrase=Network.PUBLIC_NETWORK_PASSPHRASE,
base_fee=100
)
.append_invoke_contract_function_op(
contract_id=pool_address,
function_name="swap",
parameters=[
scval.to_address(keypair.public_key),
scval.to_uint32(in_idx),
scval.to_uint32(out_idx),
scval.to_uint128(amount_in),
scval.to_uint128(minimum_out),
]
)
.set_timeout(300)
.build()
)
print("Preparing transaction for submission...")
prepared_tx = soroban_server.prepare_transaction(tx)
prepared_tx.sign(keypair)
print("Submitting transaction to Horizon...")
response = horizon_server.submit_transaction(prepared_tx)
if 'result_meta_xdr' not in response:
print("Transaction failed.")
return
meta = xdr.TransactionMeta.from_xdr(response['result_meta_xdr'])
if meta.v3 and meta.v3.soroban_meta:
out_value = u128_to_int(meta.v3.soroban_meta.return_value.u128)
print("Swap successful!")
print(f"Received token out: {out_value / 1e7}")
else:
print("No result returned by the contract.")async function executeSwap() {
const sorobanServer = new rpc.Server(sorobanServerUrl);
const horizonServer = new Horizon.Server(horizonServerUrl);
const amount = new XdrLargeInt("u128", amountIn.toFixed()).toU128();
const slippageCoefficient = (100 - slippagePercent) / 100;
const estimatedResult = await estimateSwap();
const estimateWithSlippage = Math.floor(estimatedResult * slippageCoefficient);
const minimumOut = new XdrLargeInt("u128", estimateWithSlippage.toFixed()).toU128();
const inIdxSCVal = xdr.ScVal.scvU32(inIdx);
const outIdxSCVal = xdr.ScVal.scvU32(outIdx);
const keypair = Keypair.fromSecret(userSecretKey);
// Load the user account information from Soroban server
const account = await sorobanServer.getAccount(keypair.publicKey());
const contract = new Contract(poolAddress);
// Build the swap transaction
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
// Append the invoke_contract_function operation for swap
.addOperation(
contract.call(
"swap",
xdr.ScVal.scvAddress(
Address.fromString(keypair.publicKey()).toScAddress(),
),
inIdxSCVal,
outIdxSCVal,
amount,
minimumOut,
),
)
.setTimeout(TimeoutInfinite)
.build();
// Prepare and sign the transaction
const preparedTx = await sorobanServer.prepareTransaction(tx);
preparedTx.sign(keypair);
// Submit the transaction to the Horizon server
const result = await horizonServer.submitTransaction(preparedTx);
// Parse the transaction metadata to extract results
const meta = (await sorobanServer.getTransaction(result.id)).resultMetaXdr;
const returnValue = meta.v3().sorobanMeta().returnValue();
// Extract swapped amount
const outValue = u128ToInt(returnValue.value());
console.log("Swap successful!");
console.log(`Received token out: ${outValue / 1e7}`);
}Complete Code Examples
This code swaps 1 XLM to AQUA with Aquarius AMM on mainnet.
To successfully execute the code, provide the secret key of a Stellar account with at least 3 XLM and an established trustline for AQUA.
Last updated