# Soroswap Finance > The Swap Aggregator for Stellar: one API that quotes every Stellar venue and returns the best route, plus the Soroswap AMM. > ## Agent Instructions > Soroswap Aggregator API: base URL https://api.soroswap.finance, pick the network with ?network=mainnet or ?network=testnet (omitted means mainnet). > Auth: Authorization: Bearer sk_... on every call. Get a key at https://api.soroswap.finance/register. A missing or invalid key returns 403, never 401. Free plan 1 request/s; see https://docs.soroswap.finance/api/plans.md. > Flow: POST /quote (assetIn, assetOut as C... contract addresses, amount as an integer string in the smallest unit, tradeType, protocols) -> POST /quote/build with the unchanged quote and from -> the user signs the XDR -> POST /send. Build right after quoting: a Soroban XDR is valid for 180 seconds. Submit it as is, never re-simulate it. > AI tools: docs MCP at https://docs.soroswap.finance/mcp (claude mcp add --transport http soroswap-docs https://docs.soroswap.finance/mcp), skill with npx skills add https://docs.soroswap.finance. Other clients: https://docs.soroswap.finance/api/build-with-ai.md. > Units and Stellar pitfalls: https://docs.soroswap.finance/api/stellar-gotchas.md. Errors and retries: https://docs.soroswap.finance/api/errors.md. # Audits Source: https://docs.soroswap.finance/aggregator/audits The Runtime Verification audit of the Soroswap Aggregator smart contracts, report dated 2024-08-31, with a link to the PDF. The **Soroswap Aggregator Smart Contracts** were audited by [**Runtime Verification**](https://runtimeverification.com), a renowned firm specializing in formal verification and smart contract security audits. The audit reviewed the business logic, code, and interactions between the system’s modules to identify potential security vulnerabilities, functional errors, and opportunities for optimization. [The audit](https://github.com/soroswap/aggregator/blob/main/audits/2024-08-31_Soroswap_Aggregator_Audit_by_RuntimeVerification.pdf) ensured that the Aggregator contracts operate securely and efficiently, protecting users' funds and ensuring the smooth execution of token swaps across the various protocols supported by the [Aggregator.](/aggregator/supported-amms) [Read the report here](https://github.com/soroswap/aggregator/blob/main/audits/2024-08-31_Soroswap_Aggregator_Audit_by_RuntimeVerification.pdf) # Disclaimer Source: https://docs.soroswap.finance/aggregator/disclaimer Risks of protocol and token upgrades when the Aggregator calls subcontracts, plus user guidance. ### Disclaimer: Potential Risks of Protocol and Token Upgrades The Aggregator protocol interacts with various subcontracts through Adapter contracts and directly with exchange protocols. It is important to note that some of these protocols may upgrade their WebAssembly (WASM) code. While upgrades can bring new features and improvements, they also pose potential risks, including the introduction of malicious code. Similarly, tokens themselves can be upgraded or may contain malicious code, especially when dealing with unknown or unverified assets. #### Risk Description: When using the Aggregator, transactions transitively call multiple subcontracts. If the signature of a transaction passes the `require_auth` checks at each level, the called contracts can fully manipulate the signer's funds. This means that if a protocol the Aggregator accesses or a token being traded is upgraded to malicious code, there is a risk that a transaction could lead to the loss of some or all of the signer's funds, even if it passes the minimum or maximum checks on dynamic assets (e.g., `swap_exact_tokens_for_tokens`). #### User Guidance: * **Inspect Contracts and Tokens:** Users should carefully inspect and understand the footprint and authorization payloads of each contract call within a transaction, as well as the nature and history of the tokens being traded. * **Understand the Risk:** We acknowledge that not every user may have the technical knowledge to discern when a transaction might have malicious effects. Therefore, we recommend users educate themselves on the potential risks involved with protocol upgrades, subcontract calls, and token interactions. * **Educational Resources:** For more information on understanding contract interactions, token risks, and the dangers associated with protocol and token upgrades, please refer to the following [resources](https://developers.stellar.org/docs/build/smart-contracts/example-contracts/auth#require_auth). By using the Aggregator, you acknowledge the potential risks associated with subcontract calls, protocol upgrades, and token interactions. Please use this service responsibly and remain vigilant when interacting with any smart contracts and tokens. # Supported venues Source: https://docs.soroswap.finance/aggregator/supported-amms The venues the Soroswap Aggregator API routes through and their protocols values: soroswap, aqua (Aquarius), sushi (SushiSwap V3), comet and sdex (the Stellar classic DEX, quoted only when listed), and how each one executes. The Aggregator API prices every venue below and returns the best route. Soroban routes can split one trade across several venues, hop by hop, and execute in a single atomic transaction through [Aggregator v3](/aggregator/technical-reference/contracts/aggregator-v3). SDEX is quoted alongside as a separate classic path. | Venue | Pool type | `protocols` value | How it executes | | - | - | - | - | | [Soroswap AMM](https://soroswap.finance) | Constant product | `soroswap` | Aggregator v3 (`Hop::Soroswap`) | | [Aquarius](https://aqua.network) | Constant product, stable, concentrated | `aqua` | Aggregator v3 (`Hop::Aqua`) | | [SushiSwap V3](https://www.sushi.com) | Concentrated liquidity (Uniswap V3 style) | `sushi` | Aggregator v3 (`Hop::Sushi`) | | Comet | Weighted pools (Balancer style) | `comet` | Aggregator v3 (`Hop::Comet`) | | SDEX (Stellar classic order book) | Order book and classic liquidity pools | `sdex` | Classic path payment | * **Soroban venues** can be combined in one route, and a quote that uses them carries `"platform": "aggregator"`. You can also execute such routes from your own contract. * **SDEX:** quoted only when `"sdex"` is in `protocols`. A quote with `"platform": "sdex"` is built as a classic path payment, not an aggregator call. See [Routing through SDEX](/api/sdex). `GET /protocols?network=mainnet` returns the venue list the API accepts in `protocols`. Some assets only trade on one venue. deJTRSY, for example, has its liquidity in SushiSwap V3 pools. When you need such a pair, add that venue to `protocols`. # Aggregator v3 Source: https://docs.soroswap.finance/aggregator/technical-reference/contracts/aggregator-v3 Reference for the Soroswap Aggregator v3 contract on mainnet: entrypoints, route and hop types, fees, errors and events. Aggregator v3 is the contract that executes every Soroban swap built by the [Soroswap Aggregator API](/) on mainnet. The caller passes typed routes, and the contract runs them hop by hop across Soroswap, Aquarius, SushiSwap V3 and Comet in one atomic transaction. It then takes the fee and pays the user out of a single balance-difference check. | | Mainnet | | - | - | | Aggregator v3 | `CARVQXFP4JF5ELLXUMQ6DALR346YVGBMQOHB4ENA7SSVXAYABXLBDDC4` | | Fee treasury | `CCIGFRCEKN33YOGN37URMPPCWLJ43U2BXNHCLKH7LKUXJJAUTJMFVASK` | > [!WARNING] > > The previous aggregator, `CAYP3UWLJM7ZPTUKL6R6BFGTRWLZ46LRKOXTERI2K6BIJAWGYY62TXTO` (`swap_exact_tokens_for_tokens` with a `DexDistribution` vector), is deprecated. > Since 2026-09-24 the Soroswap API neither quotes for it nor accepts transactions against it on `/send`. > It has no SushiSwap or Comet support and will not get any. Integrations that call it should migrate to v3. The contract's interface is on chain. To generate bindings or a Rust client, fetch the WASM: ```bash theme={null} stellar contract fetch --id CARVQXFP4JF5ELLXUMQ6DALR346YVGBMQOHB4ENA7SSVXAYABXLBDDC4 \ --network mainnet -o aggregator_v3.wasm ``` ## Entrypoints ### `swap_exact_in` ```rust theme={null} fn swap_exact_in( user: Address, token_in: Address, amount_in: i128, // gross input, fee included token_out: Address, min_out: i128, routes: Vec, partner: Option, ) -> i128 // token_out delivered to `user` ``` 1. `user.require_auth()`. The aggregator then pulls `amount_in` of `token_in` from `user` with `transfer`. 2. The fee is taken from the input and sent to the treasury (see [Fees](#fees)). 3. Each route runs its hops in order, with the aggregator as the caller of every venue. Each hop's output becomes the next hop's input. 4. The aggregator compares its own `token_out` balance before and after the routes. That difference is what `user` receives, and it must be at least `min_out`, or the whole transaction reverts with `InsufficientOutput`. **The routes must add up to the post-fee amount:** `Σ routes[i].amount_in == amount_in - fee`. If they do not, the call fails with `BadRoute`. ### `swap_exact_out` ```rust theme={null} fn swap_exact_out( user: Address, token_in: Address, max_in: i128, // ceiling on input, fee included token_out: Address, amount_out: i128, // minimum to receive routes: Vec, partner: Option, ) -> i128 // input spent, fee included ``` The routes are exact-in hops sized from a forward quote, and their `amount_in` values may not exceed `max_in`. The fee is charged on the input the routes actually consumed. `user` receives everything delivered (at least `amount_out`), and any unspent input is refunded. There is no separate recipient: both entrypoints pay `user`. ## Types ```rust theme={null} pub struct Route { pub amount_in: i128, // this route's share of the post-fee input pub hops: Vec, } pub enum Hop { Soroswap(SoroswapHop), Aqua(AquaHop), Sushi(SushiHop), Phoenix(PhoenixHop), Comet(CometHop), } pub struct SoroswapHop { pub pair: Address, pub token_in: Address, pub token_out: Address } pub struct SushiHop { pub pool: Address, pub token_in: Address, pub token_out: Address, pub zero_for_one: bool } pub struct PhoenixHop { pub pool: Address, pub token_in: Address, pub token_out: Address } pub struct CometHop { pub pool: Address, pub token_in: Address, pub token_out: Address } /// One `swap_chained` call on the Aquarius router, over one or more pools. pub struct AquaHop { pub token_in: Address, /// (pool's full token set sorted by Address, pool hash, token out of this step) pub steps: Vec<(Vec
, BytesN<32>, Address)>, } pub struct Partner { pub id: Symbol, pub bps: u32 } ``` Rules the contract checks before it asks for authorization (each failure is `BadRoute`): * Every route has at least one hop and `amount_in > 0`. * The first hop consumes `token_in`, each hop consumes what the previous one produced, and the last hop produces `token_out`. * Exact-in: the route amounts add up to exactly `amount_in - fee`. Exact-out: they add up to at most `max_in`. Per venue: | Venue | What to pass | | - | - | | Soroswap | `pair` is the Soroswap pair contract for `token_in`/`token_out`. | | Aquarius | `steps[i].0` is the pool's **full** token set, sorted by binary `ScAddress` order. This is not string order: base32 encodes the digits `2` to `7` above every letter. `steps[i].1` is the pool hash from the Aquarius router (`get_pools`). Consecutive Aquarius pools go in one `AquaHop`. | | SushiSwap V3 | `zero_for_one` is `token_in == pool.token0()`. A wrong value makes the pool pull a token the aggregator did not grant, and the swap fails. | | Comet | The pool contract. | ## Fees Every swap pays **either** the protocol fee **or** a partner fee, never both. * `partner = None`: the rate is `protocol_bps`, which is 10 bps today. Read it from `config()`. * `partner = Some(Partner { id, bps })`: the rate is `bps`. It must sit between `min_partner_bps` (25 today) and 1,000. `id` is at most 9 characters. The fee is booked to `id` in the treasury. * `fee = floor(amount_in * bps / 10000)`. A fee that rounds to zero is zero. The contract accepts any `id` and books the fee under it. That fee is only paid out to you if the `id` is the partner id Soroswap assigned you. API partners read their `id` and rate from `GET /partner`. If you call the contract without the API and want a partner fee, ask the Soroswap team for your partner id first. When a quote was priced with a partner rate, the on-chain call has to pass the same `Partner`, or the route amounts will not add up. ## Authorization `user` authorizes `swap_exact_in` (or `swap_exact_out`) and, below it, the `transfer` of `token_in` from `user` to the aggregator. * **`user` is an account (G...):** simulation records both entries in one authorization tree, and the user signs it as usual. * **`user` is your contract:** your contract's direct call to the aggregator is authorized implicitly, but the token `transfer` one level below is not. Authorize it with `env.authorize_as_current_contract` right before the call. See [Cross-Contract Integration](/aggregator/technical-reference/cross-contract-integration). ## Errors | Code | Name | Meaning | | - | - | - | | 1 | `Paused` | Swaps are paused by the admin. | | 2 | `InvalidAmount` | `amount_in`, `min_out`, `max_in` or `amount_out` is not positive. | | 3 | `SameToken` | `token_in == token_out`. | | 4 | `PartnerBpsOutOfRange` | The partner `bps` is below `min_partner_bps` or above 1,000. | | 5 | `BadRoute` | A route breaks the continuity or amount rules above. | | 6 | `AllowanceLeft` | A venue left an allowance standing after its hop. | | 7 | `InsufficientOutput` | Delivered less than `min_out` / `amount_out`, or a hop returned nothing. | | 8 | `InsufficientInput` | Exact-out spent more than `max_in`. | | 11 | `PartnerIdTooLong` | The partner `id` is longer than 9 characters. | Codes 9, 10 and 12 are admin-only (`NoPendingAdmin`, `BpsOutOfRange`, `NotInitialized`). ## Events Each swap emits one `swap` event: * Topics: `["swap", token_in, token_out, user]`. * Data: `{ amount_in, amount_out, fee, partner: Option }`. For exact-out, `amount_in` is the consumed input plus the fee. # Cross-Contract Integration Source: https://docs.soroswap.finance/aggregator/technical-reference/cross-contract-integration Call Soroswap Aggregator v3 from your own Soroban contract: client import, route building, the token authorization, and a verified mainnet example. Use this when **your contract** calls the aggregator: a router, a vault or an intent settler. If your backend just needs a transaction for a user to sign, use the API's `/quote/build` instead and skip this page. The full contract reference is in [Aggregator v3](/aggregator/technical-reference/contracts/aggregator-v3). ## 1. Import the client The WASM on chain is the interface. Fetch it once and import it: ```bash theme={null} stellar contract fetch --id CARVQXFP4JF5ELLXUMQ6DALR346YVGBMQOHB4ENA7SSVXAYABXLBDDC4 \ --network mainnet -o aggregator_v3.wasm ``` ```rust theme={null} mod aggregator { soroban_sdk::contractimport!(file = "aggregator_v3.wasm"); } use aggregator::{Hop, Route, SushiHop}; ``` `contractimport!` generates `aggregator::Client` and every wire type (`Route`, `Hop`, `SoroswapHop`, `AquaHop`, `SushiHop`, `CometHop`, `PhoenixHop`, `Partner`). ## 2. Build the routes A route names the pools, in order, that take `token_in` to `token_out`, like the single-pool example below. The route amounts must add up to `amount_in - fee`, with `fee = floor(amount_in * bps / 10000)`. ## 3. Authorize the token pull, then call The aggregator pulls `token_in` from `user` with `transfer`. When `user` is your contract, that `transfer` happens one call below your own, so your contract must authorize it explicitly: ```rust theme={null} use soroban_sdk::{ auth::{ContractContext, InvokerContractAuthEntry, SubContractInvocation}, vec, Address, Env, IntoVal, Symbol, }; let me = env.current_contract_address(); env.authorize_as_current_contract(vec![ &env, InvokerContractAuthEntry::Contract(SubContractInvocation { context: ContractContext { contract: token_in.clone(), fn_name: Symbol::new(&env, "transfer"), args: (me.clone(), aggregator_id.clone(), amount_in).into_val(&env), }, sub_invocations: vec![&env], }), ]); let delivered: i128 = aggregator::Client::new(&env, &aggregator_id).swap_exact_in( &me, // user: the tokens come from, and go back to, this contract &token_in, &amount_in, // gross, fee included &token_out, &min_out, &routes, &None, // or Some(Partner { id, bps }) if you have a partner rate ); ``` `delivered` is credited to `me`. Forward it wherever your contract needs it. ## Example: USDC to deJTRSY through a SushiSwap V3 pool This contract was deployed and run on mainnet on 2026-10-08: ```rust theme={null} #![no_std] use soroban_sdk::{ auth::{ContractContext, InvokerContractAuthEntry, SubContractInvocation}, contract, contractimpl, vec, Address, Env, IntoVal, Symbol, }; mod aggregator { soroban_sdk::contractimport!(file = "aggregator_v3.wasm"); } use aggregator::{Hop, Route, SushiHop}; #[contract] pub struct MyRouter; #[contractimpl] impl MyRouter { pub fn swap_via_sushi( e: Env, aggregator_id: Address, pool: Address, token_in: Address, token_out: Address, zero_for_one: bool, amount_in: i128, share: i128, // amount_in - fee min_out: i128, ) -> i128 { let me = e.current_contract_address(); let routes = vec![ &e, Route { amount_in: share, hops: vec![ &e, Hop::Sushi(SushiHop { pool, token_in: token_in.clone(), token_out: token_out.clone(), zero_for_one, }), ], }, ]; e.authorize_as_current_contract(vec![ &e, InvokerContractAuthEntry::Contract(SubContractInvocation { context: ContractContext { contract: token_in.clone(), fn_name: Symbol::new(&e, "transfer"), args: (me.clone(), aggregator_id.clone(), amount_in).into_val(&e), }, sub_invocations: vec![&e], }), ]); aggregator::Client::new(&e, &aggregator_id).swap_exact_in( &me, &token_in, &amount_in, &token_out, &min_out, &routes, &None, ) } } ``` The call used these values: | Argument | Value | | - | - | | `pool` | `CDMJRRH5MAJLB7T5SQAWQOOL7UJ3BABGTURSVKIE6AEDSXESDDJIBVCT` (deJTRSY/USDC, fee tier 500) | | `token_in` | USDC `CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75` (token1) | | `token_out` | deJTRSY `CBI7UCH5KGSVQRO5H4SUCZUTZABCITZLRHQQZTWL2TK4RZ72TAR6IHRV` (token0) | | `zero_for_one` | `false`, because USDC is not `token0` | | `amount_in` / `share` | `5000000` / `4995000` (0.5 USDC, minus the 10 bps fee) | The swap delivered 0.4865 deJTRSY to the contract. See [the transaction](https://stellar.expert/explorer/public/tx/95ad366282a9ee0ac9adcbe6c55a617dcc458dd4c9742b985143be92891dbc15). The same swap from an account, with the user signing (no `authorize_as_current_contract` needed), in both directions: [USDC → deJTRSY](https://stellar.expert/explorer/public/tx/2e90c3d95b1d3ac5dd1cabaae4838e6a93473e74c49218a6598f01a6d602325e) and [deJTRSY → USDC](https://stellar.expert/explorer/public/tx/83b710e9e24a5a7ce9b9937f61c5126aa23a68aa480bf9d85b1e334101a0a132). ## Checklist * Call `CARVQXFP4JF5ELLXUMQ6DALR346YVGBMQOHB4ENA7SSVXAYABXLBDDC4`, not the deprecated `CAYP3U…` aggregator. * Make the route amounts add up to `amount_in - fee`, with the same `partner` the quote was priced with. * Set `zero_for_one = (token_in == pool.token0())` on every Sushi hop. * Sort each Aquarius step's token set by `Address` order, not string order. * When `user` is a contract, authorize `token_in.transfer(user, aggregator, amount_in)`. * Set `min_out` from your own slippage tolerance. It is the only price guarantee: the aggregator calls each venue with no real per-hop minimum (0 or 1, or none for SushiSwap and Phoenix) and checks only the total at the end. # Audits Source: https://docs.soroswap.finance/amm/audits Link to the OtterSec security audit report for the Soroswap core protocol. **Audit Report by OtterSec:** [View the report](https://github.com/soroswap/core/blob/main/audits/2024-02-22_soroswap_ottersec_audit.pdf) For a detailed evaluation of Soroswap’s security and compliance, refer to the audit report conducted by [**OtterSec**](https://osec.io). The audit provides an in-depth analysis of the Soroswap core protocol, identifying any vulnerabilities and ensuring adherence to best practices. # Ecosystem Participants Source: https://docs.soroswap.finance/amm/ecosystem-participants Roles in the Soroswap ecosystem: liquidity providers, traders, developers, and the team and community. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-20-a-las-16-23-07.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=63e4d720058641b18272c451aef0f5e0) The Soroswap ecosystem is primarily comprised of three types of users: liquidity providers, traders, and developers. Liquidity providers are [incentivized](/tutorials/stellar-classic-assets/testing#accessing-test-tokens) to contribute tokens to common liquidity pools. Traders can swap these tokens for one another for a fixed [0.30% fee](/concepts/fees) (which goes to liquidity providers). Developers can integrate directly with Soroswap smart contracts to power new and exciting interactions with tokens, trading interfaces, retail experiences, and more. In total, interactions between these classes create a positive feedback loop, fueling digital economies by defining a common language through which tokens can be pooled, traded and used. ## Liquidity Providers Liquidity providers, or LPs, are not a homogenous group: * Passive LPs are token holders who wish to passively invest their assets to accumulate trading fees. * Professional LPs are focused on market making as their primary strategy. They usually develop custom tools and ways of tracking their liquidity positions across different DeFi projects. * Token projects sometimes choose to become LPs to create a liquid marketplace for their token. This allows tokens to be bought and sold more easily, and unlocks interoperability with other DeFi projects through Soroswap. * Finally, some DeFi pioneers are exploring complex liquidity provision interactions like incentivized liquidity, liquidity as collateral, and other experimental strategies. Soroswap is the perfect protocol for projects to experiment with these kinds of ideas. ## Traders There are a several categories of traders in the protocol ecosystem: * Speculators use a variety of community built tools and products to swap tokens using liquidity pulled from the Soroswap protocol. * Arbitrage bots seek profits by comparing prices across different platforms to find an edge. (Though it might seem extractive, these bots actually help equalize prices across broader Soroban/Stellar markets and keep things fair.) * DAPP users buy tokens on Soroswap for use in other applications on Soroban/Stellar. * Smart contracts that execute trades on the protocol by implementing swap functionality (from products like DEX aggregators to custom Soroban scripts). In all cases, trades are subject to the same flat fee for trading on the protocol. Each is important for increasing the accuracy of prices and incentivizing liquidity. ## Developers/Projects There are far too many ways Soroswap is used in the wider Soroban/Stellar ecosystem to count, but some examples include: * The open-source, accessible nature of Soroswap means there are countless UX experiments and front-ends built to offer access to Soroswap functionality. * Wallets often integrate swapping and liquidity provision functionality as a core offering of their product. * DEX (decentralized exchange) aggregators pull liquidity from many liquidity protocols to offer traders the best prices by splitting their trades. Soroswap is the biggest single decentralized liquidity source for these projects. * Smart contract developers use the suite of functions available to invent new DeFi tools and other various experimental ideas. ## Soroswap Team and Community The Soroswap team along with the broader Soroswap community drives development of the protocol and ecosystem. # Glossary Source: https://docs.soroswap.finance/amm/glossary Definitions of key Soroswap and automated market maker terms used across the documentation. #### Asset Are digital tokens representing various forms of value, including cryptocurrencies, fiat currencies, and other financial instruments. They are uniquely identified by their **asset code** and the **issuer’s public key**, with [assets ](https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/assets)created through payment operations by the issuing account. Assets in Soroban, the smart contract platform of Stellar are also identified by the [Stellar Asset Contract](https://developers.stellar.org/docs/build/guides/tokens) (SAC), which is the address of the smart contract associated to the asset. For example, for the USDC token, a stable coin emmited by Circle, we have several ways to identify this asset ``` { "code": "USDC", "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", "contract": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", "name": "USD Coin" } ``` In Soroswap we use either the SAC contract address, or the CODE:ISSUER combination #### Automated market maker An automated market maker is a smart contract on Soroban that holds on-chain liquidity reserves. Users can trade against these reserves at prices set by an automated market making formula. #### Constant product formula The automated market making algorithm used by Soroswap. See [x\*y=k](/amm/glossary#x--y--k). **Core**\ Smart contracts that are essential for Soroswap to exist. Upgrading to a new version of core would require a liquidity migration. **Factory**\ A smart contract that deploys a unique smart contract for any trading pair. **Flash swap**\ A trade that uses the tokens being purchased before paying for them. Currently flash swaps are not supported in Soroswap as Soroban does not support reentrancy. **Invariant**\ The "k" value in the constant product formula. **Liquidity provider / LP**\ A liquidity provider is someone who deposits an equivalent value of two tokens into the liquidity pool within a pair. Liquidity providers take on price risk and are compensated with fees. **Mid price**\ The price between what users can buy and sell tokens at a given moment. In Soroswap, this is the ratio of the two token reserves. **Pair**\ A smart contract deployed from the Soroswap Factory that enables trading between two tokens. Pair contracts are also called "Pools" because they are the contracts that hold the liquidity of both tokens. Example: The [`CDJDRGUCHANJDXALZVJ5IZVB76HX4MWCON5SHF4DE5HB64CBBR7W2ZCD`](https://stellar.expert/explorer/public/contract/CDJDRGUCHANJDXALZVJ5IZVB76HX4MWCON5SHF4DE5HB64CBBR7W2ZCD) contract is the Pair/Pool contract that holds liquidity for the XLM-USDx pair. Check all Soroswap AMM pairs in [https://dune.com/queries/4341139/7289687](https://dune.com/queries/4341139/7289687) **Periphery**\ External smart contracts that are useful, but not required for Soroswap to exist. New periphery contracts can always be deployed without migrating liquidity. **Price impact**\ The difference between the mid-price and the execution price of a trade. **Pool**\ Liquidity within a pair is pooled across all liquidity providers. See "Pair" **Slippage**\ The amount the price moves in a trading pair between when a transaction is submitted and when it is executed. Is the same as "price impact", and often is expressed in percentage. **Token Interface** Token interface standar. [Read more](https://developers.stellar.org/docs/smart-contracts/tokens/token-interface) # How Soroswap AMM works Source: https://docs.soroswap.finance/amm/how-it-works Soroswap AMM basics: constant product formula, liquidity pools, pool tokens, and the 0.30% fee. ### Quick Reference ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2025-04-24-a-las-15-07-19.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=e188b853fb4c215e1e35471ab87e833b) Soroswap AMM is an *automated liquidity protocol* powered by a [constant product formula](/amm/glossary#constant-product-formula) and implemented in a system of non-upgradeable smart contracts on the [Soroban](https://developers.stellar.org/docs/smart-contracts) blockchain. It obviates the need for trusted intermediaries, prioritizing **decentralization**, **censorship resistance**, and **security**. Soroswap is **open-source software** licensed under the [Apache 2.0 Licence](https://github.com/soroswap/core/blob/main/LICENSE). Each Soroswap Pair smart contract, manages a liquidity pool made up of reserves of two tokens. These can be either (1) Soroban native tokens implemeting the [Soroban Token Interface](https://developers.stellar.org/docs/smart-contracts/tokens/token-interface), or (2) [Stellar Assets](https://developers.stellar.org/docs/issuing-assets/anatomy-of-an-asset) Anyone can become a liquidity provider (LP) for a pool by depositing an equivalent value of each underlying token in return for pool tokens. These tokens track pro-rata LP shares of the total reserves, and can be redeemed for the underlying assets at any time. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-20-a-las-15-42-48.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=578e07ef1ac52b08a3449de1580c6472) Pairs act as automated market makers, standing ready to accept one token for the other as long as the “constant product” formula is preserved. This formula, most simply expressed as `x * y = k`, states that trades must not change the product (`k`) of a pair’s reserve balances (`x` and `y`). Because `k` remains unchanged from the reference frame of a trade, it is often referred to as the invariant. This formula has the desirable property that larger trades (relative to reserves) execute at exponentially worse rates than smaller ones. In practice, Soroswap applies a 0.30% fee to trades, which is added to reserves. As a result, each trade actually increases `k`. This functions as a payout to LPs, which is realized when they burn their pool tokens to withdraw their portion of total reserves. In the future, this fee may be reduced to 0.25%, with the remaining 0.05% withheld as a protocol-wide charge. This is the only change that the protocol accepts. The total 0.30% fee it's hardcoded in the code and cannot be changed. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-20-a-las-16-20-55.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=36ba47644699b8511c34069376a97821) Because the relative price of the two pair assets can only be changed through trading, divergences between the Soroswap price and external prices create arbitrage opportunities. This mechanism ensures that Soroswap prices always trend toward the market-clearing price. ## Further reading To see how token swaps work in practice, and to walk through the lifecycle of a swap, check out [Swaps](/concepts/swap). Or, to see how liquidity pools work, see [Pools](/concepts/liquidity-pools). Ultimately, of course, the Soroswap protocol is just smart contract code running on Soroban. To understand how they work, head over to [The Soroban Home Page](https://developers.stellar.org/docs/smart-contracts). # Soroswap AMM (DEX) Source: https://docs.soroswap.finance/amm/index The Soroswap AMM: the constant-product liquidity pool protocol that swaps happen on, written in Rust for Soroban. The Soroswap AMM is the DeFi primitive underneath everything else here: a set of liquidity pools that let swaps happen without an order book. It is a Rust implementation of the UniswapV2 design on Soroban. > **[How Soroswap AMM works](/amm/how-it-works)** > > The constant-product formula and the lifecycle of a swap. > **[Ecosystem Participants](/amm/ecosystem-participants)** > > Traders, liquidity providers, arbitrageurs and what each one is for. > **[Technical Reference](/amm/technical-reference)** > > Contracts, deployed addresses, error codes and integration. > **[Glossary](/amm/glossary)** > > The vocabulary used across these pages. > **[Audits](/amm/audits)** > > The OtterSec audit of the core protocol. The full source lives at [github.com/soroswap/core](https://github.com/soroswap/core). # Smart contracts Source: https://docs.soroswap.finance/amm/technical-reference/contracts/index Map of Soroswap core and periphery contracts: factory, pairs, library, and router. Soroswap is a smart contract system. [Core](/amm/technical-reference/contracts#core) contracts provide fundamental safety guarantees for all parties interacting with Soroswap. [Periphery](/amm/technical-reference/contracts#periphery) contracts interact with one or more core contracts but are not themselves part of the core. ## Core [Source code](https://github.com/soroswap/core) The core consists of a singleton [factory](/amm/technical-reference/contracts#factory) and many [pairs](/amm/technical-reference/contracts#pairs), which the factory is responsible for creating and indexing. These contracts are quite minimal, even brutalist. The simple rationale for this is that contracts with a smaller surface area are easier to reason about, less bug-prone, and more functionally elegant. Perhaps the biggest upside of this design is that many desired properties of the system can be asserted directly in the code, leaving little room for error. One downside, however, is that core contracts are somewhat user-unfriendly. In fact, interacting directly with these contracts is not recommended for most use cases. Instead, a periphery contract should be used. ### SoroswapFactory: [Reference documentation](/amm/technical-reference/contracts/soroswap-factory) The factory holds the generic bytecode responsible for powering pairs. Its primary job is to create one and only one smart contract per unique token pair. It also contains logic to turn on the protocol charge. ### SoroswapPairs: [Reference documentation](/amm/technical-reference/contracts/soroswap-pair) Pairs have two primary purposes: serving as automated market makers and keeping track of pool token balances. They also expose data which can be used to build decentralized price oracles. ## Periphery: [Source code](https://github.com/soroswap/core/tree/main/contracts) The periphery is a constellation of smart contracts designed to support domain-specific interactions with the core. Because of Soroswap's permissionless nature, the contracts described below have no special privileges, and are in fact only a small subset of the universe of possible periphery-like contracts. However, they are useful examples of how to safely and efficiently interact with the Soroswap Protocol. ### SoroswapLibrary: [Reference documentation](/amm/technical-reference/contracts/soroswap-library) The library provides a variety of convenience functions for fetching data and pricing. ### SoroswapRouter: [Reference documentation](/amm/technical-reference/contracts/soroswap-router) The router, which uses the library, fully supports all the basic requirements of a front-end offering trading and liquidity management functionality. # SoroswapFactory Source: https://docs.soroswap.finance/amm/technical-reference/contracts/soroswap-factory SoroswapFactory contract overview and interface for creating and indexing one pair per token pair. Creates one SoroswapPair (Liquidity Pool) smart contract per unique token pair. It does receive as initialization argument the WASM hash of a already installed SoroswapPair smart contract. Check the code here: [https://github.com/soroswap/core/tree/main/contracts/factory/src](https://github.com/soroswap/core/tree/main/contracts/factory/src) Here is the contract interface: ```rust theme={null} pub trait SoroswapFactoryTrait { /* *** Read-only functions: *** */ /// Returns the recipient of the fee. fn fee_to(e: Env) -> Result; /// Returns the address allowed to change `fee_to`. fn fee_to_setter(e: Env) -> Result; /// Checks if fees are enabled. fn fees_enabled(e: Env) -> Result; /// Returns the total number of pairs created through the factory so far. fn all_pairs_length(e: Env) -> Result; /// Returns the address of the pair for `token_a` and `token_b`, if it has been created. fn get_pair(e: Env, token_a: Address, token_b: Address) -> Result; /// Returns the address of the nth pair (0-indexed) created through the factory. fn all_pairs(e: Env, n: u32) -> Result; /// Returns a boolean indicating if a pair exists for the given `token_a` and `token_b`. fn pair_exists(e: Env, token_a: Address, token_b: Address) -> Result; /* *** State-Changing Functions: *** */ /// Sets the `fee_to_setter` address and initializes the factory. /// /// # Arguments /// /// * `e` - An instance of the `Env` struct. /// * `setter` - The address to set as the `fee_to_setter`. /// * `pair_wasm_hash` - The Wasm hash of the SoroswapPair contract. fn initialize(e: Env, setter: Address, pair_wasm_hash: BytesN<32>) -> Result<(), FactoryError>; /// Sets the `fee_to` address. /// /// # Arguments /// /// * `e` - An instance of the `Env` struct. /// * `to` - The address to set as the `fee_to`. fn set_fee_to(e: Env, to: Address)-> Result<(), FactoryError>; /// Sets the `fee_to_setter` address. /// /// # Arguments /// /// * `e` - An instance of the `Env` struct. /// * `new_setter` - The address to set as the new `fee_to_setter`. fn set_fee_to_setter(e: Env, new_setter: Address)-> Result<(), FactoryError>; /// Sets whether fees are enabled or disabled. /// /// # Arguments /// /// * `e` - An instance of the `Env` struct. /// * `is_enabled` - A boolean indicating whether fees are enabled or disabled. fn set_fees_enabled(e: Env, is_enabled: bool)-> Result<(), FactoryError>; /// Creates a pair for `token_a` and `token_b` if one doesn't exist already. /// /// # Arguments /// /// * `e` - An instance of the `Env` struct. /// * `token_a` - The address of the first token in the pair. /// * `token_b` - The address of the second token in the pair. fn create_pair(e: Env, token_a: Address, token_b: Address) -> Result; } ``` # SoroswapLibrary Source: https://docs.soroswap.finance/amm/technical-reference/contracts/soroswap-library SoroswapLibrary crate: how to add it, and reference for its pricing and helper functions. The SoroswapLibrary is a rust crate that anyone can implement in their smart contracts. Check all the documentation here: [https://docs.rs/soroswap-library/latest/soroswap\_library/](https://docs.rs/soroswap-library/latest/soroswap_library/) ## Code You can find the Soroswap library code on the [Soroswap GitHub repository](https://github.com/soroswap/core/tree/main/contracts/library). in [https://github.com/soroswap/core/tree/main/contracts/library](https://github.com/soroswap/core/tree/main/contracts/library) ## Usage as a crate 1.- Add this to your Cargo.toml: ```toml theme={null} [dependencies] soroswap-library = "2.0.0" ``` Check [crates.io/crates/soroswap-library](https://crates.io/crates/soroswap-library) for the current version. 2.- Import it: ``` use soroswap_library; ``` 3.- Use it: ``` let quote = soroswap_library::quote(amount_a, reserve_a, reserve_b) ``` ## Public functions Every function below is `pub` in the crate and returns a `Result`, with `SoroswapLibraryError` as the error type. Signatures are from version 2.0.0. ### sort\_tokens ```rust theme={null} pub fn sort_tokens(token_a: Address, token_b: Address) -> Result<(Address, Address), SoroswapLibraryError>; ``` Sorts two token addresses into the canonical order a pair uses. Errors if the two addresses are identical. ### pair\_for ```rust theme={null} pub fn pair_for(e: Env, factory: Address, token_a: Address, token_b: Address) -> Result; ``` Returns the address of the pair for two tokens. ### get\_reserves\_with\_factory ```rust theme={null} pub fn get_reserves_with_factory(e: Env, factory: Address, token_a: Address, token_b: Address) -> Result<(i128, i128), SoroswapLibraryError>; ``` Fetches the reserves for a pair, looking the pair up through the factory, and returns them in the order the caller asked for rather than the pair's internal order. ### get\_reserves\_with\_pair ```rust theme={null} pub fn get_reserves_with_pair(e: Env, pair: Address, token_a: Address, token_b: Address) -> Result<(i128, i128), SoroswapLibraryError>; ``` The same, for a pair address you already have. Saves the factory lookup. ### quote ```rust theme={null} pub fn quote(amount_a: i128, reserve_a: i128, reserve_b: i128) -> Result; ``` Given an amount of one asset and the two reserves, returns the amount of the other asset representing an equivalent value. * Useful for calculating optimal token amounts before calling `deposit`. ### get\_amount\_out ```rust theme={null} pub fn get_amount_out(amount_in: i128, reserve_in: i128, reserve_out: i128) -> Result; ``` Given an input amount and the reserves, returns the maximum output amount of the other asset, fees accounted for. * Used in `get_amounts_out`. ### get\_amount\_in ```rust theme={null} pub fn get_amount_in(amount_out: i128, reserve_in: i128, reserve_out: i128) -> Result; ``` Returns the minimum input amount required to buy a given output amount, fees accounted for. * Used in `get_amounts_in`. ### get\_amounts\_out ```rust theme={null} pub fn get_amounts_out(e: Env, factory: Address, amount_in: i128, path: Vec
) -> Result, SoroswapLibraryError>; ``` Walks a path of token addresses, calling `get_reserves_with_factory` and then `get_amount_out` for each hop, and returns every intermediate amount. * Useful for calculating optimal token amounts before calling `swap`. ### get\_amounts\_in ```rust theme={null} pub fn get_amounts_in(e: Env, factory: Address, amount_out: i128, path: Vec
) -> Result, SoroswapLibraryError>; ``` The same walk in reverse, using `get_amount_in`, returning every preceding minimum input amount. * Useful for calculating optimal token amounts before calling `swap`. This Soroswap library is designed to facilitate efficient and precise token swapping and handling in the Soroswap.Finance ecosystem. It includes a range of functions to support various aspects of token management, from sorting token addresses to calculating reserves and performing chained calculations for different pairs. These functions are crucial for the optimal functioning of Soroswap in the Stellar network. # SoroswapPair Source: https://docs.soroswap.finance/amm/technical-reference/contracts/soroswap-pair SoroswapPair contract interface: deposit, swap, withdraw, reserves, and the standard token functions. The SoroswapPair Contract implements the token interface (hence, it has the same functions that any Stellar Asset Contract) and it also has the specific functions for a liquidity pool to exist.\ \ Check the code here: [https://github.com/soroswap/core/tree/main/contracts/pair](https://github.com/soroswap/core/tree/main/contracts/pair) Here is the contract interface: ```rust theme={null} pub trait SoroswapPairTrait{ /// Initializes a new Soroswap pair by setting token addresses, factory, and initial reserves. /// /// # Arguments /// * `e` - The runtime environment. /// * `factory` - The address of the Soroswap factory contract. /// * `token_0` - The address of the first token in the pair. /// * `token_1` - The address of the second token in the pair. fn initialize(e: Env, factory: Address, token_0: Address, token_1: Address)-> Result<(), SoroswapPairError>; /// Deposits tokens into the Soroswap pair and mints LP tokens in return. /// /// # Arguments /// * `e` - The runtime environment. /// * `to` - The address where the minted LP tokens will be sent. /// /// # Returns /// The amount of minted LP tokens. /// Possible errors: /// - `SoroswapPairError::NotInitialized`: The Soroswap pair has not been initialized. /// - `SoroswapPairError::DepositInsufficientAmountToken0`: Insufficient amount of token 0 sent. /// - `SoroswapPairError::DepositInsufficientAmountToken1`: Insufficient amount of token 1 sent. /// - `SoroswapPairError::DepositInsufficientFirstLiquidity`: Insufficient first liquidity minted. /// - `SoroswapPairError::DepositInsufficientLiquidityMinted`: Insufficient liquidity minted. /// - `SoroswapPairError::UpdateOverflow`: Overflow occurred during update. fn deposit(e:Env, to: Address) -> Result; /// Executes a token swap within the Soroswap pair. /// /// # Arguments /// * `e` - The runtime environment. /// * `amount_0_out` - The desired amount of the first token to receive. /// * `amount_1_out` - The desired amount of the second token to receive. /// * `to` - The address where the swapped tokens will be sent. ////// # Errors /// Returns an error if the swap cannot be executed. Possible errors include: /// - `SoroswapPairError::NotInitialized` /// - `SoroswapPairError::SwapInsufficientOutputAmount` /// - `SoroswapPairError::SwapNegativesOutNotSupported` /// - `SoroswapPairError::SwapInsufficientLiquidity` /// - `SoroswapPairError::SwapInvalidTo` /// - `SoroswapPairError::SwapInsufficientInputAmount` /// - `SoroswapPairError::SwapNegativesInNotSupported` /// - `SoroswapPairError::SwapKConstantNotMet`: If the K constant condition is not met after the swap. fn swap(e: Env, amount_0_out: i128, amount_1_out: i128, to: Address) -> Result<(), SoroswapPairError>; /// Withdraws liquidity from the Soroswap pair, burning LP tokens and returning the corresponding tokens to the user. /// /// # Arguments /// * `e` - The runtime environment. /// * `to` - The address where the withdrawn tokens will be sent. /// /// # Returns /// A tuple containing the amounts of token 0 and token 1 withdrawn from the pair. fn withdraw(e: Env, to: Address) -> Result<(i128, i128), SoroswapPairError>; /// Skims excess tokens from reserves and sends them to the specified address. /// /// # Arguments /// * `e` - The runtime environment. /// * `to` - The address where the excess tokens will be sent. fn skim(e: Env, to: Address); /// Forces reserves to match current balances. /// /// # Arguments /// * `e` - The runtime environment. fn sync(e: Env); /// Returns the address of the first token in the Soroswap pair. fn token_0(e: Env) -> Address; /// Returns the address of the second token in the Soroswap pair. fn token_1(e: Env) -> Address; /// Returns the address of the Soroswap factory contract. fn factory(e: Env) -> Address; /// Returns the value of the last product of reserves (`K`) stored in the contract. /// /// # Arguments /// * `e` - The runtime environment. /// /// # Returns /// The value of the last product of reserves (`K`). fn k_last(e: Env) -> i128; /// Returns the current reserves of both tokens. /// /// # Arguments /// * `e` - The runtime environment. /// /// # Returns /// A tuple containing the reserves of token 0 and token 1. fn get_reserves(e: Env) -> (i128, i128); } #[contract] pub struct SoroswapPairToken; #[contractimpl] impl SoroswapPairToken { pub fn total_supply(e: Env) -> i128; } #[contractimpl] impl token::Interface for SoroswapPairToken { fn allowance(e: Env, from: Address, spender: Address) -> i128; fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32); fn balance(e: Env, id: Address) -> i128; fn transfer(e: Env, from: Address, to: Address, amount: i128); fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128); fn burn(e: Env, from: Address, amount: i128); fn burn_from(e: Env, spender: Address, from: Address, amount: i128); fn decimals(e: Env) -> u32; fn name(e: Env) -> String; fn symbol(e: Env) -> String; } ``` # SoroswapRouter Source: https://docs.soroswap.finance/amm/technical-reference/contracts/soroswap-router SoroswapRouter contract interface and functions for liquidity management, token swapping, and price quoting. The `SoroswapRouter` contract is a smart contract on the Soroban blockchain that provides functions for token swapping and liquidity management in token pairs. It is designed for use in decentralized exchanges (DEX) and other DeFi applications on the Soroban platform.\ \ Check the code here: [https://github.com/soroswap/core/tree/main/contracts/router](https://github.com/soroswap/core/tree/main/contracts/router) Here is the contract interface: ```rust theme={null} pub trait SoroswapRouterTrait { /// Initializes the contract and sets the factory address fn initialize(e: Env, factory: Address) -> Result<(), CombinedRouterError>; /// Adds liquidity to a token pair's pool, creating it if it doesn't exist. Ensures that exactly the desired amounts /// of both tokens are added, subject to minimum requirements. /// /// This function is responsible for transferring tokens from the user to the pool and minting liquidity tokens in return. /// /// # Arguments /// * `e` - The contract environment (`Env`) in which the contract is executing. /// * `token_a` - The address of the first token to add liquidity for. /// * `token_b` - The address of the second token to add liquidity for. /// * `amount_a_desired` - The desired amount of the first token to add. /// * `amount_b_desired` - The desired amount of the second token to add. /// * `amount_a_min` - The minimum required amount of the first token to add. /// * `amount_b_min` - The minimum required amount of the second token to add. /// * `to` - The address where the liquidity tokens will be minted and sent. /// * `deadline` - The deadline for executing the operation. /// /// # Returns /// A tuple containing the actual amounts of token A and B added to the pool, as well as the amount of liquidity tokens minted. fn add_liquidity( e: Env, token_a: Address, token_b: Address, amount_a_desired: i128, amount_b_desired: i128, amount_a_min: i128, amount_b_min: i128, to: Address, deadline: u64, ) -> Result<(i128, i128, i128), CombinedRouterError>; /// Removes liquidity from a token pair's pool. /// /// This function facilitates the removal of liquidity from a Soroswap Liquidity Pool by burning a specified amount /// of Liquidity Pool tokens (`liquidity`) owned by the caller. In return, it transfers back the corresponding /// amounts of the paired tokens (`token_a` and `token_b`) to the caller's specified address (`to`). /// /// # Arguments /// * `token_a` - The address of the first token in the Liquidity Pool. /// * `token_b` - The address of the second token in the Liquidity Pool. /// * `liquidity` - The desired amount of Liquidity Pool tokens to be burned. /// * `amount_a_min` - The minimum required amount of the first token to receive. /// * `amount_b_min` - The minimum required amount of the second token to receive. /// * `to` - The address where the paired tokens will be sent to, and from where the LP tokens will be taken. /// * `deadline` - The deadline for executing the operation. /// /// # Returns /// A tuple containing the amounts of `token_a` and `token_b` withdrawn from the pool. fn remove_liquidity( e: Env, token_a: Address, token_b: Address, liquidity: i128, amount_a_min: i128, amount_b_min: i128, to: Address, deadline: u64, ) -> Result<(i128, i128), CombinedRouterError>; /// Swaps an exact amount of input tokens for as many output tokens as possible /// along the specified trading route. The route is determined by the `path` vector, /// where the first element is the input token, the last is the output token, /// and any intermediate elements represent pairs to trade through if a direct pair does not exist. /// /// # Arguments /// * `amount_in` - The exact amount of input tokens to be swapped. /// * `amount_out_min` - The minimum required amount of output tokens to receive. /// * `path` - A vector representing the trading route, where the first element is the input token /// and the last is the output token. Intermediate elements represent pairs to trade through. /// * `to` - The address where the output tokens will be sent to. /// * `deadline` - The deadline for executing the operation. /// /// # Returns /// A vector containing the amounts of tokens received at each step of the trading route. fn swap_exact_tokens_for_tokens( e: Env, amount_in: i128, amount_out_min: i128, path: Vec
, to: Address, deadline: u64, ) -> Result, CombinedRouterError>; /// Swaps tokens for an exact amount of output token, following the specified trading route. /// The route is determined by the `path` vector, where the first element is the input token, /// the last is the output token, and any intermediate elements represent pairs to trade through. /// /// # Arguments /// * `amount_out` - The exact amount of output token to be received. /// * `amount_in_max` - The maximum allowed amount of input tokens to be swapped. /// * `path` - A vector representing the trading route, where the first element is the input token /// and the last is the output token. Intermediate elements represent pairs to trade through. /// * `to` - The address where the output tokens will be sent to. /// * `deadline` - The deadline for executing the operation. /// /// # Returns /// A vector containing the amounts of tokens used at each step of the trading route. fn swap_tokens_for_exact_tokens( e: Env, amount_out: i128, amount_in_max: i128, path: Vec
, to: Address, deadline: u64, ) -> Result, CombinedRouterError>; /* *** Read only functions: *** */ /// This function retrieves the factory contract's address associated with the provided environment. /// It also checks if the factory has been initialized and raises an assertion error if not. /// If the factory is not initialized, this code will raise an assertion error with the message "SoroswapRouter: not yet initialized". /// /// # Arguments /// * `e` - The contract environment (`Env`) in which the contract is executing. fn get_factory(e: Env) -> Result; /* LIBRARY FUNCTIONS: */ /// Calculates the deterministic address for a pair without making any external calls. /// check /// /// # Arguments /// /// * `e` - The environment. /// * `token_a` - The address of the first token. /// * `token_b` - The address of the second token. /// /// # Returns /// /// Returns `Result` where `Ok` contains the deterministic address for the pair, and `Err` indicates an error such as identical tokens or an issue with sorting. fn router_pair_for(e: Env, token_a: Address, token_b: Address) -> Result; /// Given some amount of an asset and pair reserves, returns an equivalent amount of the other asset. /// /// # Arguments /// /// * `amount_a` - The amount of the first asset. /// * `reserve_a` - Reserves of the first asset in the pair. /// * `reserve_b` - Reserves of the second asset in the pair. /// /// # Returns /// /// Returns `Result` where `Ok` contains the calculated equivalent amount, and `Err` indicates an error such as insufficient amount or liquidity fn router_quote(amount_a: i128, reserve_a: i128, reserve_b: i128) -> Result; /// Given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset. /// /// # Arguments /// /// * `amount_in` - The input amount of the asset. /// * `reserve_in` - Reserves of the input asset in the pair. /// * `reserve_out` - Reserves of the output asset in the pair. /// /// # Returns /// /// Returns `Result` where `Ok` contains the calculated maximum output amount, and `Err` indicates an error such as insufficient input amount or liquidity. fn router_get_amount_out(amount_in: i128, reserve_in: i128, reserve_out: i128) -> Result; /// Given an output amount of an asset and pair reserves, returns a required input amount of the other asset. /// /// # Arguments /// /// * `amount_out` - The output amount of the asset. /// * `reserve_in` - Reserves of the input asset in the pair. /// * `reserve_out` - Reserves of the output asset in the pair. /// /// # Returns /// /// Returns `Result` where `Ok` contains the required input amount, and `Err` indicates an error such as insufficient output amount or liquidity. fn router_get_amount_in(amount_out: i128, reserve_in: i128, reserve_out: i128) -> Result; /// Performs chained get_amount_out calculations on any number of pairs. /// /// # Arguments /// /// * `e` - The environment. /// * `amount_in` - The input amount. /// * `path` - Vector of token addresses representing the path. /// /// # Returns /// /// Returns `Result, SoroswapLibraryError>` where `Ok` contains a vector of calculated amounts, and `Err` indicates an error such as an invalid path. fn router_get_amounts_out(e: Env, amount_in: i128, path: Vec
) -> Result, CombinedRouterError>; /// Performs chained get_amount_in calculations on any number of pairs. /// /// # Arguments /// /// * `e` - The environment. /// * `amount_out` - The output amount. /// * `path` - Vector of token addresses representing the path. /// /// # Returns /// /// Returns `Result, SoroswapLibraryError>` where `Ok` contains a vector of calculated amounts, and `Err` indicates an error such as an invalid path. fn router_get_amounts_in(e: Env, amount_out: i128, path: Vec
) -> Result, CombinedRouterError>; } ``` ## Initialization ### `initialize` `initialize(e: Env, factory: Address)` Initializes the router contract by setting the address of the factory contract, which manages pairs of tokens. This function should be called once to set up the router. * `e` (Env): The contract environment. * `factory` (Address): The address of the factory contract. ## Liquidity Management ### `add_liquidity` `add_liquidity(e: Env, token_a: Address, token_b: Address, amount_a_desired: i128, amount_b_desired: i128, amount_a_min: i128, amount_b_min: i128, to: Address, deadline: u64) -> (i128, i128, i128)` Adds liquidity to a token pair's pool, creating it if it doesn't exist. Ensures that exactly the desired amounts of both tokens are added, subject to minimum requirements. This function is responsible for transferring tokens from the user to the pool and minting liquidity tokens in return. * `e` (Env): The contract environment. * `token_a` (Address): The address of the first token. * `token_b` (Address): The address of the second token. * `amount_a_desired` (i128): The desired amount of the first token. * `amount_b_desired` (i128): The desired amount of the second token. * `amount_a_min` (i128): The minimum required amount of the first token. * `amount_b_min` (i128): The minimum required amount of the second token. * `to` (Address): The address where the liquidity tokens will be minted and sent. * `deadline` (u64): The deadline for executing the operation. Returns a tuple containing: * `amount_a` (i128): The actual amount of the first token added to the pool. * `amount_b` (i128): The actual amount of the second token added to the pool. * `liquidity` (i128): The amount of liquidity tokens minted. *** ### `remove_liquidity` `remove_liquidity(e: Env, token_a: Address, token_b: Address, liquidity: i128, amount_a_min: i128, amount_b_min: i128, to: Address, deadline: u64) -> (i128, i128)` Removes liquidity from a pool of token pairs. It transfers liquidity tokens from the user to the pool and returns the corresponding amounts of the two tokens. * `e` (Env): The contract environment. * `token_a` (Address): The address of the first token. * `token_b` (Address): The address of the second token. * `liquidity` (i128): The amount of liquidity tokens to remove. * `amount_a_min` (i128): The minimum required amount of the first token. * `amount_b_min` (i128): The minimum required amount of the second token. * `to` (Address): The address where the removed tokens will be sent. * `deadline` (u64): The deadline for executing the operation. Returns a tuple containing: * `amount_a` (i128): The actual amount of the first token received. * `amount_b` (i128): The actual amount of the second token received. ## Token Swapping ### `swap_exact_tokens_for_tokens` `swap_exact_tokens_for_tokens(e: Env, amount_in: i128, amount_out_min: i128, path: Vec
, to: Address, deadline: u64) -> Vec` Swaps an exact amount of input tokens for as many output tokens as possible. The swapping route is determined by the provided path, where the first element is the input token and the last is the output token. * `e` (Env): The contract environment. * `amount_in` (i128): The exact amount of input tokens to swap. * `amount_out_min` (i128): The minimum required amount of output tokens. * `path` (Vec): An array of token addresses representing the swap route. * `to` (Address): The address where the output tokens will be sent. * `deadline` (u64): The deadline for executing the operation. Returns an array of amounts that represent the actual output tokens received. *** ### `swap_tokens_for_exact_tokens` `swap_tokens_for_exact_tokens(e: Env, amount_out: i128, amount_in_max: i128, path: Vec
, to: Address, deadline: u64) -> Vec` Swaps tokens for an exact amount of output tokens. The swapping route is determined by the provided path, where the first element is the input token and the last is the output token. * `e` (Env): The contract environment. * `amount_out` (i128): The exact amount of output tokens to receive. * `amount_in_max` (i128): The maximum allowed amount of input tokens. * `path` (Vec): An array of token addresses representing the swap route. * `to` (Address): The address where the input tokens will be taken from. * `deadline` (u64): The deadline for executing the operation. Returns an array of amounts that represent the actual input tokens used for the swap. ## Library Functions These functions exist in the SoroswapRouter contract, but will use the SoroswapLibrary code: ### `router_quote` `router_quote(amount_a: i128, reserve_a: i128, reserve_b: i128) -> i128` Given some amount of an asset and pair reserves, returns an equivalent amount of the other asset. * `amount_a` (i128): The input amount. * `reserve_a` (i128): The reserve amount of the first asset. * `reserve_b` (i128): The reserve amount of the second asset. Returns the equivalent amount of the second asset. *** ### `router_get_amount_out` `router_get_amount_out(amount_in: i128, reserve_in: i128, reserve_out: i128) -> i128` Given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset. * `amount_in` (i128): The input amount. * `reserve_in` (i128): The reserve amount of the input asset. * `reserve_out` (i128): The reserve amount of the output asset. Returns the maximum output amount of the output asset. *** ### `router_get_amount_in` `router_get_amount_in(amount_out: i128, reserve_in: i128, reserve_out: i128) -> i128` Given an output amount of an asset and pair reserves, returns a required input amount of the other asset. * `amount_out` (i128): The output amount. * `reserve_in` (i128): The reserve amount of the input asset. * `reserve_out` (i128): The reserve amount of the output asset. Returns the required input amount of the input asset. ### `router_get_amounts_out` ```rust theme={null} fn router_get_amounts_out(e: Env, amount_in: i128, path: Vec
) -> Vec ``` Performs chained `getAmountOut` calculations on any number of pairs. Given an input amount, it calculates the expected output amounts through the specified token path. ### `router_get_amounts_in` ```rust theme={null} fn router_get_amounts_in(e: Env, amount_out: i128, path: Vec
) -> Vec ``` Performs chained `getAmountIn` calculations on any number of pairs. Given an output amount, it calculates the required input amounts through the specified token path. These library functions are used in the router to facilitate various operations, such as estimating amounts, performing swaps, and managing liquidity. They play a crucial role in ensuring accurate and efficient token exchange within the Soroswap ecosystem. # Deploy Soroswap Yourself Source: https://docs.soroswap.finance/amm/technical-reference/deploy-yourself/index Running the Soroswap contracts yourself: the quickstart from soroswap/core, and where the deploy scripts put their output. You can deploy the whole protocol yourself, on a local standalone network or on testnet. The deploy tooling lives in [soroswap/core](https://github.com/soroswap/core) and is the source of truth; the summary below mirrors that repository's README. > [!WARNING] > > This page used to carry a long CLI walkthrough written against the old `soroban` CLI, > Futurenet and the `soroban-preview` Docker images. All three are gone: the CLI is now > `stellar`, and the deploy path is the scripted one below. The walkthrough was removed > rather than left to mislead. Follow the repository README for anything not covered here. ## Prerequisites `jq`, `docker`, `docker-compose`, `node`, `yarn`. ## Set up ```bash theme={null} git clone https://github.com/soroswap/core.git cd core cp .env.example .env ``` Fill in `SOROSWAP_ADMIN_SECRET_KEY`, `TEST_TOKENS_ADMIN_SECRET_KEY`, `TESTING_ACCOUNT_SECRET_KEY` and `MAINNET_RPC_URL`. These are the keys the deploy runs as. Start the network container in one terminal: ```bash theme={null} bash scripts/quickstart.sh standalone # or testnet ``` And the workspace container in another: ```bash theme={null} bash scripts/run.sh yarn cd /workspace/contracts make build ``` ## Deploy From the workspace container: ```bash theme={null} yarn build yarn deploy ``` That one command installs the Pair, Factory and Router WASM, then deploys and initializes the Factory and the Router. On any network other than mainnet it also seeds the network so there is something to trade against: eight test tokens, liquidity across three paths, four Stellar test tokens, and eight random tokens. Native XLM is wrapped if it is not already. It writes three files: * `.soroban/tokens.json` * `.soroban/.contracts.json` * `.soroban/random_tokens.json` ## Serve the addresses locally ```bash theme={null} bash scripts/serve_with_docker.sh ``` | Endpoint | Serves | | - | - | | `http://localhost:8010/api/tokens` | The token list | | `http://localhost:8010/api/random_tokens` | The random tokens | | `http://localhost:8010/api//factory` | The factory address | | `http://localhost:8010/api//router` | The router address | These mirror the shape of the public API, so a frontend pointed at localhost behaves the same way it does against production. ## The deployed contracts If you only want to talk to the contracts that are already live rather than run your own, you do not need any of the above. The mainnet addresses are on [Deployed Addresses](/amm/technical-reference/deployed-addresses), and the current values are always in [`public/mainnet.contracts.json`](https://github.com/soroswap/core/blob/main/public/mainnet.contracts.json). # Deployed Addresses Source: https://docs.soroswap.finance/amm/technical-reference/deployed-addresses Mainnet contract addresses for SoroswapFactory, SoroswapRouter and Aggregator v3, plus where to find Testnet addresses. ## Mainnet Addresses Soroswap.Finance is live on **Mainnet** * Deployer Address: [`GAYPUMZFDKUEUJ4LPTHVXVG2GD5B6AV5GGLYDMSZXCSI4QILQKSY25JI`](https://stellar.expert/explorer/public/account/GAYPUMZFDKUEUJ4LPTHVXVG2GD5B6AV5GGLYDMSZXCSI4QILQKSY25JI) * SoroswapFactory: `CA4HEQTL2WPEUYKYKCDOHCDNIV4QHNJ7EL4J4NQ6VADP7SYHVRYZ7AW2` * [See in Stellar.Expert](https://stellar.expert/explorer/public/contract/CA4HEQTL2WPEUYKYKCDOHCDNIV4QHNJ7EL4J4NQ6VADP7SYHVRYZ7AW2) * [Se in StellarChain](https://stellarchain.io/contracts/3872426bd59e4a61585086e3886d457903b53f22f89e361ea806ffcb07ac719f) * [See in SorobanExp](https://www.sorobanexp.com/blockchain/ct/contract/CAG5LRYQ5JVEUI5TEID72EYOVX44TTUJT5BQR2J6J77FH65PCCFAJDDH) * SoroswapRouter: `CAG5LRYQ5JVEUI5TEID72EYOVX44TTUJT5BQR2J6J77FH65PCCFAJDDH` * [See in Stellar.Expert](https://stellar.expert/explorer/public/contract/CAG5LRYQ5JVEUI5TEID72EYOVX44TTUJT5BQR2J6J77FH65PCCFAJDDH) * [See in StellarChain](https://stellarchain.io/contracts/0dd5c710ea6a4a23b32207fd130eadf9c9ce899f4308e93e4ffe53fbaf108a04) * [See in SorobanExp](https://www.sorobanexp.com/blockchain/ct/contract/CAG5LRYQ5JVEUI5TEID72EYOVX44TTUJT5BQR2J6J77FH65PCCFAJDDH) These addreesses can also be found in [https://github.com/soroswap/core/blob/main/public/mainnet.contracts.json](https://github.com/soroswap/core/blob/main/public/mainnet.contracts.json) ## Aggregator (Mainnet) * Aggregator v3: `CARVQXFP4JF5ELLXUMQ6DALR346YVGBMQOHB4ENA7SSVXAYABXLBDDC4`, deployed 2026-09-14 * [See in Stellar.Expert](https://stellar.expert/explorer/public/contract/CARVQXFP4JF5ELLXUMQ6DALR346YVGBMQOHB4ENA7SSVXAYABXLBDDC4) * Reference: [Aggregator v3](/aggregator/technical-reference/contracts/aggregator-v3) * Fee treasury: `CCIGFRCEKN33YOGN37URMPPCWLJ43U2BXNHCLKH7LKUXJJAUTJMFVASK` * Deprecated aggregator: `CAYP3UWLJM7ZPTUKL6R6BFGTRWLZ46LRKOXTERI2K6BIJAWGYY62TXTO`. The API stopped using it on 2026-09-24. Do not integrate new work against it. The aggregator has no testnet deployment with live venues. ## Testnet Addresses Check the current Testnet addresses in [https://github.com/soroswap/core/blob/main/public/testnet.contracts.json](https://github.com/soroswap/core/blob/main/public/testnet.contracts.json) # Design Decisions Source: https://docs.soroswap.finance/amm/technical-reference/design-decisions Notable Soroswap protocol design decisions: how pairs receive tokens, and minimum liquidity burning. The following sections describe some of the notable design decisions made in the Soroswap Protocol. These are safe to skip unless you're interested in gaining a deep technical understanding of how the protocol works under the hood, or writing smart contract integrations! ## Sending Tokens Typically, smart contracts which need tokens to perform some functionality require would-be interactors to first make an approval on the token contract, then call a function that in turn calls transferFrom on the token contract. This is *not* how V2 pairs accept tokens. Instead, pairs check their token balances at the *end* of every interaction. Then, at the beginning of the *next* interaction, current balances are differenced against the stored values to determine the amount of tokens that were sent by the current interactor. See the [whitepaper](https://uniswap.org/whitepaper.pdf) for a justification of why this is the case, but the takeaway is that **tokens must be transferred to the pair before calling any token-requiring method** (the one exception to this rule is [Flash Swaps](/concepts/flash-swaps#flash-swaps)). The router fully supports interacting with any WETH pair via ETH. ## Minimum Liquidity To ameliorate rounding errors and increase the theoretical minimum tick size for liquidity provision, pairs burn the first [MINIMUM\_LIQUIDITY](/amm/technical-reference/design-decisions#minimum-liquidity) pool tokens. For the vast majority of pairs, this will represent a trivial value. The burning happens automatically during the first liquidity provision, after which point the [totalSupply](/amm/technical-reference/design-decisions) is forevermore bounded. # Error Codes Source: https://docs.soroswap.finance/amm/technical-reference/error-codes Error codes returned by the SoroswapPair, SoroswapFactory, and SoroswapRouter contracts. Error Codes in the Soroswap AMM Smart Contractws are organized by Contrract **SoroswapPair** returns errors in **100s** **SoroswapFactory** returns errors in **200s** **SorowsapRouter** returns errors in **500s** ```rust theme={null} pub enum SoroswapPairError { /// SoroswapPair: already initialized InitializeAlreadyInitialized = 101, /// SoroswapPair: not yet initialized NotInitialized = 102, /// SoroswapPair: token_0 must be less than token_1 while initializing InitializeTokenOrderInvalid = 103, /// SoroswapPair: insufficient amount of token 0 sent while doing deposit DepositInsufficientAmountToken0 = 104, /// SoroswapPair: insufficient amount of token 1 sent while doing deposit DepositInsufficientAmountToken1 = 105, /// SoroswapPair: insufficient first liquidity minted while doing deposit DepositInsufficientFirstLiquidity = 106, /// SoroswapPair: insufficient liquidity minted while doing deposit DepositInsufficientLiquidityMinted = 107, /// SoroswapPair: insufficient output amount while doing deposDepositit SwapInsufficientOutputAmount = 108, /// SoroswapPair: negatives amounts out dont supported while doing swap SwapNegativesOutNotSupported = 109, /// SoroswapPair: insufficient liquidity to do the swap SwapInsufficientLiquidity = 110, /// SoroswapPair: invalid to to do the swap SwapInvalidTo = 111, /// SoroswapPair: insufficient input amount while doing swap SwapInsufficientInputAmount = 112, /// SoroswapPair: negatives amounts in dont supported while doing swap SwapNegativesInNotSupported = 113, /// SoroswapPair: K constant is not met while doing swap SwapKConstantNotMet = 114, /// SoroswapPair: liquidity was not initialized yet while doing withdraw WithdrawLiquidityNotInitialized = 115, /// SoroswapPair: insufficient sent shares while doing withdraw WithdrawInsufficientSentShares = 116, /// SoroswapPair: insufficient liquidity burned while doing withdraw WithdrawInsufficientLiquidityBurned = 117, /// SoroswapPair: OVERFLOW while updating UpdateOverflow = 118, } pub enum FactoryError { /// SoroswapFactory: not yet initialized NotInitialized = 201, /// SoroswapFactory: token_a and token_b have identical addresses CreatePairIdenticalTokens = 202, /// SoroswapFactory: pair already exists between token_a and token_b CreatePairAlreadyExists = 203, /// SoroswapFactory: already initialized InitializeAlreadyInitialized = 204, /// SoroswapFactory: pair does not exist PairDoesNotExist = 205, /// SoroswapFactory: index does not exist IndexDoesNotExist = 206, } pub enum CombinedRouterError { RouterNotInitialized = 501, RouterNegativeNotAllowed = 502, RouterDeadlineExpired = 503, RouterInitializeAlreadyInitialized = 504, RouterInsufficientAAmount = 505, RouterInsufficientBAmount = 506, RouterInsufficientOutputAmount = 507, RouterExcessiveInputAmount = 508, RouterPairDoesNotExist = 509, LibraryInsufficientAmount = 510, LibraryInsufficientLiquidity = 511, LibraryInsufficientInputAmount = 512, LibraryInsufficientOutputAmount = 513, LibraryInvalidPath = 514, LibrarySortIdenticalTokens = 515, } ``` # Technical Reference Source: https://docs.soroswap.finance/amm/technical-reference/index Pair and Factory contracts of the Soroswap protocol, a Rust implementation inspired by UniswapV2. The Soroswap Protocol is inspired in [UniswapV2](https://github.com/Uniswap/v2-core/). Contracts are written in rust using the Soroban SDK, implementing all the functionalities of UniswapV2, with some limitations of rust (see further) The Soroswap.Finance protocol consists in: 1.- A **Pair** contract (liquidity pool) (The **SoroswapPair** contract): They serve as automated market makers (AMM) and keep track of pool token balances. In Soroban, these contracts implements the Stellar token interface 2.- A **Factory** contract (The **SoroswapFactory** contract): Creates one Liquidity Pool Token smart contract per unique token pair. With this approach, any user can start a new liquidity pool pair from the front-end, and hence without knowing how to code, or without needing to manually deploy a smart contract using the soroban-cli software. Check all the code of the Soroswap AMM in [http://github.com/soroswap/core](http://github.com/soroswap/core) # Smart Contract Integration Source: https://docs.soroswap.finance/amm/technical-reference/smart-contract-integration Calling the SoroswapRouter from your own Soroban contract, with the client import and authorization it needs. How do you integrate Soroswap liquidity pools into your own DeFi protocol from a smart contract, rather than from a frontend or the API? You call the [SoroswapRouter](/amm/technical-reference/contracts/soroswap-router) from your contract using a generated client. This is exactly what the aggregator's Soroswap adapter does, and the pattern below is taken from [`contracts/adapters/soroswap/src/protocol_interface.rs`](https://github.com/soroswap/aggregator/blob/main/contracts/adapters/soroswap/src/protocol_interface.rs) in the aggregator repository. ## Import the router client Generate a typed client from the router's WASM with `contractimport!`: ```rust theme={null} use soroban_sdk::{Address, Env, Vec}; soroban_sdk::contractimport!( file = "./soroswap_contracts/soroswap_router.wasm" ); pub type SoroswapRouterClient<'a> = Client<'a>; ``` You need the router WASM in your build. Fetch it from [soroswap/core](https://github.com/soroswap/core) or from the deployed contract. ## Call it ```rust theme={null} pub fn swap_through_soroswap( e: &Env, router: &Address, amount_in: &i128, amount_out_min: &i128, path: &Vec
, to: &Address, deadline: &u64, ) -> Vec { let router_client = SoroswapRouterClient::new(e, router); router_client.swap_exact_tokens_for_tokens( amount_in, amount_out_min, path, to, deadline, ) } ``` The call returns the amount at each hop of `path`, not just the final output amount. ## Authorization The router calls `to.require_auth()` inside the swap. That means the address receiving the output has to authorize the call, so your contract cannot swap on a user's behalf unless that user authorized the invocation, or `to` is your own contract address and your contract authorizes it. Design your flow around one of those two, not around holding user keys. ## Getting the router address Take it from [Deployed Addresses](/amm/technical-reference/deployed-addresses), or read it at runtime from the API: ```bash theme={null} curl https://api.soroswap.finance/api/mainnet/router # {"address":"C..."} ``` Store it as a constant or in contract storage rather than hardcoding it in several places. ## The rest of the interface `swap_tokens_for_exact_tokens`, `add_liquidity` and `remove_liquidity` are called the same way through the same client. Their signatures are on the [SoroswapRouter](/amm/technical-reference/contracts/soroswap-router) page. If you only need a price and not an execution, `router_get_amounts_out` and `router_quote` are read-only and cost you no state. > [!TIP] > > If you are routing across more than just Soroswap, call > [Aggregator v3](/aggregator/technical-reference/contracts/aggregator-v3) instead of the > router. It runs typed routes across Soroswap, Aquarius, SushiSwap V3 and Comet in one call. > See [Cross-Contract Integration](/aggregator/technical-reference/cross-contract-integration). # Using Soroswap with TypeScript Source: https://docs.soroswap.finance/amm/technical-reference/using-soroswap-with-typescript Swapping and managing liquidity from TypeScript with the official @soroswap/sdk, from quote to signed transaction. The supported way to use Soroswap from TypeScript is the official SDK, [`@soroswap/sdk`](https://www.npmjs.com/package/@soroswap/sdk). It wraps the Soroswap API, so you get routing across every supported protocol without assembling Soroban invocations by hand. > [!NOTE] > > If you need to call the contracts directly from another **contract**, that is a different > job: see [Smart Contract Integration](/amm/technical-reference/smart-contract-integration). ## Install ```bash theme={null} pnpm add @soroswap/sdk ``` You need an API key. Register at [api.soroswap.finance/register](https://api.soroswap.finance/register); keys start with `sk_`. > [!WARNING] > > The API key is a secret. Keep it server side. Do not ship it in a browser bundle. ## Initialize ```typescript theme={null} import { SoroswapSDK, SupportedNetworks, SupportedProtocols, TradeType, } from '@soroswap/sdk'; const soroswap = new SoroswapSDK({ apiKey: process.env.SOROSWAP_API_KEY!, defaultNetwork: SupportedNetworks.MAINNET, }); ``` | Option | Meaning | | - | - | | `apiKey` | Your key, starting with `sk_`. Required. | | `baseUrl` | Override the API host. Defaults to `https://api.soroswap.finance`. | | `defaultNetwork` | `SupportedNetworks.MAINNET` or `SupportedNetworks.TESTNET`. | | `timeout` | Request timeout in ms. Defaults to 30000. | Every method takes an optional trailing `network` argument that overrides `defaultNetwork` for that one call. ## The swap flow A swap is four steps: quote, build, sign, send. The SDK does three of them; signing is yours, because the SDK never holds a key. ### 1. Quote ```typescript theme={null} const quote = await soroswap.quote({ assetIn: 'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA', assetOut: 'CDTKPWPLOURQA2SGTKTUQOWRCBZEORB4BWBOMJ3D3ZTQQSGE5F6JBQLV', amount: 10000000n, tradeType: TradeType.EXACT_IN, protocols: [ SupportedProtocols.SOROSWAP, SupportedProtocols.AQUA, SupportedProtocols.SDEX, ], slippageBps: 50, }); ``` `amount` is a **BigInt**, in the asset's smallest unit. Passing a `number` will not work. `tradeType` is `TradeType.EXACT_IN` when you know what you are spending, or `TradeType.EXACT_OUT` when you know what you want to receive. Other fields on the request: `parts`, `maxHops`, `assetList`, `feeBps` and `gaslessTrustline`. `slippageBps` and `feeBps` are basis points, so 50 is 0.5%. To see which protocols a network currently supports: ```typescript theme={null} const protocols = await soroswap.getProtocols(SupportedNetworks.MAINNET); ``` ### 2. Build ```typescript theme={null} const { xdr } = await soroswap.build({ quote, from: userAddress, to: userAddress, // optional, defaults to `from` }); ``` If the quote carried a `feeBps`, `build` also requires `referralId`, the address the fee is paid to. ### 3. Sign The SDK returns an unsigned XDR and stops there. Sign it with whatever the surrounding app already uses: a browser wallet through [`stellar-wallets-kit`](https://github.com/Creit-Tech/Stellar-Wallets-Kit), or a keypair server side. ```typescript theme={null} const signedXdr = await yourSigner.sign(xdr); ``` ### 4. Send ```typescript theme={null} const result = await soroswap.send(signedXdr); ``` ## Pools and prices ```typescript theme={null} // Every pool on the given protocols const pools = await soroswap.getPools( SupportedNetworks.MAINNET, [SupportedProtocols.SOROSWAP, SupportedProtocols.AQUA], ); // One pair const pool = await soroswap.getPoolByTokens( assetA, assetB, SupportedNetworks.MAINNET, [SupportedProtocols.SOROSWAP], ); const price = await soroswap.getPrice([assetA, assetB]); const balances = await soroswap.getBalances(userAddress); ``` ## Liquidity `addLiquidity` and `removeLiquidity` return an XDR the same way `build` does: you sign and send it yourself. ```typescript theme={null} const { xdr } = await soroswap.addLiquidity({ assetA, assetB, amountA: 1000000n, amountB: 2000000n, to: userAddress, slippageBps: '50', }); ``` > [!WARNING] > > Fetch the pool first and derive `amountA` and `amountB` from its current ratio. Amounts > that do not match the pool's proportions fail during simulation. ```typescript theme={null} const positions = await soroswap.getUserPositions(userAddress); ``` ## Finding the router address If you need the deployed router address rather than the SDK: ```bash theme={null} curl https://api.soroswap.finance/api/mainnet/router # {"address":"C..."} ``` The addresses are also listed on [Deployed Addresses](/amm/technical-reference/deployed-addresses). ## Understanding the route A swap walks the `path` array pair by pair, exchanging at each step (`0 <-> 1`, `1 <-> 2`, and so on to `n <-> n+1`) until the route completes. Every extra hop costs fees and slippage, which is why routing quality matters and why the API computes it for you rather than leaving you to guess a path. ## Reference * SDK source and full type definitions: [github.com/soroswap/sdk](https://github.com/soroswap/sdk) * REST reference, if you would rather call the API directly: [API reference](/api-reference) * Getting started with the API: [Soroswap Aggregator API](/) # Soroswap vs UniswapV2 Source: https://docs.soroswap.finance/amm/technical-reference/vs-uniswap-v2/index Feature by feature comparison table between UniswapV2 and Soroswap. > [!NOTE] > > These pages are a design-time comparison written while Soroswap was being ported from UniswapV2, kept for the rationale it records. The Rust snippets and function signatures shown here reflect an early version of the contracts, not what is deployed today. For the current API, see [Contracts](/amm/technical-reference/contracts). Here we can see a comparison of the implementation of UniswapV2 and Soroswap. | **Feature** | **UniswapV2** | **Soroswap** | | - | - | - | | **Algorithm** | Automated Market Maker (AMM) with constant product formula | Optimized routing algorithm for multi-pool efficiency | | **Liquidity Pools** | Direct swaps between two tokens; liquidity in equal value pairs | Supports multi-hop swaps through intermediate pools | | **Smart Contracts** | Two core contracts: factory and router | Advanced contracts with aggregated liquidity and custom routing | | **Routing** | Direct matching within or between pools | Sophisticated routing with multiple paths for optimal rates | | **Efficiency** | Fast execution; may not always provide optimal rates | Improved efficiency with better trading rates through optimization | | **Liquidity Provision** | 50/50 ratio token pairs; straightforward addition/removal | Complex arrangements; support for multiple AMMs and protocols | | **Incentives** | Fees from trades within pools | Aggregated fees and potentially higher returns | | **Security** | Extensive testing and audits; proven track record | Rigorous audits; advanced security measures | | **User Experience** | Simple, user-friendly interface | Enhanced features; advanced routing and multi-chain support | | **Documentation** | Well-established, extensive community support | Comprehensive documentation and support for new features | # SoroswapLibrary Comparison Source: https://docs.soroswap.finance/amm/technical-reference/vs-uniswap-v2/library How UniswapV2Library functions are implemented in Rust for the SoroswapLibrary contract. The `UniswapV2Library` contract is a contract that helps the router, or any other Uniswap Client Contract to interact with the `Pair` contract. It helps sort tokens (remember that for the pair contract it's not the same token0 than token1), get reserves for an specific pair, and calculates the optimal in/out amount someone needs to send to swap an specific quantity of an specific token. In the following we are going to analize each function or characteristic of the `UniswapV2Library` contract, originally written in Solidity for EVM's, and we are going to think on how we could implement those functionalities and functions in rust for Soroban: ## Safe Math: In Solidity, the SafeMath library is used to validate arithmetic operations and prevent integer overflow/underflow. Should such a situation arise, the library throws an exception, which effectively reverts the transaction. In Rust, we can achieve similar functionality by activating the overflow check flag during the compilation process with the following code: ```toml theme={null} [profile.release] overflow-checks = true ``` Additionally, we use an overflow-safe implementation of functions `checked_add`, `checked_mul`, `checked_div`, and `checked_sub`. You can explore these functions and test their functionality in this repository: [Overflow Soroban Repository](https://github.com/esteblock/overflow-soroban/) Also, we have overflow-safe functions `checked_add`, `checked_mul`, `checked_div` and `checked_sub` You can check and test these techniques in the following repository: [Overflow Soroban Repository](https://github.com/esteblock/overflow-soroban/) In conclusion, Soroswap prevents overflows by leveraging these techniques. ## The `sortTokens` function: This function returns sorted token addresses, used to handle return values from pairs sorted in this order. In Solidity the code is like this: ```javascript theme={null} function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES'); (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS'); } ``` As you can see on our SoroswapPair contract, we have imposed that: ```rust theme={null} if token_a >= token_b { panic!("token_a must be less than token_b"); } ``` Hence, we will implement an order of this type. Regarding the zero address, in Soroban there is no such as zero address ([read this discussion](https://discord.com/channels/897514728459468821/1077831317175144528/1077831317175144528)), so the last `require` statement won't be implemented. ## The `pairFor` function This function calculates the CREATE2 address for a pair without making any external calls. This is because UniswapV2Factory uses the CREATE2 opcode by carefully selecting a unique salt value in order to have a deterministic contract address for each (factory, token0, token1) combination (and of course the same pair contract code) The whole idea of this function is to avoid an external call to the Factory contract in order to ask it what the pair address is... In Soroban this external call won't be expensive... and it's not event direct that calculating this address will be cheaper than the external call Can we do the same with Soroban? The answer is **yes!** In our SoroswapFactory contract, when we deploy a new Pair contract, we do it like this: ``` e.deployer() .with_current_contract(token_pair.salt(&e)) // Use the salt as a unique identifier for the new contract instance .deploy(pair_wasm_hash) // Deploy the new contract instance using the given pair_wasm_hash value ``` Here, the `salt` is an unique identifier that will be used to calculate the new smart contract address. In fact, the new smart contract address depends only in the combination of **the deployer address and the salt**. In fact, in the `salt` in the factory is created by a combination of the `token_0` and `token_1` address: ```rust theme={null} impl Pair { pub fn new(a: Address, b: Address) -> Self { if a < b { Pair(a, b) } else { Pair(b, a) } } pub fn salt(&self, e: &Env) -> BytesN<32> { let mut salt = Bytes::new(e); // Append the bytes of token_a and token_b to the salt salt.append(&self.0.clone().to_xdr(e)); // can be simplified to salt.append(&self.clone().to_xdr(e)); but changes the hash salt.append(&self.1.clone().to_xdr(e)); // Hash the salt using SHA256 to generate a new BytesN<32> value e.crypto().sha256(&salt) } pub fn token_a(&self) -> &Address { &self.0 } pub fn token_b(&self) -> &Address { &self.1 } } ``` If you want to be sure about what we say here, please check this playground repo: [https://github.com/paltalabs/deterministic-address-soroban](https://github.com/paltalabs/deterministic-address-soroban) So, in Soroban we can also calculate the pair contract address without doing a cross-contract call to the Factory contract, we just need the salt explined above + the Factory address like this: ```rust theme={null} pub fn calculate_address( env: Env, deployer: Address, salt: BytesN<32>, ) -> Address { let deployer_with_address = env.deployer().with_address(deployer.clone(), salt); // Calculate deterministic address: // This function can be called at anytime, before or after the contract is deployed, because contract addresses are deterministic. // https://docs.rs/soroban-sdk/20.0.0-rc2/soroban_sdk/deploy/struct.DeployerWithAddress.html#method.deployed_address let deterministic_address = deployer_with_address.deployed_address(); deterministic_address } ``` ## The `getReserves` function This function fetches and sorts the reserves for a pair. It's code in Solidity is: ```javascript theme={null} function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) { (address token0,) = sortTokens(tokenA, tokenB); (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } ``` It is a set of simple external and internal function calls, so it will be easy to implement in Soroban ## The `quote` function This function given some amount of an asset and pair reserves, returns an equivalent amount of the other asset It's code in Solidity is: ```javascript theme={null} function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) { require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT'); require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); amountB = amountA.mul(reserveB) / reserveA; } ``` It's a set of restrictions and arithmetics operations, so it will be implemented in Soroban without any problem. ## The `getAmount...` functions There are four important functions in Uniswap's code: `getAmountOut`, `getAmountIn`, `getAmountsOut` and `getAmountsIn` that are similar. These functions are used for making trades and managing liquidity in Uniswap. * \`getAmountOut\`\` calculates how much of one token you can get when you put in another token, considering fees and available reserves. * `getAmountIn` does the opposite, determining how much you need to put in to get a specific amount of another token. * `getAmountsOut` and \`getAmountsIn\`\` help with more complex trades that involve multiple steps, like going from Token A to Token B to Token C. They make sure you get the right amount at each step. These operations involve math and checks. These same operations can be easily recreated in Rust and Soroban. # SoroswapPair Comparison Source: https://docs.soroswap.finance/amm/technical-reference/vs-uniswap-v2/pair Function by function comparison of SoroswapPair with the UniswapV2Pair contract, and what differs. The sources for the following two sections are: * [https://blog.uniswap.org/uniswap-v2](https://blog.uniswap.org/uniswap-v2) * [https://rskswap.com/audit.html](https://rskswap.com/audit.html) The Pair contract for Soroswap, written in Rust, is inspired by the UniswapV2Pair contract, which is written in Solidity. At the time this comparison was written, SoroswapPair was in its first version (0.0.1) and did not yet implement many of the functions, variables, events, and other features present in the UniswapV2Pair contract. The sections below compare that early Soroswap pair contract with the UniswapV2 pair contract, using the latter as a reference point. This lets us discuss why certain features were implemented or left out at the time. For the contract as it stands today, see [Contracts](/amm/technical-reference/contracts). ## Events: Included! The UniswapV2 pair contract has four events: Mint, Burn, Swap and Sync. The corresponding events in the Soroswap pair contract are: deposit, withdraw, swap, and sync. ```javascript theme={null} event Mint(address indexed sender, uint amount0, uint amount1); event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); event Swap( address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to ); event Sync(uint112 reserve0, uint112 reserve1); ``` Since Mint already exists as an event in the SAC token interface, an alternative name is necessary. For context, Ethereum's ERC20 protocol emits a Transfer event when a token is minted. Refer to [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) for details. Mint may not be the most descriptive name for this event, as the arguments are amount0 and amount1. A more fitting name is deposit, which represents the user's deposit of amount0 units of token0 and amount1 units of token1. Further, tracking the minted tokens is unnecessary, as the Mint event (LP units of LP tokens) is already being emitted. As a result, we've chosen to use deposit for this event. Similarly, Burn has been replaced with withdraw. In Soroban, msg.sender is not utilized, so the event implementation becomes: ```rust theme={null} events::withdraw(&e, to, out_a, out_b, to) ``` The Swap event is implemented in Rust in a manner essentially equivalent to UniswapV2: ```rust theme={null} pub(crate) fn swap( e: &Env, sender: Address, amount_0_in: i128, amount_1_in: i128, amount_0_out: i128, amount_1_out: i128, to: Address, ) { let topics = (PAIR, Symbol::new(e, "swap"), sender); e.events().publish(topics, (amount_0_in, amount_1_in, amount_0_out,amount_1_out, to)); } ``` The sync event is used in Rust to update the reserves after each change. ```rust theme={null} pub(crate) fn sync(e: &Env, reserve_0: u64, reserve_1: u64) { let topics = (PAIR, Symbol::new(e, "sync")); e.events().publish(topics, (reserve_0, reserve_1)); } ``` **These features are all implemented in the code!** *** *** ## SafeMath: Included! In Solidity, the SafeMath library is used to validate arithmetic operations and prevent integer overflow and underflow. When such a situation arise, the library throws an exception, which effectively reverts the transaction. In Rust, we can achieve a similar level of protection by enabling the [overflow check](https://doc.rust-lang.org/rustc/codegen-optionsindex.html#overflow-checks) flag during the compilation process with the following code: ``` [profile.release] overflow-checks = true ``` In addition, we have an overflow-safe implementation of functions `checked_add`, `checked_mul`, `checked_div`, and `checked_sub`. You can explore these functions and test their functionality in this repository: [https://github.com/esteblock/overflow-soroban/](https://github.com/esteblock/overflow-soroban/) When it comes to preventing overflow in Soroban, we have the two solutions mentioned above: using the compiler flag or the overflow-safe functions. Yet, as we will see in the oracle section, there are cases where overflow is the intended result. Hence, we will bypass the compiler flag option, choosing instead to use overflow-safe functions for our arithmetic operations. Exceptions will be made only in those unique cases where overflow is desirable. *** About underflow, it is worth noting that since we are using i128, a signed integer type, underflow will not occur as it would simply result in negative numbers. However, to ensure the integrity of our calculations, we've implemented checks where necessary. For instance: ```rust theme={null} fn put_reserve_a(e: &Env, amount: i128) { if amount < 0 { panic!("put_reserve_a: amount cannot be negative") } e.storage().set(&DataKey::Reserve0, &amount) } ``` **Overflow and underflow safety are included in the code!** *** *** ## Reserves Function: included! In UniswapV2, the reserves function returns the reserves of token0 and token1, along with the timestamp of the last block. ```javascript theme={null} function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) { _reserve0 = reserve0; _reserve1 = reserve1; _blockTimestampLast = blockTimestampLast; } ``` Soroswap adopted a similar approach, implementing this functionality in the `get_reserves` function. Note that the SoroswapPair contract as deployed today returns exactly two values from `get_reserves`, the reserve of `token_0` and the reserve of `token_1`. No block timestamp is returned. The snippet below is from the early version. ```rust theme={null} fn get_reserves(e: Env) -> (i128, i128, i128) { (get_reserve_a(&e), get_reserve_b(&e), get_block_timestamp_last(&e)) } fn get_reserve_0(e: &Env) -> i128 { e.storage().get_unchecked(&DataKey::Reserve0).unwrap() } fn get_reserve_1(e: &Env) -> i128 { e.storage().get_unchecked(&DataKey::Reserve1).unwrap() } ``` That early version also carried a `get_block_timestamp_last` function, which returned the timestamp of the last block, defaulting to 0 if it did not exist. ```rust theme={null} fn get_block_timestamp_last(e: &Env) -> u64 { if let Some(block_timestamp_last) = e.storage().get(&DataKey::BlockTimestampLast) { block_timestamp_last.unwrap() } else { 0 } } ``` **This functionality is integrated directly into the Soroswap codebase!** *** *** ## Name of Pairs: included! In UniswapV2, the name and symbol of the token pairs are designated as follows: ```javascript theme={null} string public constant name = 'Uniswap V2'; string public constant symbol = 'UNI-V2'; ``` Soroswap has similarly implemented the assignment of names and symbols for token pairs: ```rust theme={null} Bytes::from_slice(&e, b"Soroswap Pair Token"), Bytes::from_slice(&e, b"SOROSWAP-LP"), ``` **This feature has been seamlessly integrated into the Soroswap codebase!** *** *** ## Mint (Deposit) In UniswapV2, the mint function is invoked when a user adds liquidity to the pool, resulting in the creation of pool tokens. Before calling the swap function, the seller transfers the asset to the core contract. The contract then measures the received asset quantity by comparing the last recorded balance with its current balance. This approach makes the core contract agnostic to how the trader transfers the asset. Instead of transferFrom, a meta transaction or any other future mechanism for authorizing the transfer of ERC-20s can be used. ```javascript theme={null} // this low-level function should be called from a contract which performs important safety checks function mint(address to) external lock returns (uint liquidity) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); uint amount0 = balance0.sub(_reserve0); uint amount1 = balance1.sub(_reserve1); bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee if (_totalSupply == 0) { liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); } require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); _mint(to, liquidity); _update(balance0, balance1, _reserve0, _reserve1); if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date emit Mint(msg.sender, amount0, amount1); } ``` ### Comments for Soroswap implementation: * The equivalent function in Soroswap is named deposit. To avoid confusion with the mint function of the token interface, we have opted to keep deposit as the function name. * This function in UniswapV2 employs a reentrancy guard. Since reentrancy is not currently possible in Soroban, we have not implemented this guard. * In UniswapV2, the router contract sends (with approval) tokens from the user to the Pair contract before executing the mint function. This design isn't necessary in Soroban (read [https://stellar.org/developers-blog/sorobans-technical-design-decisions-learnings-from-ethereum](https://stellar.org/developers-blog/sorobans-technical-design-decisions-learnings-from-ethereum)) because tokens can be sent using from.require\_auth();, which is checked in the token contract itself. * However, we need to consider tokens that do not implement require\_auth. In such cases, we can follow Uniswap's design and implement a `Router` with a `addLiquidity_with_transfer_from` and a standard `addLiquidity` with `require_auth`. * At the time of writing, the objective was simply to implement the UniswapV2 Pair and Factory contracts, so the design was: ```rust theme={null} fn deposit(e: Env, to: Address, desired_a: i128, min_a: i128, desired_b: i128, min_b: i128) { to.require_auth(); ... let amounts = get_deposit_amounts(desired_a, min_a, desired_b, min_b, reserve_0, reserve_1); ... token_a_client.transfer(&to, &e.current_contract_address(), &amounts.0); token_b_client.transfer(&to, &e.current_contract_address(), &amounts.1); ``` That changed in a later iteration. The Periphery contracts were implemented (see [https://github.com/Uniswap/v2-periphery](https://github.com/Uniswap/v2-periphery) for the Uniswap equivalent), SoroswapRouter shipped and is deployed on mainnet, and `deposit` now takes only the `to` address and requires the tokens to be sent to the pair before it is called. * We've implemented bool feeOn = \_mintFee(\_reserve0, \_reserve1);. * As there's no `totalSupply` in the Soroban token interface, we've implemented a `get_total_shares` and a `put_total_shares` function. * UniswapV2Pair compares whether `totalSupply == 0` to send the “first” LP with `sqrt(x*y)` because it mints a `MINIMUM_LIQUIDITY` to the zero address to permanently lock it forever. This ensures there's always some level of liquidity available, preventing scenarios where liquidity providers could fully drain a pool. Uniswap defines the least amount of liquidity as 1e-15 of the total pool shares, which equates to 1000 times the smallest possible unit of pool shares. To illustrate, UniswapV2 LP tokens operate with 18 decimal places, meaning one token unit corresponds to 1e-18. However, in the Stellar-based soroban-examples liquidity pool contract, such a minimum liquidity requirement is absent. Soroswap emulates this approach by creating 1000 times the smallest possible unit of tokens, equating to 10\*\*3 as the minimum liquidity. In line with the traditional Stellar assets, which have 7 decimals, Soroswap also uses 7 decimals places for this initial version. As such, this minimum liquidity represents 1e-4 of the total pool shares. *** *** ## Swap This function is invoked when a user swaps tokens. Emits Swap and Sync events. ```javascript theme={null} // this low-level function should be called from a contract which performs important safety checks function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY'); uint balance0; uint balance1; { // scope for _token{0,1}, avoids stack too deep errors address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); } _update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } ``` The equivalent function in Soroswap at the time was as follows. The comment inside the snippet anticipated the Router: SoroswapRouter was later written and deployed on mainnet, and the amount calculation and routing logic moved there. The pair `swap` function now takes `amount_0_out`, `amount_1_out` and `to`, matching the UniswapV2 shape shown above. ```rust theme={null} fn swap(e: Env, to: Address, buy_0: bool, amount_out: i128, amount_in_max: i128) { to.require_auth(); /* UniswapV2 implements 2 things that Soroswap it's not going to implement for now: 1.- FlashSwaps. Soroban is not allowing reentrancy for the moment. So no data as a parameter. 2.- uint amount0Out as parameter. Soroswap will impleent all the login in the Router contract. All this logic will change in this contract when the Router contract is implemented */ if amount_out <= 0 { panic!("insufficient output amount") } if to == get_token_0(&e) || to == get_token_1(&e) {panic!("invalid to")} let (reserve_0, reserve_1) = (get_reserve_0(&e), get_reserve_1(&e)); let (reserve_in, reserve_out) = if buy_0 { (reserve_1, reserve_0) } else { (reserve_0, reserve_1) }; // First calculate how much needs to be sold to buy amount amount_out from the pool let n = reserve_in.checked_mul(amount_out).unwrap().checked_mul(1000).unwrap(); let d = (reserve_out.checked_sub(amount_out).unwrap()).checked_mul(997).unwrap(); let amount_in = (n.checked_div(d).unwrap()).checked_add(1).unwrap(); if amount_in > amount_in_max {panic!("amount in is over max") } if amount_in <= 0 { panic!("insufficient input amount")} // Transfer the amount_in being sold to the contract let sell_token = if buy_0 { get_token_1(&e) } else { get_token_0(&e) }; let sell_token_client = TokenClient::new(&e, &sell_token); sell_token_client.transfer(&to, &e.current_contract_address(), &amount_in); let (balance_0, balance_1) = (get_balance_0(&e), get_balance_1(&e)); // residue_numerator and residue_denominator are the amount that the invariant considers after // deducting the fee, scaled up by 1000 to avoid fractions let residue_numerator: i128 = 997; let residue_denominator: i128 = 1000; let zero = 0; let new_invariant_factor = |balance: i128, reserve: i128, amount_out: i128| { let delta = balance.checked_sub(reserve).unwrap().checked_sub(amount_out).unwrap(); let adj_delta = if delta > zero { //residue_numerator * delta residue_numerator.checked_mul(delta).unwrap() } else { // residue_denominator * delta residue_denominator.checked_mul(delta).unwrap() }; //residue_denominator * reserve + adj_delta residue_denominator.checked_mul(reserve).unwrap().checked_add(adj_delta).unwrap() }; let (amount_0_in, amount_1_in) = if buy_0 { (0, amount_in) } else { (amount_in, 0) }; let (amount_0_out, amount_1_out) = if buy_0 { (amount_out, 0) } else { (0, amount_out) }; let new_inv_a = new_invariant_factor(balance_0, reserve_0, amount_0_out); let new_inv_b = new_invariant_factor(balance_1, reserve_1, amount_1_out); //let old_inv_a = residue_denominator * reserve_0; let old_inv_a = residue_denominator.checked_mul(reserve_0).unwrap(); //let old_inv_b = residue_denominator * reserve_1; let old_inv_b = residue_denominator.checked_mul(reserve_1).unwrap(); // if new_inv_a * new_inv_b < old_inv_a * old_inv_b { if new_inv_a.checked_mul(new_inv_b).unwrap() < old_inv_a.checked_mul(old_inv_b).unwrap() { panic!("constant product invariant does not hold"); } if buy_0 { transfer_token_0_from_pair(&e, to.clone(), amount_0_out); } else { transfer_token_1_from_pair(&e, to.clone(), amount_1_out); } let new_balance_0 = balance_0.checked_sub(amount_0_out).unwrap(); let new_balance_1 = balance_1.checked_sub(amount_1_out).unwrap(); update(&e, new_balance_0, new_balance_1, reserve_0.try_into().unwrap(), reserve_1.try_into().unwrap()); event::swap(&e, to.clone(), amount_0_in, amount_1_in, amount_0_out, amount_1_out, to); } fn swap ``` *** *** ## Burn (Withdraw) This function is invoked when a user withdraws liquidity from the pool. Emits Burn, Transfer and Sync events. ```javascript theme={null} // this low-level function should be called from a contract which performs important safety checks function burn(address to) external lock returns (uint amount0, uint amount1) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings address _token0 = token0; // gas savings address _token1 = token1; // gas savings uint balance0 = IERC20(_token0).balanceOf(address(this)); uint balance1 = IERC20(_token1).balanceOf(address(this)); uint liquidity = balanceOf[address(this)]; bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); _burn(address(this), liquidity); _safeTransfer(_token0, to, amount0); _safeTransfer(_token1, to, amount1); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); _update(balance0, balance1, _reserve0, _reserve1); if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date emit Burn(msg.sender, amount0, amount1, to); } ``` The equivalent function in Soroswap at the time was as follows. The comments inside the snippet point at work that has since been done: SoroswapRouter shipped, the minimum-amount checks live there, and the pair `withdraw` function now takes only the `to` address and returns the two withdrawn amounts. ```rust theme={null} fn withdraw(e: Env, to: Address, share_amount: i128, min_a: i128, min_b: i128) -> (i128, i128) { to.require_auth(); // We get the original reserves before the action: let (mut reserve_0, mut reserve_1) = (get_reserve_0(&e), get_reserve_1(&e)); /* For now we are sending the pair token to the contract here. This will change with a Router contract that will send the tokens to us. */ Token::transfer(e.clone(), to.clone(), e.current_contract_address(), share_amount); // address _token0 = token0; // gas savings // address _token1 = token1; // gas savings // uint balance0 = IERC20(_token0).balanceOf(address(this)); // uint balance1 = IERC20(_token1).balanceOf(address(this)); // uint liquidity = balanceOf[address(this)]; let (mut balance_0, mut balance_1) = (get_balance_0(&e), get_balance_1(&e)); let user_sent_shares = get_balance_shares(&e).checked_sub(MINIMUM_LIQUIDITY).unwrap(); // bool feeOn = _mintFee(_reserve0, _reserve1); let fee_on: bool = mint_fee(&e, reserve_0, reserve_1); // uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee let total_shares = get_total_shares(&e); // Now calculate the withdraw amounts let out_0 = (balance_0.checked_mul(user_sent_shares).unwrap()).checked_div(total_shares).unwrap(); let out_1 = (balance_1.checked_mul(user_sent_shares).unwrap()).checked_div(total_shares).unwrap(); if out_0 <= 0 || out_1 <= 0 { panic!("insufficient amount_0 or amount_1"); } // TODO: In the next iteration this should be in the Router contract if out_0 < min_a || out_1 < min_b { panic!("min not satisfied"); } // _burn(address(this), liquidity); burn_shares(&e, user_sent_shares); transfer_token_0_from_pair(&e, to.clone(), out_0.clone()); transfer_token_1_from_pair(&e, to.clone(), out_1.clone()); (balance_0, balance_1) = (get_balance_0(&e), get_balance_1(&e)); // _update(balance0, balance1, _reserve0, _reserve1); update(&e, balance_0, balance_1, reserve_0.try_into().unwrap(), reserve_1.try_into().unwrap()); // Update reserve_0 and reserve_1 after being updated in update() function: (reserve_0, reserve_1) = (get_reserve_0(&e), get_reserve_1(&e)); // if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date if fee_on { put_klast(&e, reserve_0.checked_mul(reserve_1).unwrap()); } event::withdraw(&e, to.clone(), user_sent_shares, out_0, out_1, to); (out_0, out_1) } ``` ## Reentrancy Guards: Currently not implemented In UniswapV2, a reentrancy guard is employed to prevent recursive calls. Here is the corresponding code snippet: ```javascript theme={null} uint private unlocked = 1; modifier lock() { require(unlocked == 1, 'UniswapV2: LOCKED'); unlocked = 0; _; unlocked = 1; } ``` For now, Soroban does not permit reentrancy. Further information is available at these sources: * [https://github.com/esteblock/reentrancy-soroban](https://github.com/esteblock/reentrancy-soroban) * [https://discord.com/channels/897514728459468821/993874836336152576](https://discord.com/channels/897514728459468821/993874836336152576) We plan to revisit this aspect if the allowance of reentrancy is considered in the future. **Current Status: Not implemented** *** *** ## Protocol Fee Mechanism: Mint Fee Implemented! UniswapV2 incorporates a protocol fee of 0.05%, which can be toggled on or off. When activated, this fee is routed to an address, `feeTo`, specified in the factory contract. Initially, `feeTo` isn't set, and hence, no fees are collected. There is a designated address, `feeToSetter`, with the power to invoke the `setFeeTo` function on the UniswapV2 factory contract, altering the `feeTo` value. `feeToSetter` can also change its address via the `setFeeToSetter` function. ```javascript theme={null} uint public constant MINIMUM_LIQUIDITY = 10**3; uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) { address feeTo = IUniswapV2Factory(factory).feeTo(); feeOn = feeTo != address(0); uint _kLast = kLast; // gas savings if (feeOn) { if (_kLast != 0) { uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1)); uint rootKLast = Math.sqrt(_kLast); if (rootK > rootKLast) { uint numerator = totalSupply.mul(rootK.sub(rootKLast)); uint denominator = rootK.mul(5).add(rootKLast); uint liquidity = numerator / denominator; if (liquidity > 0) _mint(feeTo, liquidity); } } } else if (_kLast != 0) { kLast = 0; } } ``` The Soroswap equivalent to the above code is: ```rust theme={null} fn mint_fee(e: &Env, reserve_0: i128, reserve_1: i128) -> bool{ let factory = get_factory(&e); let factory_client = FactoryClient::new(&e, &factory); // address feeTo = IUniswapV2Factory(factory).feeTo(); // feeOn = feeTo != address(0); let fee_on = factory_client.fees_enabled(); let klast = get_klast(&e); if fee_on{ let fee_to: Address = factory_client.fee_to(); if klast != 0 { let root_k = (reserve_0.checked_mul(reserve_1).unwrap()).sqrt(); let root_klast = (klast).sqrt(); if root_k > root_klast{ // uint numerator = totalSupply.mul(rootK.sub(rootKLast)); let total_shares = get_total_shares(&e); let numerator = total_shares.checked_mul(root_k.checked_sub(root_klast).unwrap()).unwrap(); // uint denominator = rootK.mul(5).add(rootKLast); let denominator = root_k.checked_mul(5_i128).unwrap().checked_add(root_klast).unwrap(); // uint liquidity = numerator / denominator; let liquidity_pool_shares_fees = numerator.checked_div(denominator).unwrap(); // if (liquidity > 0) _mint(feeTo, liquidity); if liquidity_pool_shares_fees > 0 { mint_shares(&e, fee_to, liquidity_pool_shares_fees); } } } } else if klast != 0{ put_klast(&e, 0); } fee_on } ``` In this code, we have utilized the `checked_add`, `checked_sub`, `checked_mult` and `checked_div` functions to prevent potential overflows. **This functionality has been successfully integrated into the code!** *** *** ## Oracles: The marginal price of a token pair is calculated by dividing the reserve of one token by the reserve of the other token. Since arbitrageurs will trade against the pair contract to make profits, the marginal price of the pair contract will tend to follow the market price, so maybe we can use the marginal price as an oracle for the market price. However, this is not enough to reliably use this price as an on-chain oracle. An attacker could manipulate the price at an specific moment. If the attacker can get a dApp to check the oracle at the precise instant when the price has been manipulated, then they can cause significant harm to the system. UniswapV1 was vulnerable to this attack, as we can see [here](https://samczsun.com/taking-undercollateralized-loans-for-fun-and-for-profit/). In UniswapV2, the oracle function was modified to prevent this attack, and we will use this oracle function as a reference for our implementation. The solution is to use a cumulative price, which is the sum of the marginal prices over a period of time. The oracle measures and stores the price before the first trade of each block. This price is more difficult to manipulate than the prices in the middle of a block. If the attacker tries to manipulate the price at the start of the block, another arbitrageur can send a transaction to trade back the manipulated price to the real price, so the attacker can't profit from the manipulation. A miner or an attacker that uses enough gas to fill an entire block can try to manipulate the price at the end of the block, but this will be useless if they mine the following block themselves. The miners can't know if they will mine the next block, so they can't profit from this manipulation. So, we know that the price at the start of the block is difficult to manipulate, but we still need to know how to use it as an oracle. ### A note on arithmetic operations and data types: The design of oracle functions requires some consideration of arithmetic operations and data types, given that neither Solidity nor Soroban support floating-point numbers or non-integer number data types natively. Both systems employ custom-made fixed-point number data types, conforming to the [Q format](https://en.wikipedia.org/wiki/Q_\(number_format\)), which are stored as integers. The Q format is a [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) format that specifies the number of bits used for the integer and fractional parts. Both UniswapV2 and Soroswap utilize the **unsigned** variant of the Q format, called UQ, only diverging in the number of bits assigned for the integer and fractional components. A UQn.m number is stored as an unsigned integer of n+m bits, where the first n bits are used for the integer part, and the last m bits are used for the fractional part. For illustration, suppose that we have a UQ4.4 format. It means that we are using 4 bits for the integer part and 4 bits for the fractional part. The whole number is stored as an 8-bit unsigned integer. Some examples of UQ4.4 numbers are: * The number 1.5 in UQ4.4 format is represented as 00011000 in binary. The first four bits (0001) represent the integer part 1, and the last four bits (1000) represent the fractional part 0.5. * The number 3.75 in UQ4.4 format is represented as 00111100 in binary. The first four bits (0011) represent the integer part 3, and the last four bits (1100) represent the fractional part 0.75. To convert the binary number back to a decimal number, we divide the value represented by the fractional part by 2 to the power of m. In the case of UQ4.4 format, we divide by $2^4$ = 16. So, 00011000 would be converted to 1 (from the integer part) plus 8/16 (from the fractional part), or 1.5. In the case of UniswapV2, the [UQ112.112](https://github.com/Uniswap/v2-core/blob/master/contracts/libraries/UQ112x112.sol) is used, in contrast to the UQ64.64 used in Soroswap whose implementation is on [https://github.com/esteblock/fractions-soroban](https://github.com/esteblock/fractions-soroban) ```javascript theme={null} using UQ112x112 for uint224; ... uint112 private reserve0; // uses single storage slot, accessible via getReserves uint112 private reserve1; // uses single storage slot, accessible via getReserves uint public price0CumulativeLast; uint public price1CumulativeLast; uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves ... // update reserves and, on the first call per block, price accumulators function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { // * never overflows, and + overflow is desired price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; } reserve0 = uint112(balance0); reserve1 = uint112(balance1); blockTimestampLast = blockTimestamp; emit Sync(reserve0, reserve1); } ``` Here, many things are happening: * Balances need to fit within the uint112 data type to be encoded into UQ112x112 and undergo division operations. * **For Soroswap:** Balances will need to fit within an u64 type to be encoded into UQ64X64. * Block timestamps are obtained by using the modulo operator to fit them within the uint32 data type. This is done for gas optimization purposes, as described in the whitepaper. Consequently, each set of 224-bit reserves (two reserves as 112-bit) is accompanied by a 32-bit timestamp within a single 256-bit storage slot. * **For Soroswap:** We won't pay much attention for now in gas usage. Can be u32 or u64 * The block timestamp has the potential to overflow, with the next overflow occurring on 02/07/2106. Oracles are required to account for this and ensure proper functionality by checking prices at least once within each interval of 2^ 32 - 1 seconds (approximately 136 years). * **For Soroswap:** Block timestamp can be stored in u64, and will overflow in the year 2554, so we are safe. * The variables price0CumulativeLast and price1CumulativeLast are stored using 224 bits each because they hold a sum and multiplications of UQ112X112.\\ * **For Soroswap:** price0CumulativeLast will need to be u128. * The price itself will not overflow, but the accumulated price over an interval may exceed the 224-bit limit. To address this, an additional 32 bits are allocated in the storage slots for the accumulated prices of the ratios token A/token B and token B/token A. These extra bits handle any overflow resulting from repeated summations of prices. * **For Soroswap:** By default price0CumulativeLast won't be able to overflow in soroban due to the `overflow-checks = true`. Also, there are no bigger integer types in Soroban. See [https://soroban.stellar.org/docs/fundamentals-and-concepts/built-in-types](https://soroban.stellar.org/docs/fundamentals-and-concepts/built-in-types) As per the official Uniswap audit [remarks](https://rskswap.com/audit.html#orgc9b3190), we permit overflow in the case of accumulators. This is primarily a protective strategy; an overflow-induced revert might result in a liveness failure. This means that a revert in the \_update could impede trade operations as well as hinder the entry and exit of liquidity providers (LPs). The necessity for price0CumulativeLast to overflow is emphasized to prevent the protocol from reaching a panic state. The audit illustrates this through a simulation: > Assuming that the ratio of the reserves in a given pair will be the same as the ratio of the dollar prices of one wei of each token, we can solve for a example pair consisting of a 36 decimal token and a 2 decimal token where the unit value of the 2 decimal token is 100 times that of the 36 decimal token: giving ≈ 8 months until overflow! > > Authors of oracles that build upon the price accumulator functionality in the core should therefore take care that the their oracles do not introduce spikes or discontinuities in the reported price at the overflow point, if price accumulator overflow is a realistic possibility for the assets involved. **What this means for Soroswap?**\ This means that Soroswap should allow overflow, hence not using overflow-checks = true, but using `checked_fn` every time the overflow it is NOT DESIRED (all parts except for price0CumulativeLast) * The reserves are stored using 112 bits for each token. **For Soroswap:** We will use u64 **Implemented!** *** *** *** *** ## Skim From UniswapV2 Whitepaper: > To protect against bespoke token implementations that can update the pair contract’s balance, and to more gracefully handle tokens whose total supply can be greater than $2^{112}$, Uniswap v2 has two bail-out functions: sync()and skim(). > > sync() functions as a recovery mechanism in the case that a token asynchronously deflates the balance of a pair. In this case, trades will receive sub-optimal rates, and if no liquidity provider is willing to rectify the situation, the pair is stuck. sync() exists to set the reserves of the contract to the current balances, providing a somewhat graceful recovery from this situation. > > skim() functions as a recovery mechanism in case enough tokens are sent to an pair to overflow the two uint112 storage slots for reserves, which could otherwise cause trades to fail. skim() allows a user to withdraw the difference between the current balance of the pair and 2\*\*2112 − 1 to the caller, if that difference is greater than 0. ```javascript theme={null} function sync() external lock { _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1); } // force balances to match reserves function skim(address to) external lock { address _token0 = token0; // gas savings address _token1 = token1; // gas savings _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0)); _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1)); } ``` Implementation in Soroban: ```rust theme={null} // force balances to match reserves fn skim(e: Env, to: Address) { let (balance_0, balance_1) = (get_balance_0(&e), get_balance_1(&e)); let (reserve_0, reserve_1) = (get_reserve_0(&e), get_reserve_1(&e)); transfer_token_0_from_pair(&e, to.clone(), balance_0.checked_sub(reserve_0).unwrap()); transfer_token_1_from_pair(&e, to, balance_1.checked_sub(reserve_1).unwrap()); } ``` *** *** ## Safe Transfer: not needed The `_safeTransfer` function is specific to Solidity and isn't necessary to be implemented in Soroban. ```javascript theme={null} bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)'))); function _safeTransfer(address token, address to, uint value) private { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED'); } ``` *** *** ## Constructor: not needed In Soroban, the `constructor()` and `initialize()` functions are the same, thus there's no need to separate them. ```javascript theme={null} constructor() public { factory = msg.sender; } // called once by the factory at time of deployment function initialize(address _token0, address _token1) external { require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check token0 = _token0; token1 = _token1; } ``` # SoroswapRouter Comparison Source: https://docs.soroswap.finance/amm/technical-reference/vs-uniswap-v2/router Comparison of SoroswapRouter with UniswapV2Router02: native token handling, deadlines, permit, and transfer fees. ## The Router Contract: Comparison between Soroswap and UniswapV2 Until now, we have developed the Factory and the Pair contract. The factory knows how many pairs it has created and can give us their contract addresses. Also, if we want to trade between token A and token B, and if that pair exists, the Factory will also give us the corresponding pair contract address. But what happens if there are two pairs, A-B and B-C, and a user wants to make a trade between A and C? We could first trade A to B and then B to C! This is why we have the Router Contract. The Router Contract allows swapping of tokens when a direct pair does not exist. It also handles liquidity provision and manages deposit and withdrawal functions for liquidity providers within the Soroswap ecosystem. ## The native token: ETH, WETH, and XLM. In the `UniswapV2Router02`, there is a very important distinction between any ERC20 tokens and the native token ETH (and its wrapped version WETH). In particular, in the contract we find: ```javascript theme={null} address public immutable override WETH; ``` With UniswapV2Router02, if you are using ETH, you'll need to use special functions: * `addLiquidityETH` instead of `addLiquidity` * `removeLiquidityETH` instead of `removeLiquidity` * `removeLiquidityETHWithPermit` instead of `removeLiquidityWithPermit` * Instead of just having `swapExactTokensForTokens` and `swapTokensForExactTokens`; Uniswap Router needs to add 4 extra functions: `swapExactETHForTokens`, `swapTokensForExactETH`, `swapExactTokensForETH`, `swapETHForExactTokens`. * Instead of just having `swapExactTokensForTokensSupportingFeeOnTransferTokens`, Uniswap Router needs to add 2 extra functions: `swapExactETHForTokensSupportingFeeOnTransferTokens` and `swapExactTokensForETHSupportingFeeOnTransferTokens` This is because it's very common that in Blockchains, the native token (in this case ETH in Ethereum) is treated differently, so the smart contract needs to trigger different functions when transferring the native token. **What about in Soroban?** Well, in Soroban, we won't need to have all these extra functions! This is because the native XLM token has its own token contract address and can be treated as any other token complying with the token interface. You can find more information about this in the Soroban Token Playground Chapter 8, 9, 10, and 11. **Conclusion:** none of the ETH-specific variants were needed. SoroswapRouter was written, and it is deployed on mainnet with the following public functions: ``` initialize add_liquidity remove_liquidity swap_exact_tokens_for_tokens swap_tokens_for_exact_tokens get_factory router_pair_for router_quote router_get_amount_out router_get_amount_in router_get_amounts_out router_get_amounts_in ``` None of the following UniswapV2Router02 functions were written: ```javascript theme={null} function addLiquidityETH function removeLiquidityETH function removeLiquidityETHWithPermit function swapExactETHForTokens function swapTokensForExactETH function swapExactTokensForETH function swapETHForExactTokens function swapExactETHForTokensSupportingFeeOnTransferTokens function swapExactTokensForETHSupportingFeeOnTransferTokens ``` Also, because we will treat XLM as any other token, we won't need to define its address. We won't need to write something like: ```javascript theme={null} address public immutable override WETH; ``` Finally, there is also no need for this function: ```javascript theme={null} receive() external payable { assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract } ``` as XLM can only be sent using the native contract. ## SafeMath The Router relies on the same overflow and underflow protections as the rest of Soroswap, described in the [SoroswapLibrary comparison](/amm/technical-reference/vs-uniswap-v2/library). ## Deadlines In Ethereum we can send a transaction with low gas price, and that transaction could be accepted 5, 10 or more minutes later. In order to avoid unwanted transactions after a period of time, Uniswap introduces the `deadline` parameter and the `ensure(deadline)` modifier: ```javascript theme={null} modifier ensure(uint deadline) { require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED'); _; } ``` In Soroban, we can think that having a transaction waiting for minutes is something that can never happen. But because we don't want to say **never**, Soroswap will include this "deadline modifier." Modifiers do not currently exist in the `soroban-sdk`. So we'll need to build a special function using the `env.ledger().timestamp()` object. ## Permit In `UniswapV2Router02` we use the `permit` method of the `UniswapV2ERC20.sol` contracts that is defined [here](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol): ```javascript theme={null} function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external { require(deadline >= block.timestamp, 'UniswapV2: EXPIRED'); bytes32 digest = keccak256( abi.encodePacked( '\x19\x01', DOMAIN_SEPARATOR, keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) ) ); address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE'); _approve(owner, spender, value); } ``` This function implements [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). This avoids the user to use 2 transactions `approve` and \`transfer, which allows users to modify the allowance mapping using a signed message. this function allows a token holder to grant permission for a specific address (spender) to spend their tokens up to a certain amount (value) with a specified expiration time (deadline). The signature ensures the authenticity of the permit, and the function enforces that the permit hasn't expired and that it was signed by the legitimate token owner. In Soroban we don't need to implement something like this, as we use the `require.auth()` method when sending tokens from the user into the smart contract. **Conclusion:** We don't need to impement the `removeLiquidityWithPermit`, neither the `removeLiquidityETHWithPermit` functions (neither `removeLiquidityETHWithPermitSupportingFeeOnTransferTokens`) ## Fees on Transfer In UniswapV2, there is a special function that allows the user to swap tokens that charge a fee when doing a transfer. This tokens are popular in the **deflactionist community**, and indeed it made Uniswap upgrade their original [UniswapV2Router](https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router01.sol) (latter called UniswapV2Router01) contract to a [UniswapV2Router02](https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol) version! You can read all the discussion of the Uniswap design in [this issue](https://github.com/Uniswap/interface/issues/835) In the code we see many functions that have the word `SupportingFeeOnTransferTokens`: * `_swapSupportingFeeOnTransferTokens` * `swapExactTokensForTokensSupportingFeeOnTransferTokens` * `swapExactETHForTokensSupportingFeeOnTransferTokens` * `swapExactTokensForETHSupportingFeeOnTransferTokens` * `removeLiquidityETHSupportingFeeOnTransferTokens` But in fact there is only two functions that do all the work: `_swapSupportingFeeOnTransferTokens` and `removeLiquidityETHSupportingFeeOnTransferTokens` These functions work equal to `_swap` and `removeLiquidityETH` but succeeds for tokens that take a fee on transfer. Because Soroswap wanted to support all types of tokens, the plan at the time was to carry this logic into the SoroswapRouter contract. ## library UniswapV2Library UniswapV2Router uses the UniswapV2Library. Soroswap developed its own equivalent, compared function by function in the [SoroswapLibrary comparison](/amm/technical-reference/vs-uniswap-v2/library). ## Where this landed The Rust was written. SoroswapRouter is deployed on mainnet, and its current interface is documented under [Contracts](/amm/technical-reference/contracts). # Generate API Key Source: https://docs.soroswap.finance/api-reference/account/generate-api-key POST /api-keys/generate Generates a new API key for the authenticated user. Only one API key per user is allowed - generating a new one will revoke the previous one. # Get API Keys Source: https://docs.soroswap.finance/api-reference/account/list-api-keys GET /api-keys Retrieves all API keys for the authenticated user # Login Source: https://docs.soroswap.finance/api-reference/account/login POST /login Authenticates the user and returns access and refresh tokens # Refresh access token Source: https://docs.soroswap.finance/api-reference/account/refresh POST /refresh Generates a new pair of tokens using the refresh token. Pass the refresh token in the Authorization header as Bearer token. # Register a new user Source: https://docs.soroswap.finance/api-reference/account/register POST /register [Creates a new user account](https://api.soroswap.finance/register) in the system. The account will be disabled until it is activated by an administrator. # Revoke API Key Source: https://docs.soroswap.finance/api-reference/account/revoke-api-key POST /api-keys/{keyId}/revoke Revokes an API key for the authenticated user # Add liquidity to a pool Source: https://docs.soroswap.finance/api-reference/liquidity/add POST /liquidity/add Builds an unsigned transaction that deposits two assets into a Soroswap AMM pool, or creates the pool when none exists. Amounts in stroops; amountB must match the pool ratio within slippageBps (default 1%). # Get user positions in pools Source: https://docs.soroswap.finance/api-reference/liquidity/positions GET /liquidity/positions/{address} Soroswap AMM liquidity positions of an address: pool, reserves, share of the pool and the equivalent amount of each token. Returns an empty array when the address has none. # Remove liquidity from a pool Source: https://docs.soroswap.finance/api-reference/liquidity/remove POST /liquidity/remove Builds an unsigned transaction that burns LP shares of a Soroswap AMM pool and returns both assets. Soroswap pools only; 404 Pool not found when the pair does not exist. # Get asset lists Source: https://docs.soroswap.finance/api-reference/market-data/asset-lists GET /asset-list Get all available asset lists or a specific list by name. If no name is provided, returns metadata for all available lists. If a name is provided, returns the complete asset list with all assets. # Get all token balances for an address Source: https://docs.soroswap.finance/api-reference/market-data/balances GET /balances/{walletAddress} Retrieves all token balances from the asset list for a specific address. Supports both wallet addresses (G-prefixed) and smart contracts/accounts (C-prefixed). Example: GET /balances/GALAXYVOIDAOPZTDLHILAJQKCVVFMD4IKLXLSZV5YHO7VY74IWZILUTO?network=mainnet # Pool for two tokens Source: https://docs.soroswap.finance/api-reference/market-data/pool GET /pools/{tokenA}/{tokenB} Retrieves the pool for the given tokens # Pools Source: https://docs.soroswap.finance/api-reference/market-data/pools GET /pools Pools of one venue on a network, with reserves in stroops: GET /pools?network=mainnet&protocol=soroswap, optionally filtered by assetList. # Price information Source: https://docs.soroswap.finance/api-reference/market-data/price GET /price USD price (or another referenceCurrency) of one or more assets by contract address: GET /price?network=mainnet&asset=C...&referenceCurrency=USD. price is a plain number, not stroops. # Get balance for a specific token Source: https://docs.soroswap.finance/api-reference/market-data/token-balance GET /balances/{walletAddress}/{tokenAddress} Retrieves the balance of a specific token for an address. Supports both wallet addresses (G-prefixed) and smart contracts/accounts (C-prefixed). Token address can be: - Contract ID (C-prefixed, 56 characters) - CODE:ISSUER format (e.g., USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN) Example: GET /balances/GALAXYVOIDAOPZTDLHILAJQKCVVFMD4IKLXLSZV5YHO7VY74IWZILUTO/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA?network=mainnet # List your payouts Source: https://docs.soroswap.finance/api-reference/partner/list-payouts GET /partner/statements Every payout for your partner id, newest first — id, status, amount and paidAt only. Call GET /partner/statements/:payoutId for the full statement. # Partner profile Source: https://docs.soroswap.finance/api-reference/partner/partner-profile GET /partner Your partner id, the muxed receiver address you can watch, the rate in force, your payout address and the legacy feeBps/referralId values your integration last sent. # Statement for one payout Source: https://docs.soroswap.finance/api-reference/partner/payout-statement GET /partner/statements/{payoutId} Invoice-grade detail for one of your payouts: every line it covers, the withdraw and payment transaction hashes, and how to verify your gross independently on chain. # Set the USDC payout address Source: https://docs.soroswap.finance/api-reference/partner/set-payout-address PUT /partner/payout-address A funded G account holding a USDC trustline, verified on Horizon before it is stored. # Set the fee your users pay Source: https://docs.soroswap.finance/api-reference/partner/set-rate PUT /partner/rate Inserts a rate effective now. 0 turns attribution off; otherwise 25..300 bps. Requires a profile created by Soroswap. # Build Source: https://docs.soroswap.finance/api-reference/quote/build POST /quote/build Turns a quote into an unsigned XDR for from to sign. Send the quote unchanged and sign right away: a Soroban XDR is valid for 180 seconds. 428 CREATE_TRUSTLINE when a trustline is missing, 409 Quote stale when your fee changed. # Available protocols Source: https://docs.soroswap.finance/api-reference/quote/protocols GET /protocols Returns the list of protocols available to get quotes on the specified network # Quote Source: https://docs.soroswap.finance/api-reference/quote/quote POST /quote Best route across Soroswap, Aquarius, SushiSwap V3, Comet and SDEX. assetIn and assetOut are C... addresses, amount an integer string in stroops, protocols required. Returns amountOut, otherAmountThreshold, routePlan and platform. 400 No path found. # Send Source: https://docs.soroswap.finance/api-reference/quote/send POST /send Submits a signed XDR built by the API to Stellar through Soroban RPC or Horizon and returns txHash and success. 400 Transaction Failed when the network rejects it; quote and build again instead of resending. # Swap (all-in-one) Source: https://docs.soroswap.finance/api-reference/quote/swap POST /swap Quote and build in one call: /quote fields plus from. Returns 200 with an action: SIGN_TRANSACTION, or CREATE_TRUSTLINE / RESTORE_BALANCE / RESTORE_CONTRACT with the XDR that resolves it. inlinePrerequisites returns the swap XDR too. # Soroswap contracts Source: https://docs.soroswap.finance/api-reference/utilities/contracts GET /api/{network}/{contractName} Returns the actual contract address for Soroswap contracts on the specified network. Available contracts: factory, router, aggregator. Available networks: mainnet, testnet # Health Source: https://docs.soroswap.finance/api-reference/utilities/health GET /health Checks if the indexer is up and running, and returns which protocols are available for quote requests on each network (mainnet/testnet) # Soroswap Integration Guide for Beginners Source: https://docs.soroswap.finance/api/beginner-guide Step by step tutorial: a browser swap app that connects the Freighter wallet, quotes and builds with the Soroswap API, signs in Freighter and submits with POST /send, with the full code. ## 📖 What You'll Learn This guide teaches you how to integrate Soroswap API into your application using: * **Freighter Wallet** for secure wallet connection and transaction signing * **Soroswap API** for finding the best swap routes and transaction submission ## 🎯 Prerequisites Before starting, make sure you have: * Basic knowledge of **HTML, CSS, and JavaScript** * **Freighter Wallet** extension installed * An **API key** from Soroswap sign up at [https://api.soroswap.finance/login](https://api.soroswap.finance/login) * A **web browser** with developer tools * **5-10 minutes** of focused time ## 🏗️ Project Structure Overview Our swap application consists of **6 main parts**: ``` 1. HTML Structure → User interface elements with professional styling 2. External Libraries → Freighter API for wallet integration 3. Configuration → Centralized CONFIG object with all settings 4. Application State → Tracks wallet connection and transaction status 5. Utility Functions → Helper functions for UI updates and API calls 6. Core Functions → Connect, Quote, Sign, and Send (4 separate steps) ``` Since this guide is to use the api, we'll focus on the **core functions** that make the swap happen. ## 🔧 Core Functions Explained ### Function 1: Connect to Wallet 🔗 ```javascript theme={null} async function connectStellarWallet() { // Step 1: Check if Freighter wallet is installed const hasFreighter = await freighter.isConnected(); console.log('Freighter connected:', hasFreighter); // Step 2: If not installed, show error message if (!hasFreighter.isConnected) { alert('Please install the Freighter wallet extension first.'); return; // Stop function execution } // Step 3: Request permission to connect console.log('Connecting to Freighter...'); account = await freighter.requestAccess(); account = account.address; // Extract just the address // Step 4: Update the user interface connectButton.disabled = true; // Disable connect button document.getElementById('account').innerText = `Connected account: ${account}`; } ``` **🔍 What this function does:** 1. **Checks** if Freighter wallet is available 2. **Requests** permission to access the wallet 3. **Stores** the wallet address for later use 4. **Updates** the UI to show connection status **🚨 Common issues:** * User doesn't have Freighter installed → Show installation instructions * User denies permission → Ask them to try again ### Function 2: Get Quote and Build Transaction 💱 ```javascript theme={null} async function getQuote() { try { // Make sure wallet is connected if (!appState.connected) { updateStatus('❌ Please connect your wallet first!', 'error'); return; } // PHASE 1: Get Quote updateStatus('🔄 Getting best price from exchanges...', 'info'); const quoteRequest = { assetIn: CONFIG.TOKENS.XLM, // What we're selling assetOut: CONFIG.TOKENS.USDC, // What we want to buy amount: CONFIG.TRADE.AMOUNT, // How much we're selling tradeType: CONFIG.TRADE.TYPE, // Exact input amount protocols: CONFIG.TRADE.PROTOCOLS // Which exchanges to check }; appState.currentQuote = await makeAPIRequest('/quote', quoteRequest); const outputAmount = formatAmount(appState.currentQuote.amountOut, 7); // USDC has 7 decimals updateStatus(`✅ Quote received: ${outputAmount} USDC for 1 XLM
🔄 Building transaction...`, 'info'); // PHASE 2: Build Transaction const buildRequest = { quote: appState.currentQuote, // Use the quote we just got from: appState.walletAddress, // Who's sending to: appState.walletAddress // Who's receiving (same person in a swap) }; const buildResult = await makeAPIRequest('/quote/build', buildRequest); appState.unsignedXdr = buildResult.xdr; // Show the unsigned transaction for educational purposes ELEMENTS.unsignedXdr.value = buildResult.xdr; ELEMENTS.technicalDetails.classList.remove('hidden'); updateStatus(`✅ Transaction built successfully!
⏳ Ready for signing...`, 'info'); updateButtonStates(); } catch (error) { console.error('Quote process failed:', error); updateStatus(`❌ Process failed: ${error.message}`, 'error'); } } ``` **🔍 What this function does:** 1. **Validates** wallet connection first 2. **Requests** the best price from multiple exchanges 3. **Builds** a transaction based on that price 4. **Prepares** the transaction for signing (stores in app state) 5. **Updates** the UI to enable the next step **🚨 Common issues:** * Wallet not connected → Connect wallet first * API key expired → Get a new one from dashboard * Insufficient balance → Make sure wallet has enough XLM ### Function 3: Sign the Transaction ✍️ ```javascript theme={null} async function signTransaction() { try { // Make sure wallet is connected and we have a transaction to sign if (!appState.connected) { updateStatus('❌ Please connect your wallet first!', 'error'); return; } if (!appState.unsignedXdr) { updateStatus('❌ No transaction to sign. Please get a quote first!', 'error'); return; } updateStatus('📝 Please approve the transaction in Freighter...', 'info'); // Sign the transaction using Freighter const signResult = await freighterAPI.signTransaction(appState.unsignedXdr, { network: CONFIG.NETWORK, networkPassphrase: 'Test SDF Network ; September 2015', address: appState.walletAddress }); appState.signedTransaction = signResult.signedTxXdr; // Show the signed transaction for educational purposes ELEMENTS.signedXdr.value = appState.signedTransaction; const outputAmount = formatAmount(appState.currentQuote.amountOut, 6); updateStatus(`✅ Transaction signed successfully!
📋 Ready to swap 1 XLM for ${outputAmount} USDC`, 'success'); updateButtonStates(); } catch (error) { console.error('Transaction signing failed:', error); updateStatus(`❌ Transaction signing failed: ${error.message}`, 'error'); } } ``` **🔍 What this function does:** 1. **Validates** wallet connection and transaction availability 2. **Calls** Freighter to sign the transaction 3. **Stores** the signed transaction in app state 4. **Updates** UI to enable final step **🚨 Common issues:** * User rejects signing → Ask them to try again * Freighter not connected → Check wallet connection * Wrong network → Ensure Freighter is on testnet ### Function 4: Send Transaction to Network 🚀 ```javascript theme={null} async function sendTransaction() { try { // Make sure we have a signed transaction if (!appState.signedTransaction) { updateStatus('❌ No signed transaction available. Please get a quote and sign it first.', 'error'); return; } updateStatus('🚀 Broadcasting transaction to Stellar network...', 'info'); // Send the signed transaction const sendRequest = { xdr: appState.signedTransaction // The signed transaction }; const sendResult = await makeAPIRequest('/send', sendRequest); // Create link to view transaction on Stellar Expert const explorerUrl = `https://stellar.expert/explorer/${CONFIG.NETWORK}/tx/${sendResult.txHash}`; // Show success message ELEMENTS.transactionLink.innerHTML = ` Transaction Hash: ${sendResult.txHash}
🔗 View on Stellar Expert `; ELEMENTS.finalResults.classList.remove('hidden'); updateStatus('🎉 Swap completed successfully! Check the transaction link above.', 'success'); // Reset state for potential next transaction appState.unsignedXdr = null; appState.signedTransaction = null; appState.currentQuote = null; updateButtonStates(); } catch (error) { console.error('Transaction send failed:', error); updateStatus(`❌ Transaction failed: ${error.message}`, 'error'); } } ``` **🔍 What this function does:** 1. **Validates** we have a signed transaction ready 2. **Submits** the transaction to the Stellar network via Soroswap API 3. **Shows** success message with transaction hash and explorer link 4. **Resets** state for potential next transaction ## 🔄 Complete Workflow Summary **4-Step Process:** ``` 🔗 STEP 1: Connect Wallet User clicks "Connect Freighter Wallet" ↓ connectWallet() function App connects to Freighter wallet ✅ ↓ 💱 STEP 2: Get Quote & Build Transaction User clicks "Quote & Build (1 XLM → USDC)" ↓ getQuote() function App gets best price + builds unsigned transaction ✅ ↓ ✍️ STEP 3: Sign Transaction User clicks "Sign Transaction" ↓ signTransaction() function User approves transaction in Freighter wallet ✅ ↓ 🚀 STEP 4: Send to Network User clicks "Send Transaction" ↓ sendTransaction() function App broadcasts to Stellar network ✅ ↓ 🎉 SWAP COMPLETE! ``` ## 🛠️ Key Concepts for Beginners ### What is XDR? **XDR** (External Data Representation) is how Stellar transactions are encoded: * **Unsigned XDR**: Transaction ready to be signed * **Signed XDR**: Transaction with digital signature, ready to submit ### What is a Quote? A **quote** tells you: * How much you'll receive for your trade * Which exchanges offer the best price * What path the trade will take ### What is Signing? **Signing** a transaction means: * Proving you own the wallet * Authorizing the transaction * Making it ready for the network ## 🚨 Security Best Practices ### ✅ DO: * Use testnet for learning and testing * Keep your API keys secure * Verify transaction details before signing * Start with small amounts ### ❌ DON'T: * Put API keys in public code repositories * Sign transactions you don't understand * Use mainnet while learning * Hardcode private keys (never!) ## 🔧 Common Troubleshooting ### Problem: "Freighter not found" **Solution**: Install Freighter wallet extension ### Problem: "403 Forbidden" error **Solution**: Check your API key is correct and not expired ### Problem: "Insufficient liquidity" **Solution**: Try a smaller amount or different token pair ### Problem: Transaction fails **Solution**: Check you have enough XLM for fees ## 🎓 Next Steps Once you understand this basic example: 1. **Customize the UI** with better styling 2. **Add error handling** for better user experience 3. **Support multiple tokens** instead of just XLM→USDC 4. **Add slippage protection** for price changes 5. **Try new methods** from the [API reference](/api-reference) 6. **Experiment using the SDK** ## 📚 Additional Resources * **Soroswap API reference**: [docs.soroswap.finance/api-reference](/api-reference) * **Freighter Docs**: [https://freighter.app/docs](https://freighter.app/docs) * **Stellar Docs**: [https://developers.stellar.org](https://developers.stellar.org) * **Stellar Expert**: [https://stellar.expert](https://stellar.expert) (blockchain explorer) ## 💡 Pro Tips 1. **Use browser dev tools** to debug API calls 2. **Check the console** for error messages 3. **Read API responses** to understand what's happening # Build with AI Source: https://docs.soroswap.finance/api/build-with-ai Connect Claude Code, Cursor, Codex, Windsurf, VS Code or Claude.ai to the Soroswap docs MCP (https://docs.soroswap.finance/mcp, no auth), install the skill, and give your agent the rules for quote, build, sign and send. An agent that integrates Soroswap needs three things: the docs, the rules of the API, and a way to check its work. This page gives it all three. Everything here is free and read-only unless it says otherwise. | Resource | URL | Use it for | | - | - | - | | Docs MCP | `https://docs.soroswap.finance/mcp` | Search and read these docs and the OpenAPI from your editor. No auth | | `llms.txt` | [docs.soroswap.finance/llms.txt](https://docs.soroswap.finance/llms.txt) | One-fetch index of every page, with the API essentials at the top | | `llms-full.txt` | [docs.soroswap.finance/llms-full.txt](https://docs.soroswap.finance/llms-full.txt) | Every page in one file, for a RAG pipeline or a long context | | Markdown of any page | Append `.md` to its URL, for example [/api/quickstart.md](https://docs.soroswap.finance/api/quickstart.md) | Small, exact fetches. `Accept: text/markdown` works too | | OpenAPI | [api.soroswap.finance/api-json](https://api.soroswap.finance/api-json) | Request and response schemas, for code generation | | Skill | [docs.soroswap.finance/skill.md](https://docs.soroswap.finance/skill.md) | What the API does, the intent router, errors and gotchas, in one file | ## Connect the docs MCP The docs MCP is read-only: it searches and reads documentation, it does not call the API. **Claude Code** ```bash theme={null} claude mcp add --transport http soroswap-docs https://docs.soroswap.finance/mcp ``` Check it with `claude mcp list`. To share it with your team, put it in the project's `.mcp.json` instead: ```json theme={null} { "mcpServers": { "soroswap-docs": { "type": "http", "url": "https://docs.soroswap.finance/mcp" } } } ``` **Cursor** Add it to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for every project: ```json theme={null} { "mcpServers": { "soroswap-docs": { "url": "https://docs.soroswap.finance/mcp" } } } ``` Every page of these docs also has a **Connect to Cursor** entry in its page menu. **Windsurf** Open **Windsurf: Configure MCP Servers** from the command palette and add it to `mcp_config.json`. Windsurf uses `serverUrl`, not `url`: ```json theme={null} { "mcpServers": { "soroswap-docs": { "serverUrl": "https://docs.soroswap.finance/mcp" } } } ``` **Codex** ```bash theme={null} codex mcp add soroswap-docs --url https://docs.soroswap.finance/mcp ``` Or in `~/.codex/config.toml`: ```toml theme={null} [mcp_servers.soroswap-docs] url = "https://docs.soroswap.finance/mcp" ``` **VS Code** Add it to `.vscode/mcp.json`: ```json theme={null} { "servers": { "soroswap-docs": { "type": "http", "url": "https://docs.soroswap.finance/mcp" } } } ``` Every page of these docs also has a **Connect to VS Code** entry in its page menu. **Claude.ai** 1. Open [Settings, Connectors](https://claude.ai/settings/connectors) and choose **Add custom connector**. 2. Name it **Soroswap Docs** and set the URL to `https://docs.soroswap.finance/mcp`. 3. In a chat, enable it from the tools menu. Any other MCP client connects to the same URL over Streamable HTTP. ## Install the skill The skill teaches a local agent (Claude Code, Codex, Cursor) when to use which endpoint, the errors to expect and the Stellar gotchas, so it writes a correct integration on the first try. ```bash theme={null} npx skills add https://docs.soroswap.finance ``` It installs [skill.md](https://docs.soroswap.finance/skill.md), the skill these docs publish. It tells the agent that a page it fetched from these docs wins over what the skill remembers. > [!WARNING] > > **Rolling out.** The sections below describe the next tools for agents. They are not live yet: > until each one ships, its command fails. Use the docs MCP and the skill above in the meantime. ### The skills repository A set of focused skills (API integration, calling the aggregator from a contract, AMM liquidity), with runnable examples, installable in one command: ```bash theme={null} npx skills add soroswap/agent-skills ``` ## Let the agent call the API: the trading MCP The trading MCP lets an agent quote, build and submit swaps through the Aggregator API. It runs inside the API at `https://api.soroswap.finance/mcp`. * **It never signs.** Tools that build a transaction return unsigned XDR. Your wallet or your signer signs it, and only then can the agent submit it. * **Your key passes through.** Send the same `Authorization: Bearer sk_...` header the REST API takes. The MCP uses it for that request and does not store it. ```bash theme={null} claude mcp add --transport http soroswap https://api.soroswap.finance/mcp \ --header "Authorization: Bearer $SOROSWAP_API_KEY" ``` In Cursor, VS Code or Windsurf, add the same URL with a `headers` object: `"headers": { "Authorization": "Bearer sk_..." }`. In Codex, set `bearer_token_env_var = "SOROSWAP_API_KEY"` under `[mcp_servers.soroswap]`. ## Let the agent use a terminal: the CLI The `@soroswap/sdk` package ships a `soroswap` command for agents with a shell and for bots: no prompts, JSON output, and signing as a separate step. ```bash theme={null} npx -p @soroswap/sdk soroswap --help ``` ## Rules for your repository's agent Paste this into the `AGENTS.md` or `CLAUDE.md` of the repository where you integrate Soroswap. It keeps the agent on the rules that matter even when it has not read these docs. ```markdown theme={null} ## Soroswap Aggregator API - Docs: https://docs.soroswap.finance (append .md to any page). Index: https://docs.soroswap.finance/llms.txt - Base URL: https://api.soroswap.finance. Always send ?network=mainnet or ?network=testnet; leaving it out means mainnet. - Auth: `Authorization: Bearer $SOROSWAP_API_KEY` (starts with sk_). Server side only, never in frontend code. A missing or bad key returns 403, not 401. - Flow: POST /quote -> POST /quote/build with the quote unchanged and `from` -> user signs -> POST /send (or your own RPC). Always quote before you build, and build right before signing: a Soroban XDR is valid for 180 seconds. - Submit the built XDR as is. Do not re-simulate it or call prepareTransaction on it. - Assets are contract addresses (C...), never CODE:ISSUER. Amounts are integer strings in the asset's smallest unit: XLM, USDC and every classic asset have 7 decimals, so 1 USDC = "10000000". Read decimals from the asset list for Soroban tokens. - `protocols` must list the venues: ["soroswap","aqua","sushi","comet","sdex"]. Leave out sdex when you execute from a contract or a smart wallet. - Branch on the quote's `platform` (aggregator, router, sdex) before executing. - 428 CREATE_TRUSTLINE: sign and submit actionData.xdr, then build again. 409: quote again. 429: wait Retry-After. 503: retry with backoff. Never retry a 400 unchanged. - Test on testnet first (?network=testnet, Friendbot, https://faucet.soroswap.finance). Never submit to mainnet without the user's explicit confirmation. ``` ## Prompts and the calls they should produce A correct agent turns each prompt into the calls on its right. We use this table as our own eval: each prompt runs in a fresh session with only the docs MCP and the skill installed, and passes when the agent makes these calls, in this order, with the right units. | Prompt | Expected sequence | Must not | | - | - | - | | "Swap 100 USDC to XLM for `G...`" | `POST /quote` (`assetIn` USDC `C...`, `amount: "1000000000"`, `EXACT_IN`, all venues) → `POST /quote/build` (`quote`, `from`) → sign → `POST /send` | Send `"100"` or `100.0` as the amount | | "Buy exactly 50 USDC with XLM" | `POST /quote` (`tradeType: "EXACT_OUT"`, `amount: "500000000"`) → `/quote/build` → sign → `/send` | Quote `EXACT_IN` and hope | | "How much XLM do I get for 10 USDC?" | `POST /quote` with no wallet, report `amountOut` ÷ 10^7 | Build or ask for a signature | | "What is XLM worth in USD?" | `GET /price?network=mainnet&asset=CAS3...&referenceCurrency=USD` | Divide `price` by 10^7 | | "Show the balances of `G...`" | `GET /balances/{address}?network=mainnet`, report `amount` / `available` | Read `rawAmount` as whole units | | "Swap XLM to EURC for a wallet that never held EURC" | `/quote` → `/quote/build` returns **428** → sign and submit `actionData.xdr` → `/quote/build` again → sign → `/send` | Treat the 428 as a failure | | "Let a user with no XLM receive USDC" | `/quote` with `gaslessTrustline: "create"` and `sdex` in `protocols` → `/quote/build` with `sponsor` → user and sponsor sign → `/send` | Use it when the user already has the trustline | | "Which venues can you route through?" | `GET /protocols?network=mainnet` | Hard-code a list | | "Charge my users 0.5% on swaps" | `GET /partner` → `PUT /partner/rate` with `{"partnerBps": 50}` (the account must already be a partner, see [Fees](/api/fees)) | Put `feeBps` in the quote body, which is ignored | | "Execute the best route from my Soroban contract" | `/quote` without `sdex` → pass `rawTrade.distribution` to the aggregator's `swap_exact_in` | Call `/quote/build` with a `C...` address | | "Test a swap on testnet" | Friendbot → `/quote?network=testnet` with `protocols: ["soroswap"]` and testnet addresses → `/quote/build` → sign → `/send?network=testnet` | Reuse mainnet contract addresses | | "Add liquidity to the XLM/USDC pool" | `GET /pools/{tokenA}/{tokenB}?network=mainnet&protocol=soroswap` for the ratio → `POST /liquidity/add` → sign → `/send` | Pick `amountB` off the pool ratio | ## When an agent gets something wrong Most first-try failures come from units, asset addresses, an expired build or a missing trustline. [Stellar gotchas](/api/stellar-gotchas) lists each one with the exact error, and [Errors](/api/errors) says which ones to retry. If these docs led your agent astray, tell us from the docs MCP (it has a feedback tool) or on [Discord](https://discord.gg/A2rQ3cm9XG). # Errors Source: https://docs.soroswap.finance/api/errors Every Soroswap Aggregator API error by status and message, whether to retry it, and what to do: 400 bad input, 403 missing key (never 401), 409 stale quote, 428 missing trustline, 429 rate limit, 503 routing. Errors come back as JSON with an HTTP status and a message. Quote and transaction errors also carry `title`, `detail` and `extra` with the specifics. Successful `POST` calls return **201**, not 200: treat any 2xx as success. **Retryable** means you can send the same request again, after a backoff, and expect it to succeed. **No** means the same request will fail again: change what the "What to do" column says first. ## Swap endpoints `/quote`, `/quote/build`, `/swap` and `/send`. | Status | Message | When | Retryable | What to do | | - | - | - | - | - | | **400** | `No path found` | No route exists between the two assets for that amount, with the venues in `protocols` | No | Add venues to `protocols`, try a smaller amount, or check that both assets have liquidity | | **400** | `Invalid Stellar address` | `assetIn` or `assetOut` is not a contract address, for example `CODE:ISSUER` | No | Send contract addresses (`C...`); see [Stellar gotchas](/api/stellar-gotchas#assets-are-contract-addresses) | | **400** | `amount must be a positive integer number of stroops` | `amount` is zero or negative | No | Send the amount in the asset's smallest unit, as a positive integer string | | **400** | `Amount too small: the fee leaves nothing to swap` | The fee takes the whole `EXACT_IN` amount | No | Send a larger amount | | **400** | `Each value in protocols must be a supported protocol` | `protocols` is missing or has an unknown value | No | Send values from `GET /protocols?network=...` | | **400** | `Unsupported network: ...` | `?network=` is not `mainnet` or `testnet` | No | Send `?network=mainnet` or `?network=testnet` | | **400** | `Slippage tolerance must be between 0 and 10000 bps` | `slippageBps` out of range or not a whole number | No | Send a whole number of basis points: `50` is 0.5% | | **400** | `sdex is not available for contract (C) addresses; include at least one Soroban protocol` | `from` or `to` is a contract and `sdex` was the only venue | No | Add a Soroban venue to `protocols`; see [Routing through SDEX](/api/sdex) | | **400** | `Soroban routes deliver to the signing account only: to must equal from (or be omitted)` | `/quote/build` on a Soroban route with a different `to` | No | Leave `to` out, or send the output to the other account in a separate payment | | **400** | `invalid version byte. expected 48, got 16` | `from` on `/quote/build` is a contract (`C...`) | No | Smart wallets execute from the contract path; see [Stellar gotchas](/api/stellar-gotchas#smart-wallets-and-passkeys) | | **400** | `Gasless trustline is only supported with SDEX` | `gaslessTrustline` without `sdex` in `protocols` | No | Add `sdex`, or drop `gaslessTrustline`; see [Gasless Trustline](/api/gasless-trustline) | | **400** | `sponsor is required when gaslessTrustline is ...` | A gasless trustline build or swap without `sponsor` | No | Send the sponsor's `G...` address | | **400** | `Simulation Failed` | The transaction fails when simulated. `errorCode` is the contract error, for example `10` (`TokenError.InsufficientBalance`) | No | Read `errorCode` and `extra`, fix the account state (balance, reserve) and build again | | **400** | `Invalid contract address, not a soroswap contract` | `/send` got a Soroban transaction that does not call a venue the API routes through | No | Submit it through your own Soroban RPC, or send only XDRs built by the API | | **400** | `Transaction Failed` | `/send` submitted the transaction and the network rejected it, for example `tx_too_late` | No | Read `detail` and `extra`; quote and build again before you resubmit. Never resend the same XDR | | **403** | `Forbidden resource` | The API key is missing, malformed or revoked, or the route needs a role your key does not have | No | Send `Authorization: Bearer sk_...` with an active key. The API answers 403, never 401 | | **409** | `Quote stale` | Your partner fee changed between `/quote` and `/quote/build`, so the quote's fee no longer matches | No | Quote again and build from the new quote | | **428** | `CREATE_TRUSTLINE` | The receiving account has no trustline for an asset of the route | After the prerequisite | Sign and submit `actionData.xdr`, then build again, or use [Gasless Trustline](/api/gasless-trustline) | | **429** | `Rate limit exceeded` | You are over your plan's request rate | Yes | Wait `Retry-After` seconds (also `retryAfter` in the body); see [Plans and rate limits](/api/plans) | | **500** | `Internal server error` | Seen when `amount` has a decimal point, for example `"10.5"` | No | Send an integer string. If your input was valid, report it with the request body | | **503** | `Routing unavailable` | The routing service could not answer. Also returned when `assetIn` or `assetOut` is a `G...` address | Yes, if your addresses are `C...` | Retry with backoff. If it persists for one pair only, check the asset addresses | `POST /swap` behaves differently from `/quote/build` on a missing trustline: it returns 200 with an `action` field (`CREATE_TRUSTLINE`, `RESTORE_BALANCE`, `RESTORE_CONTRACT`) instead of an error, so a single call can carry the prerequisite. Sign and submit the `xdr` of that action, then call `/swap` again. ## Account endpoints `/register`, `/login`, `/refresh` and `/api-keys`. | Status | Message | When | Retryable | What to do | | - | - | - | - | - | | **409** | `User already exists` | `POST /register` with an email that has an account | No | [Log in](https://api.soroswap.finance/login) instead | | **403** | `Your account is pending activation. Please contact support.` | `POST /login` before the account is enabled | No | Wait for activation or write to [dev@paltalabs.io](mailto:dev@paltalabs.io) | | **403** | `Access Denied` | Wrong email or password, or an invalid refresh token | No | Check the credentials, or log in again for a new refresh token | ## Retry policy in one line Retry **429** after `Retry-After` and **503** with exponential backoff. Resolve **428** and build again. Quote again on **409** and on any build older than its [time bound](/api/stellar-gotchas#a-built-transaction-expires). Never retry a **400**, **403** or **500** unchanged. # Fees and revenue share Source: https://docs.soroswap.finance/api/fees One fee per aggregated swap, taken from the input token: the 10 bps protocol fee without a partner, or your partner fee of 25 to 300 bps set on your account with PUT /partner/rate, shared with Soroswap and settled monthly in USDC. Soroswap charges one fee per aggregated swap, taken from the input token before routing. Who sets it and who keeps it depends on whether the swap carries a partner. ## The two modes | Mode | Who sets the rate | Rate today | Who keeps it | | - | - | - | - | | **No partner** | Soroswap | 10 bps (`protocol_bps`) | Soroswap | | **Partner** | The integrator, within a range | 25 to 300 bps through the API (from `min_partner_bps`) | Shared between the integrator and Soroswap | A swap pays **either** the protocol fee **or** the partner fee, never both. When you pass a partner, the protocol fee is replaced by your fee, and Soroswap's share comes out of it. The floor on the partner fee exists so that Soroswap's share stays at or above what it would have earned without a partner. Read it the way other aggregators express it: a 10 bps base plus your fee, shared. At the 25 bps floor with the standard 50/50 share, you keep 12.5 bps and Soroswap keeps 12.5 bps. > [!NOTE] > > Both dials, `protocol_bps` and `min_partner_bps`, are set on the aggregator contract by its > admin and can change. The current values are readable on-chain from the contract's `config()`. > The contract itself accepts a partner fee up to 1,000 bps; the API lets you set 25 to 300. ## Pool fees are separate Each liquidity source charges its own pool fee (for example, 0.3% on Soroswap AMM pools, paid to liquidity providers; other venues have their own). The aggregator fee described here is charged on top of the pool fees of the route and is the only fee Soroswap itself collects on an aggregated swap. The [Fees](/concepts/fees) concept page describes the AMM pool fee. ## Setting your fee **Through the API.** The fee belongs to your API account, not to each request. Once Soroswap activates your account as a partner, every mainnet quote you make carries your fee and reports it in `platformFee`, so you can show it to your user before they sign. The fee is always taken from the input token. ```bash theme={null} # See your partner profile: partner id, current rate and payout address curl https://api.soroswap.finance/partner \ -H "Authorization: Bearer " # Set the fee your users pay: 25 to 300 bps, or 0 to switch the partner fee off curl -X PUT https://api.soroswap.finance/partner/rate \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"partnerBps": 25}' # Set where your share is paid: a funded Stellar account (G...) with a USDC trustline curl -X PUT https://api.soroswap.finance/partner/payout-address \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"address": "G..."}' ``` A new rate applies from the next quote. Do not scale quotes yourself: `amountOut` already has the fee taken out. `feeBps` and `referralId` in a `/quote` body are accepted for backwards compatibility and ignored for pricing. The rate endpoint returns 403 until your account is activated as a partner. To activate it, write to the contact in [Questions](#questions). **Calling the aggregator contract directly.** Pass a `Partner { id, bps }` in `swap_exact_in` or `swap_exact_out`. `id` is the partner identifier Soroswap assigns to you (at most 9 characters); `bps` is your fee. Without a partner struct the swap pays the protocol fee and nothing is attributed to you. Your partner identifier is the `partnerId` that `GET /partner` returns. Ask in your integration channel if you do not have it yet. > [!WARNING] > > **Exact-out swaps.** The fee is always taken in the input token. On `swap_exact_out` the > contract charges it on the input the routes actually consumed and pays it from whatever is > left of `max_in`, so size `max_in` as the quoted input, plus your slippage tolerance, plus the > fee on that amount. If `max_in` only covers the slippage, a price move that uses the headroom > leaves nothing for the fee and the swap reverts with `InsufficientInput`. ## Settlement Partner fees are collected on-chain in the token of each swap and booked under your partner identifier in the Soroswap fee treasury. At the end of each calendar month Soroswap converts them to USDC at market rate, pays your share to the wallet you designate on Stellar, and sends a statement listing, per token, the fees collected, the conversion rate and time, and the resulting allocation. Everything is verifiable on-chain against your partner identifier. Your statements are also available through the API: `GET /partner/statements` lists your payouts and `GET /partner/statements/{payoutId}` returns one in full. Amounts are in stroops. Positive slippage (output above the quoted amount) is passed to the user today and is not captured as a fee. ## API plans API access is priced by request rate, independently of swap fees. See [Plans and rate limits](/api/plans). ## Questions Write to [dev@paltalabs.io](mailto:dev@paltalabs.io), ask in your integration channel, or see [Business Partnerships](/resources/partnerships/business-partnerships). # Gasless Trustline Source: https://docs.soroswap.finance/api/gasless-trustline Send gaslessTrustline: "create" to /quote and a sponsor to /quote/build: one SDEX transaction opens the user's trustline, swaps, and repays the sponsor's 0.5 XLM reserve from the output. User and sponsor both sign; G... accounts only. The **Gasless Trustline** feature lets your users receive a brand-new asset **without ever needing to understand, fund, or manually pay for a Stellar trustline**. It is one of the most powerful onboarding tools in the Soroswap API, and it is designed to remove the single biggest friction point that new users hit on Stellar. > **TL;DR** — A sponsor account fronts the \~0.5 XLM reserve needed to open the user's trustline, and the user repays it *inside the same swap* using the assets they are already trading. The user does **not** need to hold XLM beforehand, and never has to think about trustlines at all. > [!NOTE] > > New to trustlines? Read the [Trustlines concept page](/concepts/trustlines) first. In short: on Stellar, an account must open a **trustline** — and lock up a small XLM base reserve — before it can hold any non-native asset. ## The problem it solves On Stellar, before an account can receive a token (say USDC), it must: 1. **Understand** what a trustline is. 2. **Own XLM** to pay the base reserve (\~0.5 XLM per trustline) plus the transaction fee. 3. **Submit a separate `changeTrust` transaction** *before* the swap. For a brand-new user who only holds, for example, some bridged USDC and no XLM, this is a dead end: they can't open the trustline needed to receive the very asset they want, because they have no XLM to pay for it. This "empty wallet" problem is a classic onboarding cliff. **Gasless Trustline removes all three barriers.** The user just swaps. The trustline is created, funded, and repaid automatically as part of the transaction they were going to sign anyway. ## Benefits | Benefit | Why it matters | | - | - | | 🚀 **Zero-XLM onboarding** | Users can acquire their first non-native asset even with **no XLM** in their wallet. | | 🧠 **No trustline knowledge required** | The concept is fully abstracted away — your UI never has to explain "base reserves" or "changeTrust". | | 🔗 **One signature, one flow** | The trustline creation and the swap are bundled into a single transaction — no separate pre-step, no second round-trip. | | 💸 **User self-repays** | The sponsor is reimbursed from the swap's proceeds, so sponsoring is (near) cost-neutral — you're lending, not gifting. | | 👀 **Transparent costs** | The quote returns the trustline cost in **both** the input and output asset, so you can show the user exactly what it costs (or hide it entirely). | | 🛡 **Sponsor stays in control** | The sponsor's XLM only covers the *reserve* via `beginSponsoringFutureReserves` / `endSponsoringFutureReserves`; it is repaid within the same atomic transaction. | | ⚛️ **Atomic & safe** | Everything happens in one transaction. If any part fails, the whole thing reverts — the sponsor is never left out of pocket. | ## How it works Gasless Trustline runs on the **Stellar Classic DEX (SDEX)** using path payments, and applies **only to the first swap for a given user** (i.e., when that user does not yet have a trustline for the output asset). ### The role of the sponsor A **sponsor** is a wallet you control (your app's account) that temporarily covers the XLM base reserve required to open the user's trustline. Two Stellar primitives make this safe and atomic: * `beginSponsoringFutureReserves` — the sponsor starts paying reserves on behalf of the user. * `endSponsoringFutureReserves` — the sponsorship window closes. Between those two operations, the transaction opens the trustline, performs the swap, and **repays the sponsor's \~0.5 XLM** out of the assets being swapped — so the sponsor is made whole within the same transaction. ### The operations, step by step When gasless trustline is enabled, `/quote/build` assembles a single SDEX transaction (built on the **sponsor's** account as source) containing all of the operations below. For an **EXACT\_IN** swap: 1. **`beginSponsoringFutureReserves`** — sponsor starts sponsoring the user's future reserves. 2. **`changeTrust`** — the user opens the trustline for the output asset (reserve paid by the sponsor). 3. *(optional)* **Platform-fee payments** — if `feeBps` / `referralId` are set. 4. **`pathPaymentStrictSend`** — the user performs the actual swap (`assetIn` → `assetOut`). 5. **`pathPaymentStrictReceive`** — the user buys **0.5 XLM** using the output asset and sends it to the sponsor, **repaying** the reserve. 6. **`endSponsoringFutureReserves`** — the sponsorship window closes. The **EXACT\_OUT** flow is analogous but reorders the steps: the user first buys the 0.5 XLM for the sponsor (via a `pathPaymentStrictReceive`, or a plain `payment` when the input asset *is* XLM), then opens the trustline, then performs the main swap. ``` ┌──────────────────────── ONE ATOMIC TRANSACTION ────────────────────────┐ │ │ │ beginSponsoringFutureReserves (sponsor covers the reserve) │ │ └─ changeTrust (user opens trustline for assetOut) │ │ └─ pathPayment (the actual swap: assetIn → assetOut) │ │ └─ pathPayment (user buys 0.5 XLM → repays sponsor) │ │ endSponsoringFutureReserves (sponsorship closes) │ │ │ └─────────────────────────────────────────────────────────────────────────┘ Signed by BOTH the user AND the sponsor. ``` > [!WARNING] > > Because the transaction touches both the user's account (trustline + swap) and the sponsor's account (reserve), the resulting XDR **must be signed by both the user and the sponsor**. ## Using it in the API Gasless Trustline touches two endpoints: `/quote` and `/quote/build`. ### Step 1 — Request a gasless quote Add `gaslessTrustline: "create"` to your `/quote` request body. ```bash theme={null} POST https://api.soroswap.finance/quote?network=mainnet Authorization: Bearer sk_your_api_key Content-Type: application/json ``` ```json theme={null} { "assetIn": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", "assetOut": "CDTKPWPLOURQA2SGTKTUQOWRCBZEORB4BWBOMJ3D3ZTQQSGE5F6JBQLV", "amount": "10000000", "tradeType": "EXACT_IN", "slippageBps": "50", "gaslessTrustline": "create" } ``` > [!CAUTION] > > Only set `gaslessTrustline: "create"` when you are **sure the user does not already have a trustline** for the output asset, **and** you have a funded sponsor account. If the user already trusts the asset, use a regular quote instead. The response is a **Gasless Trustline Swap Quote**. It always routes through `"platform": "sdex"` and includes two extra objects: ```json theme={null} { "assetIn": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", "assetOut": "CDTKPWPLOURQA2SGTKTUQOWRCBZEORB4BWBOMJ3D3ZTQQSGE5F6JBQLV", "amountIn": "10000000", "amountOut": "8567253", "otherAmountThreshold": "8467253", "tradeType": "EXACT_IN", "priceImpactPct": "0.50", "platform": "sdex", "gaslessTrustline": "create", "rawTrade": { "source_amount": "10000000", "destination_amount": "9067253", "min_destination_amount": "9000000", "trustlineOperation": { "source_amount": "0.5000000", "max_source_amount": "0.5100000", "destination_asset_type": "native", "destination_amount": "0.5000000", "path": [] }, "netAmount": "8567253", "otherNetAmountThreshold": "8467253" }, "trustlineInfo": { "trustlineCostAssetIn": "0.5000000", "trustlineCostAssetOut": "0.5000000" } } ``` Key fields: | Field | Meaning | | - | - | | `gaslessTrustline` | Confirms this is a gasless trustline quote (`"create"`). | | `amountOut` / `netAmount` | What the user **actually receives after** the trustline cost is repaid. | | `trustlineInfo.trustlineCostAssetIn` | The trustline cost expressed in the **input** asset — display it to the user if you like. | | `trustlineInfo.trustlineCostAssetOut` | The same cost expressed in the **output** asset. | | `rawTrade.trustlineOperation` | The path-payment details used to buy the 0.5 XLM and repay the sponsor. | ### Step 2 — Build the transaction Pass the quote to `/quote/build` along with the user (`from`) and the **`sponsor`** account. ```bash theme={null} POST https://api.soroswap.finance/quote/build?network=mainnet ``` ```json theme={null} { "quote": { /* the gasless quote from Step 1 */ }, "from": "GBQJ4HBCQHJQKSKNT3JRKC5VKA3QJUXBVFWIVSMHKPQSJ7XP2IXTCAAK", "to": "GBQJ4HBCQHJQKSKNT3JRKC5VKA3QJUXBVFWIVSMHKPQSJ7XP2IXTCAAK", "sponsor": "GDFRQKZH2F6QPZDW7YNWCV3H6FGUQZ3XAJS7XEZQVYZSQYB4SVPQXVYB" } ``` The response contains the unsigned XDR: ```json theme={null} { "xdr": "AAAAAgAAAAB8shYDi1JO9x2/3jMeVzK2IGtrsFVWLCL4uHRJf+YPyAAA..." } ``` > [!NOTE] > > **Requirements & limits** > > * `sponsor` is **required** when `gaslessTrustline` is set — the build fails without it. > * The `from` and `to` addresses must currently be **the same** (a different recipient is not yet supported for gasless trustlines). > * Gasless trustline works **only on the Stellar Classic DEX (SDEX)** and applies **only to the user's first swap** for that asset. ### Step 3 — Sign with **both** wallets, then send Because the transaction spans the user and the sponsor, collect **both signatures** before submitting: 1. The **user** signs the XDR (opens the trustline, performs the swap, repays the sponsor). 2. The **sponsor** signs the XDR (authorizes covering the reserve). 3. Submit the fully-signed XDR to `POST /send` (or through your own Soroban RPC / Horizon). ```json theme={null} POST /send?network=mainnet { "xdr": "AAAAAgAAAABiSu3MsKtQvDuQO5i2b..." } ``` ## End-to-end example (JavaScript) ```javascript theme={null} const BASE = 'https://api.soroswap.finance'; const headers = { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }; // 1. Gasless quote — user has no trustline for assetOut yet const quote = await fetch(`${BASE}/quote?network=mainnet`, { method: 'POST', headers, body: JSON.stringify({ assetIn: ASSET_IN, assetOut: ASSET_OUT, amount: '10000000', tradeType: 'EXACT_IN', slippageBps: '50', gaslessTrustline: 'create', }), }).then(r => r.json()); // Optional: show the user what the trustline costs console.log('Trustline cost (in assetOut):', quote.trustlineInfo.trustlineCostAssetOut); console.log('You will receive:', quote.amountOut); // 2. Build — include the sponsor account const { xdr } = await fetch(`${BASE}/quote/build?network=mainnet`, { method: 'POST', headers, body: JSON.stringify({ quote, from: USER_ADDRESS, to: USER_ADDRESS, sponsor: SPONSOR_ADDRESS, }), }).then(r => r.json()); // 3. Sign with BOTH the user and the sponsor const userSignedXdr = await signWithUserWallet(xdr); const fullySignedXdr = await signWithSponsorWallet(userSignedXdr); // 4. Send const result = await fetch(`${BASE}/send?network=mainnet`, { method: 'POST', headers, body: JSON.stringify({ xdr: fullySignedXdr }), }).then(r => r.json()); console.log('Done! tx hash:', result.hash); ``` ## When to use it ✅ **Use gasless trustline when:** * The user is acquiring an asset **for the first time** (no existing trustline). * You want the smoothest possible onboarding, especially for users with **no XLM**. * You operate a **funded sponsor account** to front the reserve. ❌ **Don't use it when:** * The user **already has** a trustline for the output asset (use a regular quote — it will be cheaper and can route across all protocols, not just SDEX). * You need to send the output to a **different recipient** than the swapper (`from` ≠ `to` is not yet supported). * You want routing across Soroswap, Aquarius, SushiSwap V3 or Comet: gasless trustline is **SDEX-only**. ## Related * [Trustlines — concept](/concepts/trustlines) * [Quick Start Guide](/api/quickstart) * [Beginner Tutorial](/api/beginner-guide) * [Routing through SDEX](/api/sdex) * [API Reference](/api-reference/quote/quote) # Going to production Source: https://docs.soroswap.finance/api/going-to-production Mainnet checklist for the Soroswap Aggregator API: key on the server, explicit ?network=mainnet, every venue in protocols, branch on platform, set slippageBps, build right after quoting, retry only 429 and 503. ## Checklist * **Key on the server.** Call the API from your backend. A key in frontend code is a key anyone can use against your limit. * **Mainnet.** Send `?network=mainnet` explicitly. Testnet quotes come only from Soroswap pools and carry no fee, so they do not show production prices. * **Venues.** List every venue you can execute in `protocols`. If you execute from a contract, leave out `sdex` or handle SDEX quotes separately ([Routing through SDEX](/api/sdex)). * **Read `platform`.** Branch on `aggregator`, `router` or `sdex` before you execute a quote. * **Slippage.** Set `slippageBps` for your users. `otherAmountThreshold` is the minimum they accept; on the contract path, `min_out` is your only price guarantee. * **Quote, then build right away.** Prices move. A 409 means quote again. * **Submit the built XDR as is.** `/quote/build` returns a transaction that is already simulated and sized for its route. Sign it and send it, without `prepareTransaction` or another simulation: rebuilding it can make the swap fail on chain. * **Fees.** If you are a partner, check your rate and payout address with `GET /partner` before launch ([Fees and revenue share](/api/fees)). * **Errors and retries.** Handle 400, 409, 428, 429 and 503 as described in [Errors](/api/errors). Retry 429 and 503 with backoff; never retry a 400 unchanged. * **Timeouts.** Put a timeout on every call and show the user a clear state while you wait. * **Rate limit.** Watch `X-RateLimit-Remaining` and size your plan to your peak ([Plans and rate limits](/api/plans)). * **Monitoring.** Track quote failures, build conflicts and transaction success rate on your side. ## Deprecations The pre-v3 aggregator contract (`CAYP3UWLJM7ZPTUKL6R6BFGTRWLZ46LRKOXTERI2K6BIJAWGYY62TXTO`) is deprecated: new integrations use [Aggregator v3](/aggregator/technical-reference/contracts/aggregator-v3). ## Support Developer questions go to [dev@paltalabs.io](mailto:dev@paltalabs.io) or the developer channels on [Discord](https://discord.gg/A2rQ3cm9XG). For a large integration or a commercial agreement, see [Business Partnerships](/resources/partnerships/business-partnerships). # Plans and rate limits Source: https://docs.soroswap.finance/api/plans Free 1 request/s (burst 5), Starter 10 (US$25/month), Professional 50 (US$100), Business 100 (US$350), Enterprise custom. A token bucket per key, X-RateLimit-* headers on every response, and 429 with Retry-After. API access is priced by request rate, independently of swap fees. | Plan | Rate limit | Price | | - | - | - | | Free | 1 request/s | US\$0 | | Starter | 10 requests/s | US\$25 / month | | Professional | 50 requests/s | US\$100 / month | | Business | 100 requests/s | US\$350 / month | | Enterprise | Custom | [Talk to us](/resources/partnerships/business-partnerships) | Enterprise covers request rates above Business, an integration agreement with support terms, and a direct channel with the team. Partners with a signed integration agreement get the Starter plan included. Paid plans are activated on request while self-service billing is not available. Swap fees are described in [Fees and revenue share](/api/fees). ## How the limit works Each key has a token bucket: a short burst above the sustained rate is allowed, then requests refill at the plan's rate. `GET /rate-limits/tiers` returns the burst size and refill rate of every plan, and needs no key. Every response carries your current standing: | Header | Meaning | | - | - | | `X-RateLimit-Limit` | The burst size of your plan | | `X-RateLimit-Remaining` | Requests you can make right now | | `X-RateLimit-Reset` | Seconds until the bucket is full again | | `Retry-After` | Only on a 429: seconds to wait before the next request | A request over the limit returns **429**. Wait the `Retry-After` seconds and retry; see [Errors](/api/errors). # Quickstart Source: https://docs.soroswap.finance/api/quickstart Mainnet in four calls: POST /quote (C... asset addresses, amount as an integer string in stroops), POST /quote/build with the unchanged quote and from, sign, POST /send. Also the API key, testnet, and API-built versus contract execution. ## First, choose how you execute The API always returns the best route. How that route reaches the chain is your choice, and it decides which guide you follow. | | **The API builds the transaction** | **You execute from your own contract** | | - | - | - | | Who it is for | Wallets, apps and backends that sign for a user or a `G...` account | Protocols and routers that already call contracts on-chain, and smart wallets (`C...` accounts) | | What you call | `/quote`, then `/quote/build`, then `/send` (or submit yourself) | `/quote`, then pass `rawTrade.distribution` to the aggregator contract's `swap_exact_in` / `swap_exact_out` | | Venues | All of them, SDEX included | Soroban venues only: leave `sdex` out of `protocols`, or execute SDEX quotes separately ([Routing through SDEX](/api/sdex)) | | Fees | Applied by the API from your account | Pass your partner in the contract call ([Fees and revenue share](/api/fees)) | | Guide | This page | [Execute from your own contract](/aggregator/technical-reference/cross-contract-integration) | The rest of this page follows the first path. ## 1. Get an API key 1. [Register](https://api.soroswap.finance/register) and [log in](https://api.soroswap.finance/login). 2. Generate an API key. It starts with `sk_`. 3. Send it on every request as `Authorization: Bearer sk_...`. A missing, malformed or revoked key returns **403**, not 401. Keep the key on your server: a key in frontend code is a key anyone can use against your rate limit. ## 2. Quote ```bash theme={null} curl -X POST "https://api.soroswap.finance/quote?network=mainnet" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "assetIn": "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA", "assetOut": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", "amount": "100000000", "tradeType": "EXACT_IN", "protocols": ["soroswap", "aqua", "sushi", "comet", "sdex"], "slippageBps": 50 }' ``` * `assetIn` and `assetOut` are contract addresses (`C...`). The two above are XLM and USDC on mainnet. * `amount` is in stroops, the asset's smallest unit (7 decimals for XLM and USDC). * `protocols` lists the venues to consider. `GET /protocols?network=mainnet` returns the accepted values. SDEX is only priced when `sdex` is in the list. * `slippageBps` is optional and defaults to 50 (0.5%). The response carries `amountIn`, `amountOut`, the minimum you will accept (`otherAmountThreshold`), the `routePlan` and a `platform` field that says how the route executes: `aggregator`, `router` or `sdex`. ## 3. Build, sign and send ```javascript theme={null} const API = 'https://api.soroswap.finance' const headers = { Authorization: `Bearer ${process.env.SOROSWAP_API_KEY}`, 'Content-Type': 'application/json', } async function post(path, body) { const res = await fetch(`${API}${path}?network=mainnet`, { method: 'POST', headers, body: JSON.stringify(body), }) if (!res.ok) throw new Error(`${path} ${res.status}: ${await res.text()}`) return res.json() } const quote = await post('/quote', { assetIn: 'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA', // XLM assetOut: 'CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75', // USDC amount: '100000000', // 10 XLM tradeType: 'EXACT_IN', protocols: ['soroswap', 'aqua', 'sushi', 'comet', 'sdex'], }) // Send the quote back unchanged. `from` signs; `to` receives (defaults to `from`). const { xdr } = await post('/quote/build', { quote, from: userAddress, to: userAddress }) const signedXdr = await signWithWallet(xdr) // Freighter, Stellar Wallets Kit, your own signer const result = await post('/send', { xdr: signedXdr }) console.log(result.txHash, result.success) ``` You can also submit the signed transaction through your own Soroban RPC or Horizon instead of `/send`. Build soon after you quote. Prices move, and if your partner fee changed in between, `/quote/build` returns **409**: quote again and build from the new quote. ## Testnet Use the same base URL with `?network=testnet`. Testnet quotes come only from Soroswap pools and carry no fee. Fund an account with Friendbot and mint test tokens at [faucet.soroswap.finance](https://faucet.soroswap.finance). ## Next * [Fees and revenue share](/api/fees): earn on the swaps you route. * [Gasless Trustline](/api/gasless-trustline): onboard users who hold no XLM. * [Errors](/api/errors) and [Going to production](/api/going-to-production). * [API Reference](/api-reference/quote/quote): every endpoint and field. * [Beginner guide](/api/beginner-guide): the same flow step by step, with a Freighter example. # Routing through SDEX Source: https://docs.soroswap.finance/api/sdex SDEX is quoted only when sdex is in protocols. An SDEX quote has platform sdex and decimal amounts in rawTrade, executes as a classic path payment from a G... account, and cannot pay a C... address. The Soroswap API can route a swap through the Stellar classic DEX (SDEX, the order book and classic liquidity pools) as well as through the Soroban venues the [aggregator](/aggregator/supported-amms) executes. SDEX works differently from the other venues in three ways, and an integration has to handle all three. 1. **SDEX is opt-in.** It is quoted only when `"sdex"` is in `protocols`. 2. **An SDEX quote is not an aggregator call.** It comes back with `"platform": "sdex"` and is executed as a classic path payment. 3. **A contract cannot execute it.** A path payment is a classic operation signed by a `G...` account. It cannot run inside a Soroban contract call. ## SDEX is quoted only when you ask for it `/quote` looks at the venues listed in `protocols` and nothing else. If `"sdex"` is not in the list, the API never asks SDEX for a price, and no quote will ever come back with `"platform": "sdex"`. ```json theme={null} { "assetIn": "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA", "assetOut": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", "amount": "149625000", "tradeType": "EXACT_IN", "protocols": ["soroswap", "aqua", "sushi", "comet", "sdex"] } ``` When `"sdex"` is present together with Soroban venues, the API prices both in parallel and returns **one** of them: the one with the higher `amountOut` for `EXACT_IN`, or the lower `amountIn` for `EXACT_OUT`. A single quote never mixes SDEX and Soroban hops. To compare them yourself, send two quotes, one with `["sdex"]` and one without it. `GET /protocols?network=mainnet` returns every value `protocols` accepts. > [!NOTE] > > SDEX trades classic Stellar assets only. A token that exists only as a Soroban contract, with no > classic issuer behind it, has no SDEX market, and a quote with `["sdex"]` alone returns 400 for > it. Keep the Soroban venues in `protocols` so those pairs still route. ## Read `platform` before you execute Every quote carries `platform`, and `rawTrade` has a different shape on each. | `platform` | What `rawTrade` holds | How it is executed | | - | - | - | | `aggregator` | `distribution`: routes and hops with pool ids, in stroops | An [aggregator v3](/aggregator/technical-reference/contracts/aggregator-v3) `swap_exact_in` / `swap_exact_out` call | | `router` | A Soroswap AMM path | A Soroswap router call | | `sdex` | A Horizon path record: `source_amount`, `destination_amount`, `path`, `min_destination_amount` (or `max_source_amount` for `EXACT_OUT`), in decimal units | A classic `path_payment_strict_send` (or `strict_receive`) operation | An SDEX quote looks like this: ```json theme={null} { "amountIn": "149625000", "amountOut": "26834267", "otherAmountThreshold": "26700096", "tradeType": "EXACT_IN", "platform": "sdex", "rawTrade": { "source_asset_type": "native", "source_amount": "14.9475375", "destination_asset_type": "credit_alphanum4", "destination_asset_code": "USDC", "destination_asset_issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", "destination_amount": "2.6834267", "path": [], "min_destination_amount": "2.6700096" }, "routePlan": [ { "swapInfo": { "protocol": "sdex", "path": ["CAS3J7...", "CCW67T..."] }, "percent": "100" } ] } ``` The top-level `amountIn` and `amountOut` are stroops, as on every quote. The amounts inside an SDEX `rawTrade` are decimal strings, the way Horizon returns them. `source_amount` is already net of the fee, which is charged on the input asset. An SDEX `rawTrade` has no `distribution`, so code that expects one will fail on it. ## Executing an SDEX quote Send the quote to `/quote/build` with a `G...` account as `from`. The API returns a classic transaction XDR with the path payment and, on mainnet, one extra `payment` operation carrying the fee. Sign it with `from` and submit it to the Stellar network. ```bash theme={null} curl -X POST "https://api.soroswap.finance/quote/build?network=mainnet" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"quote": , "from": "G...", "to": "G..."}' ``` SDEX swaps pay the same fee as aggregated swaps, partner fee included. See [Fees and revenue share](/api/fees). Building the SDEX transaction through `/quote/build` is what puts the fee operation in it. ## If your integration calls the aggregator from a contract Integrations that take `rawTrade.distribution` from `/quote` and pass it to the aggregator from their own router contract, or that swap for a smart wallet (`C...` address), can only execute `aggregator` quotes. You have two options: * **Leave `"sdex"` out of `protocols`.** Every quote is then a Soroban route your contract can run. You lose the pairs where SDEX gives the better price. * **Ask for `"sdex"` and branch on `platform`.** When the quote is `sdex`, execute it as a separate classic transaction from a `G...` account through `/quote/build`, outside your contract. When it is `aggregator`, keep your current path. `/quote/build` and `/swap` drop `sdex` automatically when `from` or `to` is a contract address, and return 400 if no other venue is left. ## Checklist when you never see SDEX routes * `"sdex"` is in the `protocols` you send to `/quote`. * You read `platform` on the response, not only `rawTrade.distribution`. * Both assets are classic Stellar assets. * The `from` and `to` you pass to `/quote/build` are `G...` accounts. # Stellar gotchas for integrators Source: https://docs.soroswap.finance/api/stellar-gotchas Amounts are integer strings in stroops (7 decimals for XLM, USDC, EURC, AQUA), assets are C... contract addresses, a built Soroban XDR expires in 180 s, and a missing trustline is a 428. The things that trip up a first integration on Stellar, in one place. Each one was checked against the live API on mainnet. ## Amounts are integer strings in the smallest unit Every `amount` you send to the API is an integer number of the asset's smallest unit (stroops for XLM), written as a string: `"100000000"` is 10 XLM. Convert from what the user types with `raw = human × 10^decimals`, using integer or decimal arithmetic, never floats. | Asset | Decimals | 1 unit in the API | Mainnet contract | | - | - | - | - | | XLM | 7 | `"10000000"` | `CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA` | | USDC | 7 | `"10000000"` | `CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75` | | EURC | 7 | `"10000000"` | `CDTKPWPLOURQA2SGTKTUQOWRCBZEORB4BWBOMJ3D3ZTQQSGE5F6JBQLV` | | AQUA | 7 | `"10000000"` | `CAUIKL3IYGMERDRUN6YSCLWVAKIFG5Q4YJHUKM4S4NJZQIA3BAS6OJPK` | Every classic Stellar asset has 7 decimals. Tokens issued directly on Soroban choose their own: the Soroswap token list has some with 18. Read `decimals` from `GET /asset-list?name=SOROSWAP` or from the token contract's `decimals()` instead of assuming 7. * `"0"` and negative amounts return **400** `amount must be a positive integer number of stroops`. * An amount with a decimal point (`"10.5"`) currently returns **500**, not 400. Never send one. * A JSON number is accepted, but a string is the only form that survives values above 2^53 in JavaScript. Not every field is in stroops. Read the unit of each response field before you display it: | Field | Unit | | - | - | | `amountIn`, `amountOut`, `otherAmountThreshold` on any quote | Smallest unit, integer string | | `rawTrade` of an `aggregator` or `router` quote | Smallest unit | | `rawTrade` of an `sdex` quote (`source_amount`, `destination_amount`...) | Decimal units with 7 places, for example `"0.5000000"` | | `GET /balances/{address}`: `amount` and `available` | Decimal units | | `GET /balances/{address}`: `rawAmount` and `availableRaw` | Smallest unit | | `GET /price`: `price` | A number in the reference currency (USD by default) | ## Assets are contract addresses The API identifies every asset, classic or Soroban, by its contract address (`C...`). The `CODE:ISSUER` form and the issuer's `G...` address are not accepted: * `"assetIn": "USDC:GA5Z..."` returns **400** `Invalid Stellar address`. * `"assetIn": "GA5Z..."` (the issuer) returns **503** `Routing unavailable`, which reads like an outage but is a wrong input. Check your addresses before you retry it. A classic asset reaches Soroban through its **Stellar Asset Contract (SAC)**, whose address is derived from the asset and the network. To get it: ```bash theme={null} stellar contract id asset --asset USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN --network mainnet # CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75 ``` ```javascript theme={null} import { Asset, Networks } from '@stellar/stellar-sdk' new Asset('USDC', 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN').contractId(Networks.PUBLIC) Asset.native().contractId(Networks.PUBLIC) // XLM: CAS3J7GY...OWMA ``` The same asset has a different contract address on testnet: XLM is `CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC` there. Never reuse mainnet addresses with `?network=testnet`. Accounts are a separate question. `from` and `to` are the wallets that sign and receive: a classic account (`G...`) or a contract account such as a smart wallet (`C...`). See [Smart wallets](#smart-wallets-and-passkeys) below. ## A missing trustline A classic account cannot receive a classic asset (anything but XLM) until it has a trustline for it. Tokens issued on Soroban need no trustline. When the receiving account lacks one: * `POST /quote/build` returns **428** with `action: "CREATE_TRUSTLINE"`. `actionData.xdr` is a `changeTrust` transaction for `actionData.signer`, with `assetCode` and `assetIssuer`. Sign it, submit it, then call `/quote/build` again with the same quote (quote again if it is no longer fresh). * `POST /swap` returns **200** with the same `action` instead of an error, so one call carries the prerequisite. With `inlinePrerequisites: true` it also returns the swap XDR, with the trustline transaction in a `prerequisites` array to submit first. * On a mixed-venue route the missing trustline can be for an intermediate asset, not the output one. Read `assetCode` from the response instead of assuming it is `assetOut`. * A user with no XLM to pay for the trustline can still receive the asset in one swap through [Gasless Trustline](/api/gasless-trustline), on SDEX routes, with a sponsor account you control. ## The XLM reserve Each account keeps a minimum XLM balance it cannot spend: 0.5 XLM per base reserve, two for the account itself plus one per trustline, offer or other subentry. Opening a trustline locks another 0.5 XLM. A user who swaps "all" their XLM fails: size XLM swaps from what is spendable. `GET /balances/{address}` returns it as `available` / `availableRaw`, already net of the reserve. ## A built transaction expires The XDR from `/quote/build` and `/swap` has a time bound. Past it, the network rejects the transaction with `tx_too_late`. | Route (`platform`) | Valid for | | - | - | | `aggregator`, `router` (Soroban) | 180 seconds from the build | | `sdex` | About an hour | The quote inside it ages faster than that: prices move and the minimum output was set at quote time. If the user has not signed within the window, quote and build again. Never re-sign or resubmit an expired XDR, and never edit its time bounds. ## Simulation: the API has done it A Soroban transaction must be simulated before it is signed, to get its footprint, resource fee and authorization entries. `/quote/build` already did that and sized the result for the route. Sign it and send it as is. Do not run `prepareTransaction` or another simulation on it: simulating again drops the margins the API adds for Aquarius and SushiSwap routes, and the swap can fail on chain. The one exception is a swap XDR that comes back with `simulated: false` from `/swap` with `inlinePrerequisites: true`. It could not be simulated because its prerequisites did not exist yet: submit them first, then simulate and assemble it yourself. A simulation that fails while building returns **400** `Simulation Failed` with a contract `errorCode`, for example `10` (`TokenError.InsufficientBalance`): the account cannot cover the amount. Fix the account state and build again. ## Signing: envelope, not auth entries The XDR the API builds uses the `from` account as transaction source, and its Soroban authorization is source-account credentials. A classic wallet (Freighter, Stellar Wallets Kit, a keypair on your server) signs the transaction envelope once and that covers everything. You do not sign authorization entries separately. The one exception to a single signer is [Gasless Trustline](/api/gasless-trustline), where the sponsor signs too. ## Smart wallets and passkeys A contract account (`C...`), such as a passkey smart wallet, cannot be the source of a transaction, so it cannot use `/quote/build`: a `C...` in `from` returns **400** `invalid version byte. expected 48, got 16`. Smart wallets take the [contract path](/aggregator/technical-reference/cross-contract-integration): 1. Quote with `/quote`, leaving `sdex` out of `protocols` (the classic DEX cannot pay a contract; `/swap` drops it for you and returns 400 if it was the only venue). 2. Build the aggregator `swap_exact_in` / `swap_exact_out` invocation yourself from `rawTrade.distribution`. 3. Simulate it with a fee-paying `G...` account as source, have the smart wallet sign the **authorization entry** for its own address, then assemble and submit. Soroban routes deliver to the signing account only: on `/quote/build`, `to` must equal `from` or be left out. ## Testnet is a different world `?network=testnet` uses the same base URL and key. Testnet quotes come only from Soroswap pools, carry no fee and use testnet contract addresses. Fund an account with Friendbot and get test tokens at [faucet.soroswap.finance](https://faucet.soroswap.finance). Leaving out `?network` quotes **mainnet**, so always send it. ## Responses you might misread * A successful `POST` returns **201**, not 200. Treat any 2xx as success. * A missing or invalid key returns **403** `Forbidden resource`, never 401. See [Errors](/api/errors). # Advanced Topics Source: https://docs.soroswap.finance/concepts/advanced/index In-depth material on Soroswap pricing, returns, security and open research questions. > **[Pricing](/concepts/advanced/pricing)** > > How a pool derives a price, and what moves it. > **[Understanding Returns](/concepts/advanced/understanding-returns)** > > What a liquidity provider actually earns, and what impermanent loss costs. > **[Security](/concepts/advanced/security)** > > The attack surface of an AMM and how Soroswap handles it. > **[Research](/concepts/advanced/research)** > > Open questions and directions the protocol is exploring. # Pricing Source: https://docs.soroswap.finance/concepts/advanced/pricing How pool reserves determine swap prices, and how to price exact input and exact output trades safely. ## How are prices determined? As we learned in [Protocol Overview](/amm), each pair on Uniswap is actually underpinned by a liquidity pool. Liquidity pools are smart contracts that hold balances of two unique tokens and enforces rules around depositing and withdrawing them. The primary rule is the [constant product formula](/amm/glossary#x--y--k). When a token is withdrawn (bought), a proportional amount must be deposited (sold) to maintain the constant. The ratio of tokens in the pool, in combination with the constant product formula, ultimately determine the price that a swap executes at. ## How Uniswap handles prices In Uniswap V1, trades are always executed at the "best possible" price, calculated at execution time. Somewhat confusingly, this calculation is actually accomplished with one of two different formulas, depending on whether the trade specifies an exact *input* or *output* amount. Functionally, the difference between these two functions is miniscule, but the very existence of a difference increases conceptual complexity. Initial attempts to support both functions in V2 proved inelegant, and the decision was made to **not provide any pricing functions in the core**. Instead, pairs directly check whether the invariant was satisfied (accounting for fees) after every trade. This means that rather than relying on a pricing function to *also* enforce the invariant, V2 pairs simply and transparently ensure their own safety, a nice separation of concerns. One downstream benefit is that V2 pairs will more naturally support other flavors of trades which may emerge, (e.g. trading to a specific price at execution time). At a high level, in Uniswap V2, *trades must be priced in the periphery*. The good news is that the [library](/amm/technical-reference/contracts/soroswap-library) provides a variety of functions designed to make this quite simple, and all swapping functions in the [router](/amm/technical-reference/contracts/soroswap-router) are designed with this in mind. ## Pricing Trades When swapping tokens on Uniswap, it's common to want to receive as many output tokens as possible for an *exact input amount*, or to pay as few input tokens as possible for an *exact output amount*. In order to calculate these amounts, a contract must look up the *current reserves* of a pair, in order to understand what the current price is. However, it is *not safe to perform this lookup and rely on the results without access to an external price*. Say a smart contract naively wants to send 10 DAI to the DAI/WETH pair and receive as much WETH as it can get, given the current reserve ratio. If, when called, the naive smart contract simply looks up the current price and executes the trade, it is *vulnerable to front-running and will likely suffer an economic loss*. To see why, consider a malicious actor who sees this transaction before it is confirmed. They could execute a swap which dramatically changes the DAI/WETH price immediately before the naive swap goes through, wait for the naive swap to execute at a bad rate, and then swap to change the price back to what it was before the naive swap. This attack is fairly cheap and low-risk, and can typically be performed for a profit. To prevent these types of attacks, it's vital to submit swaps *that have access to knowledge about the "fair" price their swap should execute at*. In other words, swaps need access to an *oracle*, to be sure that the best execution they can get from Uniswap is close enough to what the oracle considers the "true" price. While this may sound complicated, the oracle can be as simple as an *off-chain observation of the current market price of a pair*. Because of arbitrage, it's typically the case that the ratio of the intra-block reserves of a pair is close to the "true" market price. So, if a user submits a trade with this knowledge in mind, they can ensure that the losses due to front-running are tightly bounded. This is how, for example, the Uniswap frontend ensure trade safety. It calculates the optimal input/output amounts given observed intra-block prices, and uses the router to perform the swap, which guarantees the swap will execute at a rate no less that `x`% worse than the observed intra-block rate, where `x` is a user-specified slippage tolerance (0.5% by default). There are, of course, other options for oracles, including [native V2 oracles](/concepts/oracles). ### Exact Input If you'd like to send an exact amount of input tokens in exchange for as many output tokens as possible, you'll want to use [getAmountsOut](/amm/technical-reference/contracts/soroswap-router). The equivalent SDK function is [getOutputAmount](/amm/technical-reference/contracts/soroswap-pair), or [minimumAmountOut](/amm/technical-reference/contracts/soroswap-pair) for slippage calculations. ### Exact Output If you'd like to receive an exact amount of output tokens for as few input tokens as possible, you'll want to use [getAmountsIn](/amm/technical-reference/contracts/soroswap-router). The equivalent SDK function is [getInputAmount](/amm/technical-reference/contracts/soroswap-pair), or [maximumAmountIn](/amm/technical-reference/contracts/soroswap-pair) for slippage calculations. ### Swap to Price For this more advanced use case, see [ExampleSwapToPrice.sol](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/examples/ExampleSwapToPrice.sol). # Research Source: https://docs.soroswap.finance/concepts/advanced/research A curated selection of academic papers and articles on automated market maker design and returns. The automated market maker is a new concept, and as such, new research comes out frequently. We've selected some of the most thoughtful here. ## Uniswap's Financial Alchemy Authors: Dave White, Martin Tassy, Charlie Noyes, and Dan Robinson > An automated market maker is a type of decentralized exchange that lets customers trade between on-chain assets like USDC and ETH. Uniswap is the most popular AMM on Ethereum. Like most AMMs, Uniswap facilitates trading between a particular pair of assets by holding reserves of both assets. It sets the trading price between them based on the size of its reserves in such a way that prices will stay in line with the broader market. Anybody who would like to can join the “pool” for a particular pair and become a liquidity provider, or LP, so-called because they provide liquid assets for others to trade against. LPs contribute assets to both reserves simultaneously, taking on some of the risk of trading in exchange for a share of the returns. * [Uniswap's Financial Alchemy](https://research.paradigm.xyz/uniswaps-alchemy) ## An analysis of Uniswap markets Authors: Guillermo Angeris, Hsien-Tang Kao, Rei Chiang, Charlie Noyes, Tarun Chitra > Uniswap---and other constant product markets---appear to work well in practice despite their simplicity. In this paper, we give a simple formal analysis of constant product markets and their generalizations, showing that, under some common conditions, these markets must closely track the reference market price. We also show that Uniswap satisfies many other desirable properties and numerically demonstrate, via a large-scale agent-based simulation, that Uniswap is stable under a wide range of market conditions. * [An analysis of Uniswap markets](https://arxiv.org/abs/1911.03380) ## Improved Price Oracles: Constant Function Market Makers Authors: Guillermo Angeris, Tarun Chitra > Automated market makers, first popularized by Hanson's logarithmic market scoring rule (or LMSR) for prediction markets, have become important building blocks, called 'primitives,' for decentralized finance. A particularly useful primitive is the ability to measure the price of an asset, a problem often known as the pricing oracle problem. In this paper, we focus on the analysis of a very large class of automated market makers, called constant function market makers (or CFMMs) which includes existing popular market makers such as Uniswap, Balancer, and Curve, whose yearly transaction volume totals to billions of dollars. We give sufficient conditions such that, under fairly general assumptions, agents who interact with these constant function market makers are incentivized to correctly report the price of an asset and that they can do so in a computationally efficient way. We also derive several other useful properties that were previously not known. These include lower bounds on the total value of assets held by CFMMs and lower bounds guaranteeing that no agent can, by any set of trades, drain the reserves of assets held by a given CFMM. * [Improved Price Oracles: Constant Function Market Makers](https://arxiv.org/abs/2003.10001) ## Pintail research Published [medium](https://medium.com/@pintail) articles by Pintail. * [Understanding Uniswap Returns](https://medium.com/@pintail/understanding-uniswap-returns-cc593f3499ef) * [Uniswap: A Good Deal for Liquidity Providers?](https://medium.com/@pintail/uniswap-a-good-deal-for-liquidity-providers-104c0b6816f2) ## Liquidity Provider Returns in Geometric Mean Markets Authors: Alex Evans > Geometric mean market makers (G3Ms), such as Uniswap and Balancer, comprise a popular class of automated market makers (AMMs) defined by the following rule: the reserves of the AMM before and after each trade must have the same (weighted) geometric mean. This paper extends several results known for constant-weight G3Ms to the general case of G3Ms with time-varying and potentially stochastic weights. These results include the returns and no-arbitrage prices of liquidity pool (LP) shares that investors receive for supplying liquidity to G3Ms. Using these expressions, we show how to create G3Ms whose LP shares replicate the payoffs of financial derivatives. The resulting hedges are model-independent and exact for derivative contracts whose payoff functions satisfy an elasticity constraint. These strategies allow LP shares to replicate various trading strategies and financial contracts, including standard options. G3Ms are thus shown to be capable of recreating a variety of active trading strategies through passive positions in LP shares. * [Liquidity Provider Returns in Geometric Mean Markets](https://arxiv.org/abs/2006.08806) ## The Replicating Portfolio of a Constant Product Market Authors: Joseph Clark > We derive the replicating portfolio of a constant product market. This is structurally short volatility (selling options) which explains why positive transaction costs are needed to induce liquidity providers to participate. Where futures and options markets do not exist, this payoff can be used to create them. * [https://papers.ssrn.com/sol3/papers.cfm?abstract\_id=3550601](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3550601) # Security Source: https://docs.soroswap.finance/concepts/advanced/security Audit and bug bounty details, plus the static and dynamic risks of integrating with pair contracts. ## Audit & Formal Verification Between January 8 and April 30, a team of six engineers reviewed and formally verified crucial components of the smart contracts for Uniswap V2. Their past work includes smart contract development on and formal verification of multi-collateral DAI. The scope of work includes: * Formal verification of the core smart contracts * Code review of core smart contracts * Numerical error analysis * Code review of periphery smart contracts (during ongoing development) The report also has a "Design Comments" section that we highly recommend for gaining a deep technical understanding of some of the choices made in Uniswap V2. > [Read the report](https://uniswap.org/audit.html) ## Bug Bounty Uniswap has an open and ongoing bug [bounty program](https://uniswap.org/bug-bounty/). ## Considerations when building on Uniswap When integrating Uniswap V2 into another on-chain system, particular care must be taken to avoid security vulnerabilities, avenues for manipulations, and the potential loss of funds. As a preliminary note: smart contract integrations can happen at two levels: directly with [Pair](/amm/technical-reference/contracts/soroswap-pair) contracts, or through the [Router](/amm/technical-reference/contracts/soroswap-router). Direct interactions offer maximal flexibility but require the most work to get right. Mediated interactions offer more limited capabilities but stronger safety guarantees. There are two primary categories of risk associated with Uniswap V2. The first involves so-called "static" errors. These can include sending too many tokens to a pair during a swap (or requesting too few tokens back) or allowing transactions to linger in the mempool long enough for the sender's expectations about prices to no longer be accurate. One may address these errors with fairly straightforward logic checks. Executing these logic checks is the primary purpose of routers. Those who interact directly with pairs must perform these checks themselves (with the help of the [Library](/amm/technical-reference/contracts/soroswap-library)). "Dynamic" risk, the second category, involves runtime pricing. Because Ethereum transactions occur in an adversarial environment, naively written smart contracts can, and will, be exploited for profit. For example, suppose a smart contract checks the asset ratio in a Uniswap pool at runtime and trades against it, assuming that the ratio represents the "fair" or "market" price of these assets. In that case, it is highly vulnerable to manipulation. A malicious actor could, e.g., trivially insert transactions before and after the naive transaction (a so-called "sandwich" attack), causing the smart contract to trade at a radically worse price, profit from this at the trader's expense, and then return the contracts to their original state, all at a low cost. (One important caveat is that these types of attacks are mitigated by trading in highly liquid pools, or at low values.) The best way to protect against these attacks is to introduce a price oracle. An oracle is any device that returns desired information, in this case, a pair's spot price. The best "oracle" is simply a traders' off-chain observation of the prevailing price, which can be passed into the trade as a safety check. This strategy is best suited to retail trading venues where users initiate transactions on their own behalf. However, it is often the case that a trusted price observation is not available (e.g., in multi-step, programmatic interactions involving Uniswap). Without a price oracle, these interactions will be forced to trade at whatever the (potentially manipulated) rate on Uniswap is. For details on the Uniswap V2 approach to oracles, see [Oracles](/concepts/oracles). # Understanding Returns Source: https://docs.soroswap.finance/concepts/advanced/understanding-returns Liquidity provider returns and impermanent loss, explained with a worked example and price ratio figures. Uniswap incentivizes users to add liquidity to trading pools by rewarding providers with the fees generated when other users trade with those pools. Market making, in general, is a complex activity. There is a risk of losing money during large and sustained movement in the underlying asset price compared to simply holding an asset. ## Risks To understand the risks associated with providing liquidity you can read [https://medium.com/@pintail/uniswap-a-good-deal-for-liquidity-providers-104c0b6816f2](https://medium.com/@pintail/uniswap-a-good-deal-for-liquidity-providers-104c0b6816f2) to get an in-depth look at how to conceptualize a liquidity position. ## Example from the article > Consider the case where a liquidity provider adds 10,000 DAI and 100 WETH to a pool (for a total value of \$20,000), the liquidity pool is now 100,000 DAI and 1,000 ETH in total. Because the amount supplied is equal to 10% of the total liquidity, the contract mints and sends the market maker “liquidity tokens” which entitle them to 10% of the liquidity available in the pool. These are not speculative tokens to be traded. They are merely an accounting or bookkeeping tool to keep track of how much the liquidity providers are owed. If others subsequently add/withdraw coins, new liquidity tokens are minted/burned such that everyone’s relative percentage share of the liquidity pool remains the same. > > **Now let’s assume the price trades on Coinbase from \$100 to \$150. The Uniswap contract should reflect this change as well after some arbitrage. Traders will add DAI and remove ETH until the new ratio is now 150:1.** > > What happens to the liquidity provider? The contract reflects something closer to 122,400 DAI and 817 ETH (to check these numbers are accurate, 122,400 \* 817 = 100,000,000 (our constant product) and 122,400 / 817 = 150, our new price). Withdrawing the 10% that we are entitled to would now yield 12,240 DAI and 81.7 ETH. The total market value here is \$24,500. Roughly \$500 worth of profit was missed out on as a result of the market making. > > **Obviously no one wants to provide liquidity out of charitable means, and the revenue isn’t dependent on the ability to flip out of good trades (there is no flipping). Instead, 0.3% of all trade volume is distributed proportionally to all liquidity providers. By default, these fees are put back into the liquidity pool, but can be collected any time. It’s difficult to know what the trade-off is between revenues from fees and losses from directional movements without knowing the amount of in-between trades. The more chop and back and forth, the better.** > > #### Why is my liquidity worth less than I put in? > > To understand why the value of a liquidity provider’s stake can go down despite income from fees, we need to look a bit more closely at the formula used by Uniswap to govern trading. The formula really is very simple. If we neglect trading fees, we have the following: > > * `eth_liquidity_pool * token_liquidity_pool = constant_product` > > In other words, the number of tokens a trader receives for their ETH and vice versa is calculated such that after the trade, the product of the two liquidity pools is the same as it was before the trade. The consequence of this formula is that for trades which are very small in value compared to the size of the liquidity pool we have: > > * `eth_price = token_liquidity_pool / eth_liquidity_pool` > > Combining these two equations, we can work out the size of each liquidity pool at any given price, assuming constant total liquidity: > > * `eth_liquidity_pool = sqrt(constant_product / eth_price)` > * `token_liquidity_pool = sqrt(constant_product * eth_price)` > > So let’s look at the impact of a price change on a liquidity provider. To keep things simple, let’s imagine our liquidity provider supplies 1 ETH and 100 DAI to the Uniswap DAI exchange, giving them 1% of a liquidity pool which contains 100 ETH and 10,000 DAI. This implies a price of 1 ETH = 100 DAI. Still neglecting fees, let’s imagine that after some trading, the price has changed; 1 ETH is now worth 120 DAI. What is the new value of the liquidity provider’s stake? Plugging the numbers into the formulae above, we have: > > * `eth_liquidity_pool = 91.2871` > * `dai_liquidity_pool = 10954.4511` > > "Since our liquidity provider has 1% of the liquidity tokens, this means they can now claim 0.9129 ETH and 109.54 DAI from the liquidity pool. But since DAI is approximately equivalent to USD, we might prefer to convert the entire amount into DAI to understand the overall impact of the price change. At the current price then, our liquidity is worth a total of 219.09 DAI. What if the liquidity provider had just held onto their original 1 ETH and 100 DAI? Well, now we can easily see that, at the new price, the total value would be 220 DAI. So our liquidity provider lost out by 0.91 DAI by providing liquidity to Uniswap instead of just holding onto their initial ETH and DAI." > > "Of course, if the price were to return to the same value as when the liquidity provider added their liquidity, this loss would disappear. **For this reason, we can call it an impermanent loss.** Using the equations above, we can derive a formula for the size of the impermanent loss in terms of the price ratio between when liquidity was supplied and now. We get the following:" > > * "`impermanent_loss = 2 * sqrt(price_ratio) / (1+price_ratio) — 1`" > * "Which we can plot out to get a general sense of the scale of the impermanent loss at different price ratios:" ![Impermanent loss against price ratio](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/concepts/impermanent-loss-curve.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=7630732a7cf94099cbb1c82829ca8213) > * "Or to put it another way:" > * "a 1.25x price change results in a 0.6% loss relative to HODL" > * "a 1.50x price change results in a 2.0% loss relative to HODL" > * "a 1.75x price change results in a 3.8% loss relative to HODL" > * "a 2x price change results in a 5.7% loss relative to HODL" > * "a 3x price change results in a 13.4% loss relative to HODL" > * "a 4x price change results in a 20.0% loss relative to HODL" > * "a 5x price change results in a 25.5% loss relative to HODL" > * "N.B. The loss is the same whichever direction the price change occurs in (i.e. a doubling in price results in the same loss as a halving)." # AMM Source: https://docs.soroswap.finance/concepts/amm What an automated market maker is, and how Soroswap's constant-product pools on Soroban hold their own liquidity, separate from the Stellar classic DEX. #### What is an AMM? An [**Automated Market Maker (AMM)**](https://developers.stellar.org/docs/learn/encyclopedia/sdex/liquidity-on-stellar-sdex-liquidity-pools#automated-market-makers-amms) is a decentralized protocol that facilitates asset exchanges without relying on a traditional order book. Instead of using buy and sell orders, [AMMs ](https://stellar.org/blog/developers/introducing-automated-market-makers-on-stellar?locale=en)employ mathematical formulas to determine asset prices based on the relative quantities within their liquidity pools. Soroswap.Finance operates as an AMM with its own [liquidity pools](/concepts/liquidity-pools), distinct from other venues like the Stellar Classic DEX. Soroswap.Finance ensures that asset swaps are managed autonomously and efficiently, maintaining liquidity and price stability through its decentralized framework. To understand [How Soroswap AMM Works](/amm/how-it-works). # Bridge Source: https://docs.soroswap.finance/concepts/bridge What a blockchain bridge is, and how assets from other chains reach Stellar so Soroswap can trade them. ### What is a Bridge? In blockchain technology, a **bridge** is a protocol that connects two or more blockchain networks, enabling them to interact and exchange assets or data. Bridges are crucial for improving interoperability between different blockchains, which often operate independently of one another. ### Key Functions of a Bridge * **Asset Transfer:** Bridges facilitate the transfer of assets, such as tokens or cryptocurrencies, between different blockchains. This is essential for users who wish to move their assets from one blockchain ecosystem to another. * **Data Exchange:** Bridges allow for the transfer of data between blockchains, enabling different networks to share information and synchronize their activities. * **Interoperability:** By connecting disparate blockchains, bridges enhance interoperability, allowing applications and users to operate seamlessly across multiple blockchain platforms. ### How Bridges Work 1. **Locking and Minting:** * **Locking:** Assets are locked on the source blockchain to prevent double-spending or misuse. * **Minting:** Corresponding assets are minted or issued on the destination blockchain, allowing users to access them within the new ecosystem. 2. **Verification:** The bridge verifies transactions and ensures that assets are securely locked on the source blockchain before issuing the equivalent assets on the destination blockchain. 3. **Reverse Transfers:** Bridges also handle the process of transferring assets back to the original blockchain, ensuring that assets are locked on the destination chain before unlocking or issuing them on the source chain. ### Bridging into Stellar for Soroswap Soroswap itself is not a bridge. It trades assets that are already on Stellar, so anything issued elsewhere has to be bridged in first, and then given a [Stellar Asset Contract](/tutorials/stellar-classic-assets/wrapping) if you want to use it with Soroban contracts. The app has a **Bridge** tab for this, at [app.soroswap.finance/bridge](https://app.soroswap.finance/bridge). The provider behind it has changed over time, so the tab in the app is the accurate answer to which route is available today rather than anything written here. Once the asset is on Stellar, your account still needs a [trustline](/concepts/trustlines) before it can hold it. The Soroswap API can open that trustline as part of the swap, see [Gasless Trustline](/api/gasless-trustline). # Fees Source: https://docs.soroswap.finance/concepts/fees The 0.3% swap fee paid to liquidity providers, and the potential future 0.05% protocol fee. ### Liquidity provider fees There is a **0.3%** fee for swapping tokens. **This fee is split by liquidity providers proportional to their contribution to liquidity reserves.** Swapping fees are immediately deposited into liquidity reserves. This increases the value of liquidity tokens, functioning as a payout to all liquidity providers proportional to their share of the pool. Fees are collected by burning liquidity tokens to remove a proportional share of the underlying reserves. Since fees are added to liquidity pools, the invariant increases at the end of every trade. Within a single transaction, the invariant represents `token0_pool / token1_pool` at the end of the previous transaction. There are many community-developed tools to determine returns. You can also read more in the docs about how to think about [LP returns](/concepts/advanced/understanding-returns). ### Protocol Fees > [!NOTE] > > This page describes the Soroswap AMM pools. Swaps executed through the **Soroswap aggregator** > (the API and the aggregator contract) pay a separate aggregator fee, 10 bps today, or a partner > fee when an integrator monetizes the swap. See [Fees and revenue share](/api/fees). At the moment there are no protocol fees on the AMM pools. However, it is possible for a 0.05% fee to be turned on in the future. More information about a potential future protocol fee can be found [here](https://uniswap.org/blog/uniswap-v2/#path-to-sustainability). ### Protocol Charge Calculation In the future, it is possible that a protocol-wide charge of 0.05% per trade will take effect. This represents ⅙th (16.6̅%) of the 0.30% fee. The fee is in effect if [feeTo](/amm/technical-reference/contracts/soroswap-factory) is not `address(0)` (`0x0000000000000000000000000000000000000000`), indicating that feeTo is the recipient of the charge. This amount would not affect the fee paid by traders, but would affect the amount received by liquidity providers. Rather than calculating this charge on swaps, which would significantly increase gas costs for all users, the charge is instead calculated when liquidity is added or removed. See the [whitepaper](https://uniswap.org/whitepaper.pdf) for more details. # Flash Swaps Source: https://docs.soroswap.finance/concepts/flash-swaps Withdrawing pool reserves with no upfront capital, repaying within the same transaction, with arbitrage and leverage examples. Uniswap flash swaps allow you to withdraw up to the full reserves of any ERC20 token on Uniswap and execute arbitrary logic at no upfront cost, provided that by the end of the transaction you either: * pay for the withdrawn ERC20 tokens with the corresponding pair tokens * return the withdrawn ERC20 tokens along with a small fee Flash swaps are incredibly useful because they obviate upfront capital requirements and unnecessary order-of-operations constraints for multi-step transactions involving Uniswap. ## Examples ### Capital Free Arbitrage One particularly interesting use case for flash swaps is capital-free arbitrage. It's well-known that an integral part of Uniswap's design is to create incentives for arbitrageurs to trade the Uniswap price to a "fair" market price. While game-theoretically sound, this strategy is accessible only to those with sufficient capital to take advantage of arbitrage opportunities. Flash swaps remove this barrier entirely, effectively democratizing arbitrage. Imagine a scenario where the cost of buying 1 ETH on Uniswap is 200 DAI (which is calculated by calling `getAmountIn` with 1 ETH specified as an exact output), and on Oasis (or any other trading venue), 1 ETH buys 220 DAI. To anyone with 200 DAI available, this situation represents a risk-free profit of 20 DAI. Unfortunately, you may not have 200 DAI lying around. With flash swaps, however, this risk-free profit is available for anyone to take as long as they're able to pay gas fees. #### Withdrawing ETH from Uniswap The first step is to *optimistically* withdraw 1 ETH from Uniswap via a flash swap. This will serve as the capital that we use to execute our arbitrage. Note that in this scenario, we're assuming that: * 1 ETH is the pre-calculated profit-maximizing trade * The price has not changed on Uniswap or Oasis since our calculation It may be the case that we'd like to calculate the profit-maximizing trade on-chain at the moment of execution, which is robust to price movements. This can be somewhat complex, depending on the strategy being executed. However, one common strategy is trading as profitably as possible *against a fixed external price*. (This price may be e.g., the average execution price of one or more orders on Oasis.) If the Uniswap market price is far enough above or below this external price, the following example contains code that calculates the amount to trade over Uniswap for maximum profit: [`ExampleSwapToPrice.sol`](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/examples/ExampleSwapToPrice.sol). #### Trade at External Venue Once we've obtained our temporary capital of 1 ETH from Uniswap, we now can trade this for 220 DAI on Oasis. Once we've received the DAI, we need to pay Uniswap back. We've mentioned that the amount required to cover 1 ETH is 200 DAI, calculated via `getAmountIn`. So, after sending 200 of the DAI back to the Uniswap pair, you're left with 20 DAI of profit! ### Instant Leverage Flash swaps can be used to improve the efficiency of levering up using lending protocols and Uniswap. Consider Maker in its simplest form: a system which accepts ETH as collateral and allows DAI to be minted against it while ensuring that the value of the ETH never drops below 150% of the value of the DAI. Say we use this system to deposit a principal amount of 3 ETH, and mint the maximum amount of DAI. At a price of 1 ETH / 200 DAI, we receive 400 DAI. In theory, we could lever this position up by selling the DAI for more ETH, depositing this ETH, minting the maximum amount of DAI (which would be less this time), and repeating until we've reached our desired leverage level. It's quite simple to use Uniswap as a liquidity source for the DAI-to-ETH component of this process. However, looping through protocols in this way isn't particularly elegant, and can be gas-intensive. Luckily, flash swaps enable us to withdraw the *full* ETH amount upfront. If we wanted 2x leverage against our 3 ETH principal, we could simply request 3 ETH in a flash swap and deposit 6 ETH into Maker. This gives us the ability to mint 800 DAI. If we mint as much as we need to cover our flash swap (say 605), the remainder serves as a safety margin against price movements. # Concepts Source: https://docs.soroswap.finance/concepts/index The conceptual toolkit behind Soroswap: AMMs, liquidity pools, swaps, fees, routing and the Stellar primitives they build on. This section is your conceptual toolkit for understanding how Soroswap.Finance works. Before diving into tutorials or API calls, review these pages and master the fundamentals. ## What you'll find here > **[AMM](/concepts/amm)** > > How an Automated Market Maker replaces the order book. > **[Liquidity Pools](/concepts/liquidity-pools)** > > Where the tokens you trade against actually live. > **[Swap](/concepts/swap)** > > What happens when you exchange one token for another. > **[Fees](/concepts/fees)** > > What a trade costs and who collects it. > **[Slippage](/concepts/slippage)** > > Why the price you get can differ from the price you saw. > **[Router](/amm/technical-reference/contracts/soroswap-router)** > > The Soroswap AMM router: direct and multi-hop swaps across its pools. > **[SDEX](/concepts/sdex)** > > The Stellar Classic order book, and how it differs from Soroban AMMs. > **[Aggregator API](/)** > > One API that splits a trade across several Stellar venues for the best price. > **[Bridge](/concepts/bridge)** > > Moving assets between Stellar and other chains. > **[Trustlines](/concepts/trustlines)** > > The Stellar rule you must satisfy before holding any non-native asset. > **[Oracles](/concepts/oracles)** > > Using pool reserves as an on-chain price feed. > **[Flash Swaps](/concepts/flash-swaps)** > > Withdrawing tokens first and paying for them in the same transaction. ## Going deeper > **[Advanced Topics](/concepts/advanced)** > > Pricing, understanding returns, security and open research questions. # Liquidity Pools Source: https://docs.soroswap.finance/concepts/liquidity-pools Liquidity pools as markets for a token pair, covering pool tokens, fees, and why pools replace order books. Each liquidity pool functions as a market for a specific pair of tokens. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2025-04-24-a-las-15-07-19.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=e188b853fb4c215e1e35471ab87e833b) ## Introduction In Soroswap Finance, each liquidity pool acts as a trading venue for a pair of assets. When a pool is initially created, it starts with zero balance for each asset, requiring an initial deposit to facilitate trades. The first liquidity provider sets the initial price by depositing equal values of both assets, aligning with the current market rate. This prevents immediate arbitrage opportunities, which occur if the assets are deposited at a ratio different from the prevailing market price. Subsequent liquidity providers must deposit assets proportional to the current pool price to prevent their contributions from being arbitraged. If they believe the current price is inaccurate, they can engage in arbitrage to adjust the price to their desired level before adding liquidity, ensuring their assets are valued correctly in the pool. ## Pool tokens ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2025-04-24-a-las-15-09-28.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=e63f7f04794a70e0744e2b668e5377d0) When liquidity is deposited into a Soroswap liquidity pool, unique tokens known as pool tokens are minted and sent to the provider's address. These tokens represent the provider's stake in the pool and are a crucial component of the liquidity provision process. The number of pool tokens a provider receives is proportional to their contribution to the pool's total liquidity. When seeding a new pool, the initial liquidity provider sets the quantities of the assets, and the relationship between these assets follows the equation (x \cdot y = k), where (x) and (y) are the amounts of each asset, and (k) is a constant that remains constant during the pool's operations. Every time a trade is executed within the pool, a transaction fee of 0.3% is charged to the sender. This fee is then distributed pro-rata among all liquidity providers (LPs) in the pool, rewarding them for their participation and incentivizing continued liquidity provision. To withdraw their original assets along with any accrued fees, liquidity providers must "burn" their pool tokens. This process effectively exchanges the tokens for their share of the pool's liquidity, including the proportional allocation of the collected fees. Pool tokens themselves are tradable assets, providing flexibility to liquidity providers. They can sell, transfer, or utilize these tokens in various ways, offering additional liquidity management options and potential profit opportunities within the decentralized finance ecosystem. > Learn more with advanced topics: * [Understanding Returns](/concepts/advanced/understanding-returns) * [Fees](/concepts/fees) ## Why pools? Soroswap Finance uses liquidity pools instead of traditional order books to enable decentralized token swaps. Liquidity pools consist of user-provided assets locked in smart contracts, allowing seamless, automated trades without relying on a centralized intermediary. This structure addresses key limitations of order books, such as the need for intermediaries, active management by market makers, and high infrastructure requirements. Pools in Soroswap operate autonomously, leveraging smart contracts to continuously provide liquidity and execute trades. This design is more suited for decentralized ecosystems where tokens may have low liquidity and anyone can create or trade assets without permission. It simplifies the process, ensures more consistent liquidity, and opens up participation to a wider audience, including those without sophisticated trading tools. By embracing a pool-based system, Soroswap benefits from the open, trustless, and permissionless nature of blockchain technology, allowing decentralized finance (DeFi) to thrive with minimal friction. ## Developer resources * To see how to pool tokens in a smart contract read [Providing Liquidity](/amm/technical-reference/contracts/soroswap-router#add_liquidity). # Oracles Source: https://docs.soroswap.finance/concepts/oracles The on-chain price oracle problem, cumulative price accumulators, time weighted average prices, and manipulation resistance. ## Introduction A price oracle is any tool used to view price information about a given asset. When you look at stock prices on your phone, you are using your phone as a price oracle. Similarly, the app on your phone relies on devices to retrieve price information - likely several, which are aggregated and then displayed to you, the end-user. These are price oracles as well. When building smart contracts that integrate with DeFi protocols, developers will inevitably run into the price oracle problem. What is the best way to retrieve the price of a given asset on-chain? Many oracle designs on Ethereum have been implemented on an ad-hoc basis, with varying degrees of decentralization and security. Because of this, the ecosystem has witnessed numerous high-profile hacks where the oracle implementation is the primary attack vector. Some of these vulnerabilities are discussed [here](https://samczsun.com/taking-undercollateralized-loans-for-fun-and-for-profit/). While there is no one size fits all solution, Uniswap V2 enables developers to build highly decentralized and manipulation-resistant on-chain price oracles, which may solve many of the demands necessary for building robust protocols. ## Uniswap V2 solution Uniswap V2 includes several improvements for supporting manipulation-resistant public price feeds. First, every pair measures (but does not store) the market price at the beginning of each block, before any trades take place. This price is expensive to manipulate because it is set by the last transaction, whether it is a mint, swap, or burn, in a previous block. **To set the measured price to one that is out of sync with the global market price, an attacker has to make a bad trade at the end of a previous block** , typically with no guarantee that they will arbitrage it back in the next block. Attackers will lose money to arbitrageurs unless they can “selfishly” mine two blocks in a row. This type of attack presents several challenges and [has not been observed to date](https://arxiv.org/abs/1912.01798). Unfortunately, this alone is not enough. If significant value settles based on the price resulting from this mechanism, an attack’s profit will likely outweigh the loss. Instead, Uniswap V2 adds this end-of-block price to a single cumulative-price variable in the core contract weighted by the amount of time this price existed. **This variable represents a sum of the Uniswap price for every second in the entire history of the contract.** ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/concepts/core-concepts/v2-onchain-price-data.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=20e0f7a5ca3b439e0269fc434d37620d) This variable can be used by external contracts to track accurate time-weighted average prices (TWAPs) across any time interval. The TWAP is constructed by reading the cumulative price from an ERC20 token pair at the beginning and at the end of the desired interval. The difference in this cumulative price can then be divided by the length of the interval to create a TWAP for that period. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/concepts/core-concepts/v2-twap.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=1af4b9ad52b06f4d02dae0c8b676883a) TWAPs can be used directly or as the basis for moving averages (EMAs and SMAs) as needed. A few notes: * For a 10-minute TWAP, sample once every 10 minutes. For a 1-week TWAP, sample once every week. * For a simple TWAP, the cost of manipulation increases (approx. linear) with liquidity on Uniswap, as well as (approx. linear) with the length of time over which you average. * The Cost of an attack is relatively simple to estimate. Moving the price 5% on a 1-hour TWAP is approximately equal to the amount lost to arbitrage and fees for moving the price 5% every block for 1 hour. There are some nuances that are good to be aware of when using Uniswap V2 as an oracle, especially where manipulation resistance is concerned. The [whitepaper](https://uniswap.org/whitepaper.pdf) elaborates on some of them. Additional oracle-focused developer guides and documentation will be released soon. In the meantime, check out our [example implementation](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/examples/ExampleOracleSimple.sol) of a 24 hr TWAP Oracle built on Uniswap V2! ### Manipulation resistance The cost of manipulating the price for a specific time period can be roughly estimated as the amount lost to arbitrage and fees every block for the entire period. For larger liquidity pools and over longer time periods, this attack is impractical, as the cost of manipulation typically exceeds the value at stake. Other factors, such as network congestion, can reduce the cost of attack. For a more in-depth review of the security of Uniswap V2 price oracles, read the [security audit section on Oracle Integrity](https://uniswap.org/audit.html#org87c8b91). ## Building an oracle A developer guide on building an oracle on top of Soroswap is not published yet. # SDEX Source: https://docs.soroswap.finance/concepts/sdex Stellar's native order book exchange, how it differs from Soroswap AMMs, and how the Aggregator API routes through it. What Stellar SDEX is, how it differs from Soroswap.Finance, and how the Soroswap Aggregator API quotes it. For the integration details, see [Routing through SDEX](/api/sdex). ### What is Stellar SDEX? [**Stellar SDEX**](https://developers.stellar.org/docs/learn/encyclopedia/sdex) **(Stellar Decentralized Exchange)** is the native decentralized exchange of the Stellar network. It allows users to trade assets directly on the Stellar blockchain through a system of order books. Here’s how it operates: * **Order Books:** Stellar SDEX uses order books to facilitate trades. Users place buy and sell offers in the order book, which are matched based on price and time priority. * **Order Types:** Users can create various types of orders, such as market or limit orders. Orders are automatically matched against existing orders in the book. * **Asset Exchange:** Trades are executed by matching buy and sell orders within the order books. Assets are exchanged directly between users without needing an intermediary. * **Path Payments:** If there is no direct order for an asset pair, Stellar allows path payments to find the best route through multiple assets to complete the trade. | Feature | Stellar SDEX | Soroswap.Finance | | - | - | - | | **Exchange Model** | [Order book](https://developers.stellar.org/docs/learn/encyclopedia/sdex/liquidity-on-stellar-sdex-liquidity-pools#order-books) (buy and sell orders) | [Automated Market Makers (AMMs)](/amm/how-it-works) | | **Pricing Mechanism** | Based on matching orders in the order book | Based on mathematical formulas[ (e.g., x \* y = k)](/amm/glossary#x-y-k) | | **Liquidity Provision** | Users place orders in the order book | Users deposit assets into liquidity pools | | **Transaction Execution** | Executes orders directly based on price-time priority | Executes transactions through AMM, adjusting prices according to reserves | | **Order Types** | Limit and passive orders | Interactions with AMM, no traditional limit orders | | **Aggregator Integration** | Quoted by the [Aggregator API](/api/sdex) as a classic path | Integrated into [the Aggregator API](/) | # Slippage Source: https://docs.soroswap.finance/concepts/slippage Slippage tolerance in Soroswap: what it protects against, and the auto and custom settings. **Slippage** is the difference between the expected price of a swap and the actual price at the time of execution. It often occurs due to rapid changes in liquidity or asset demand. In Soroswap, you can set your slippage tolerance directly from the swap settings menu. #### What does it mean? > If the price changes more than the allowed slippage percentage during execution, **the transaction will automatically revert** to protect you from an unfavorable trade. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2025-04-29-a-las-14-59-47.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=decf4a461aec0d192233b224c674c24a) #### Available options: * **Auto**: The system automatically selects an appropriate slippage based on current pool liquidity. * **Custom**: Set your own value (e.g., 1%). > 🛠 You can find this setting by clicking the **gear icon** in the upper corner of the swap interface. # Swap Source: https://docs.soroswap.finance/concepts/swap Swapping Stellar assets through Soroswap AMMs or the Aggregator, and the constant product formula behind pricing. A **swap** is the process of exchanging one Stellar asset for another using liquidity from Soroban-based [**Automated Market Makers (AMMs**](/concepts/amm)**)**. Swaps can be executed directly through the [**Soroswap AMM** ](/amm)or routed via the [**Aggregator API**,](/) which optimally resolves the operation by finding the **best available price at the time** through an **intelligent distribution** across supported AMMs.\ \ 👉🏽 [How to Swap](/getting-started/how-to-swap)\ Follow the [Swap Tutorial](/tutorials/doing-swap) ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/swap-1.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=72fa1410ef34a0319f6d195219a10723) ### How does a swap work? **User selects two assets** – one to sell and one to receive. 1. **Input amount is defined** – the app estimates the output based on current pool conditions. 2. **Router SDK scans available pools** – if the Aggregator is enabled, it calculates the best distribution **`DexDistribution`**. 3. **User confirms the swap** – the smart contract receives the input asset and performs the swap. 4. **User receives the new asset** – if it's the first time, a **trustline** must be signed to accept the asset. ### 📐 Constant Product Formula Each AMM in Soroswap follows this formula: ``` x * y = k ``` Where: * `x` = balance of asset A * `y` = balance of asset B * `k` = constant product (remains unchanged) When someone swaps A for B: * A is deposited into the pool * B is withdrawn * The price updates to maintain the balance This model ensures continuous liquidity. 🔜 **Next:** [Fees](/concepts/fees)\ 🔙 **Previous:** [Liquidity Pools](/concepts/liquidity-pools) # Trustlines Source: https://docs.soroswap.finance/concepts/trustlines Trustlines authorize an account to hold a Stellar asset, required for pool participation and swaps. In **Soroswap Finance**, a *trustline* is an essential mechanism that allows your account on the Stellar network to hold and exchange a specific asset. This process is crucial for interacting with the assets you wish to trade on the platform. ### **What is a Trustline?** A [*trustline* ](https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#trustlines)is an explicit authorization that you establish between your account and an issuing account of an asset on the Stellar network. Essentially, it tells Stellar that you trust a specific issuer to allow your account to receive and handle that asset. Without an appropriate *trustline*, you cannot hold or trade that asset within your account.\\ ### **How Trustlines Function** * **Holding Assets:** To hold a specific asset, an account needs to have a [trustline](https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#trustlines) with the issuing account of that asset. This setup allows the account to keep track of the asset's balance and also imposes a limit on the amount of that asset the account can hold. * **Receiving Assets:** A trustline must be established for an account to receive any asset, except for Lumens (XLM). While it’s possible to create a claimable balance to send assets to an account without an existing trustline, the recipient must still create a trustline to claim the balance. Learn more about [Claimable Balances here.](https://developers.stellar.org/docs/learn/encyclopedia/transactions-specialized/claimable-balances) * [**Trustlines track**](https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#trustlines) **liabilities for asset trades in two ways:** Buying Liabilities, which are the total amount of an asset an account offers to buy across all its offers, and Selling Liabilities, which are the total amount of an asset an account offers to sell across all its offers. *An account must maintain a balance that is large enough to cover its selling liabilities and sufficiently below its limit to handle its buying liabilities.* ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2025-04-29-a-las-15-07-00.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=e76f128c4f3dad03f086f13f37161750) ### [**Trustlines**](https://developers.stellar.org/docs/learn/encyclopedia/sdex/liquidity-on-stellar-sdex-liquidity-pools#trustlines) **for Liquidity Pools** To participate in a[ liquidity poo](/tutorials/adding-liquidity)l and perform [swaps](/tutorials/doing-swap) on Soroswap Finance, you need to establish trustlines for the following three types of assets: * **Reserve Assets:** These are the assets held within the liquidity pool. To participate in a pool, you must establish a trustline with each of the reserve assets that make up the pool. For example, if the liquidity pool contains assets A and B, you will need trustlines for both asset A and asset B. * **Pool Share Assets:** This asset represents your share of the liquidity pool. You need a trustline for each pool share associated with the pool you want to participate in. The trustline for the pool share allows you to receive and manage your proportional part of the pool. * **Lumens (XLM):** In some cases, especially if one of the reserve assets is XLM, you do not need to establish a specific trustline for XLM, as trustlines for XLM are not required. However, if the liquidity pool does not include XLM as one of the reserve assets, you will need to establish trustlines for all the assets involved in the pool. ### **Trustlines for Performing Swaps** To exchange assets on Soroswap Finance using the path payments system, you also need to establish trustlines: * **Assets to be Exchanged:** You must establish trustlines for each asset you wish to exchange. For example, if you want to swap Asset X for Asset Y, you need to have a trustline for both Asset X and Asset Y. * **Asset Management:** Once you have established the trustlines, your account can receive and manage these assets, allowing you to perform swaps safely and effectively. # Adding Tokens to Your Wallet Source: https://docs.soroswap.finance/getting-started/adding-tokens-to-your-wallet The asset code and issuer a Stellar wallet needs to add a token, with USDC as the example, and the steps in wallets such as LOBSTR, Freighter and xBull. ### Obtain Asset Information To add an asset to your Stellar wallet, you need to obtain the following information about the token you wish to add: 1. [**Asset Code**:](https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/assets#asset-code) * **Description**: This is the unique identifier or symbol for the token (e.g., `USDC`). * **How to Obtain**: The asset code is typically provided by the token issuer or can be found in the token's official documentation. 2. **Issuer's Public Key**: * **Description**: This is the public key of the account that issued the token (e.g., `GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN`). * **How to Obtain**: The issuer's public key is usually provided by the token issuer. It can also be found in public Stellar directories such as [StellarExpert](https://stellar.expert/explorer/public) or on the issuer's website. 3. **Additional Details (Optional but Useful)**: * **Issuer Domain**: `centre.io` * **Issuer URL**: [https://centre.io](https://centre.io/) * **Asset Description**: This field can be optional in some wallets but provides additional context about the token. **Note**: *Ensure that you verify this information from reliable sources to avoid errors or fraud*. ### **Steps to Add the Token to Your Stellar Wallet** 1. **Open Your Stellar Wallet**: * [Access your Stellar wallet](/getting-started/wallet-setup) (e.g., [Lobstr Wallet](https://lobstr.co/), [Freighter Wallet](https://www.freighter.app/) , [XBull Wallet](https://xbull.app/), etc.). 2. **Navigate to the Assets or Trustlines Section**: * Look for the option to add or manage [trustlines](/concepts/trustlines) in your wallet. 3. **Add a New Trustline**: * Enter the **Asset Code** (`USDC`). * Enter the **Issuer's Public Key** (`GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN`). * Complete the transaction to set up the trustline. This usually requires a small fee in XLM (Stellar’s native currency) to establish the trustline. 4. **Verify the Token Addition**: * After setting up the trustline, you should be able to see the `USDC` token in your wallet. You can receive tokens of this type through transactions or other forms of deposits. # Bridging Tokens to Stellar Blockchain Source: https://docs.soroswap.finance/getting-started/bridging-tokens How assets from other chains reach Stellar so you can trade them on Soroswap. Soroswap trades assets that live on Stellar. To trade something issued on another chain, it has to be bridged onto Stellar first, and then, if you want to use it with Soroban contracts, [wrapped](/tutorials/stellar-classic-assets/wrapping). > **[What a bridge is](/concepts/bridge)** > > The concept, the trust assumptions, and what can go wrong. > **[Open the Bridge](https://app.soroswap.finance/bridge)** > > Bridge into Stellar from the app's Bridge tab. > [!NOTE] > > Once an asset is on Stellar you still need a [trustline](/concepts/trustlines) for it before your account can hold it. The Soroswap API can open that trustline for the user inside the swap, see [Gasless Trustline](/api/gasless-trustline). # How to Swap Source: https://docs.soroswap.finance/getting-started/how-to-swap Swapping assets in the Soroswap app: selecting tokens, confirming the trade, and adjusting slippage tolerance. The **Swap** section allows you to exchange one asset for another using liquidity provided by **Soroswap AMM** or, when available, optimized routes through the **Soroswap Aggregator**. \ **Select the assets**\ Choose the asset you want to swap *from* and the asset you want to receive. You can search or pick from the available list. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/grabacion-de-pantalla-2025-04-25-a-las-12-43-52.gif?s=aed16defe3eaa8319a9c27f0ac7ad0a4) **Enter the amount**\ Input how much you want to swap. The app will automatically estimate how much you’ll receive. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2025-04-25-a-las-12-52-39.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=847384fc5d46af064726f2ea38a8eeff) **Best route via Aggregator (automatic)**\ If a better price is available by splitting the trade across multiple AMMs (e.g. Aquarius, SushiSwap V3 or the Soroswap AMM), the **Aggregator** will route the swap automatically—no manual setup needed. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/aggregator.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=fff7c52c8ee1c342f5d0b0127928bc1d) **Approve trustline (if needed)**\ If it’s your first time using the asset, you’ll need to sign a trustline to authorize it in your wallet. **Confirm the swap**\ Review the swap details and sign the transaction in your wallet. **Check the result**\ Once confirmed, your wallet balance will update. You can also view the transaction on [Stellar.Expert](https://stellar.expert/) or [StellarChain](https://stellarchain.io/). #### ⚙️ Adjusting slippage tolerance You can control how much price variation you’re willing to accept: * Open the **Settings** panel * Select a predefined slippage value (e.g. 0.5%) or enter a custom one * Click **Save** to apply changes > 📘 Need a full walkthrough with screenshots and tips? Follow the [Swap Tutorial](/tutorials/doing-swap) *** 🔄 **Next step:** Provide Liquidity\ 🧠 **Learn more:** How the Aggregator Works # Getting Started Source: https://docs.soroswap.finance/getting-started/index Set up your wallet, connect it to Stellar, add tokens, and manage liquidity on Soroswap to begin using the platform. ## What do you need? * A **wallet compatible with Stellar/Soroban** * Access to the [Soroswap app](https://app.soroswap.finance) * Make your first **token swap** using the AMM * To try the **Aggregator** it automatically finds the best price across multiple liquidity pools #### 🔐 Supported Wallets Thanks to the `stellar-wallets-kit` library, Soroswap Finance supports: * [**Freighter**](https://www.freighter.app/) * [**xBull** ](https://xbull.app/) * [**Albedo**](https://albedo.link/) * [**Rabet**](https://rabet.io/download) * [**Lobstr**](https://lobstr.co/) * [**Hana Wallet**](https://www.hanawallet.io/) * [**Hot Wallet**](https://hot-labs.org/) The app automatically detects your available wallets and shows a simple UI to select and connect one. No extra configuration is needed. #### 🔄 How to connect? 1. Go to [app.soroswap.finance](https://app.soroswap.finance) 2. Click **“**[**Connect Wallet**](/getting-started/wallet-setup)**”** 3. Choose your preferred wallet (e.g., Freighter or **Lobstr**) 4. Approve the connection 5. Done! You’re ready to start swapping or providing liquidity # Liquidity Management on Soroswap Source: https://docs.soroswap.finance/getting-started/liquidity-management Managing Soroswap liquidity: wallet setup, adding liquidity to a pool, and withdrawing your share. On **Soroswap Finance**, liquidity management is facilitated through **liquidity pools** managed by an [**Automated Market Maker (AMM)**](/concepts/amm). This automated approach allows for efficient and continuous asset conversion without the need for a traditional order book. Here’s an overview of how to interact with liquidity pools on Soroswap and get started. ### What is a Liquidity Pool? **Liquidity pools** on Soroswap enable users to deposit and withdraw assets efficiently. **Automated Market Makers (AMMs)** manage these pools using mathematical formulas to set asset prices and ensure constant liquidity. For a detailed explanation of liquidity pools, refer to the Definition of [Liquidity Pools.](/concepts/liquidity-pools) ### Getting Started with Soroswap Finance To start using Soroswap Finance, follow these essential steps: **1. Set Up Your Wallet** * **Install Freighter Wallet**: Download and install the Freighter browser extension from [Freighter Wallet](https://freighter.app). * **Set Up Your Account**: Create a keypair or import an existing account using a mnemonic phrase. Then, switch to the Testnet from the network dropdown in Freighter. * **Get Test XLM:** To cover transaction fees, use the Friendbot to fund your [testnet](https://developers.stellar.org/docs/learn/fundamentals/networks#testnet) account with 10,000 lumens. Just enter your Stellar public address to receive the funds. Check the guide for [Soroswap testnet overviews](/tutorials/soroswap-testnet-overview). **2. Add and Manage Liquidity** Adding and managing liquidity on Soroswap Finance involves the following steps: * **Access the Add Liquidity Page**: Go to the relevant section on Soroswap to manage liquidity. Check the [guide for Adding Liquidity for detailed instructions.](/tutorials/adding-liquidity) * **Select Tokens**: Choose the tokens you wish to add to the pool. Review the pool information before confirming the transaction. * **Create a New Liquidity Pool**: When adding liquidity, you may create a new pool. Provide an initial amount of each asset you wish to include. Here’s the key information: * **Minting Tokens**: The amount of tokens minted to represent your share in the pool. * **Exchange Rate**: The rate between the assets you are depositing into the pool. * **Maximum Slippage**: The tolerance for price deviation that can cause the transaction to revert if prices change more than 0.5%. * **Pool Share**: Your percentage of ownership in the pool after adding liquidity. * **Network Fee**: The fee associated with the transaction. Once completed, review the details and submit the transaction. You can check the transaction status and hash on [Stellar.Expert](https://stellar.expert) or [StellarChain.io](https://stellarchain.io). **3. Remove Liquidity** To adjust your participation or withdraw liquidity from a pool: * **Access the Remove Liquidity Page**: Navigate to the appropriate section on Soroswap to manage liquidity removal. Refer to [the guide for Removing Liquidity for detailed instructions.](/tutorials/remove-liquidity) * **Select the Amount to Withdraw**: Choose the amount of liquidity you wish to withdraw and review the related information before confirming. * **Complete the Transaction**: Follow the steps provided to withdraw the assets corresponding to your share in the pool. # Provide Liquidity Source: https://docs.soroswap.finance/getting-started/provide-liquidity Depositing two assets into a Soroswap pool to earn trading fees, and receiving pool tokens. Providing liquidity on **Soroswap** allows users to earn rewards by depositing two assets into a liquidity pool. Liquidity providers (LPs) facilitate asset swaps for other users, and in return, they earn a share of the trading fees collected by the protocol. ### How to Provide Liquidity 1. **Access the app**\ Open [app.soroswap.finance](https://app.soroswap.finance). 2. **Connect your wallet**\ Use a Stellar/Soroban-compatible wallet. Make sure you have enough XLM for transaction fees. 3. **Navigate to the Liquidity section**\ In the app, select **Liquidity** to view existing pools ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/grabacion-de-pantalla-2025-04-28-a-las-13-30-30.gif?s=2f95ef15a6d5a71513c1b0c53a175cb3) 4. **Deposit both assets** * Enter the amounts of each asset you want to deposit. * The ratio must match the current pool price (or, when creating a new pool, you set the initial price). 5. **Receive pool tokens**\ After a successful deposit, you’ll receive pool tokens that represent your share of the pool’s liquidity. ### Key Notes * **Initial liquidity providers** set the initial price of a new pool. * **All liquidity pools follow the formula**: `x * y = k`, where `k` is constant. * **Removing liquidity**: You can withdraw your assets at any time by burning your pool tokens, receiving your share of the assets plus accumulated fees. *** > 📘 Need a full walkthrough? Check\ > [Adding Liquidity](/tutorials/adding-liquidity) l [Remove Liquidity](/tutorials/remove-liquidity) # Introduction to Soroswap Tokens Source: https://docs.soroswap.finance/getting-started/soroswap-tokens The Soroswap token list of verified Stellar assets: where it lives on GitHub, how it is used, and a dated snapshot of the main tokens. Soroswap Finance is a decentralized exchange platform that allows users to trade a wide variety of [tokens ](https://developers.stellar.org/docs/tokens/token-interface)quickly, securely, and efficiently. Our platform leverages the power of the Stellar network and the Soroban protocol to provide optimal trading routes and ensure the best rates in the market. The Soroswap [Token List](https://github.com/soroswap/token-list) is a curated collection of digital assets that have been verified and approved for use within our [AMM (Automated Market Maker) ](/concepts/amm)protocol. This list ensures that all transactions conducted through Soroswap are reliable and backed by accurate and transparent information. > [!NOTE] > > This table is a snapshot. The canonical, always-current list is > [soroswap/token-list](https://github.com/soroswap/token-list); check it before relying on > any address below. | Token Name | Issuer | Contract | | - | - | - | | [XRP by Muyu Network (XRP)](https://fchain.io/) | `GBXRPL45NPHCVMFFAYZVUVFFVKSIZ362ZXFP7I2ETNQ3QKZMFLPRDTD5` | `CAAV3AE3VKD2P4TY7LWTQMMJHIJ4WOCZ5ANCIJPC3NRSERKVXNHBU2W7` | | [Swiss Franc (XCHF)](https://kbtrading.org/) | `GDPKQ2TSNJOFSEE7XSUXPWRP27H6GFGLWD7JCHNEYYWQVGFA543EVBVT` | `CACFQEH2USDRGSBYFOVJMX4DMX7HMRYAYOV2IWR7XEX3DD3HYQEWPAAL` | | [Ethereum (ETH)](https://interstellar.exchange/) | `GCNSGHUCG5VMGLT5RIYYZSO7VQULQKAJ62QA33DBC5PPBSO57LFWVV6P` | `CAH5D34KB7BQ2XXYEUYTBEXK7UZATBP2KHVGSHODHKQXIRTE7IOKI37Q` | | [Bitcoin (BTC)](https://stellarport.io/) | `GBVOL67TMUQBGL4TZYNMY3ZQ5WGQYFPFD5VJRWXR72VA33VFNL225PL5` | `CBPMFYWP4FFV7PQUYHXJZBXS75EHR6FXYSYEZWH2UM7AUYSKI2Z3PTCG` | | [Bitcoin Cash (BCH)](https://interstellar.exchange/) | `GCNSGHUCG5VMGLT5RIYYZSO7VQULQKAJ62QA33DBC5PPBSO57LFWVV6P` | `CASVAKL4HL2UYUOHHRX6GPWEVDJJLZGD32RCKSD2ENJ7KF2BOGLFNN7G` | | [Mobius (MOBI)](https://mobius.network/) | `GA6HCMBLTZS5VYYBCATRBRZ3BZJMAFUDKYYF6AH6MVCMGWMRDNSWJPIH` | `CATHRLMZW3JUIYSXYE4YAI3SBBBQGXYAP674RINGUBQLNFTCZHMI5XZJ` | | [AQUA Token (AQUA)](https://aqua.network/) | `GBNZILSTVQZ4R7IKQDGHYGY2QXL5QOFJYQMXPKWRRM5PAV7Y4M67AQUA` | `CAUIKL3IYGMERDRUN6YSCLWVAKIFG5Q4YJHUKM4S4NJZQIA3BAS6OJPK` | | [Stellar NFT (StellarNFT)](https://stellarnft.io/) | `GALZZQF5JXHPHGI5A2ZW2EPL76DU4DWSYYGTPLOAMMEQEEECFSSRIOZJ` | `CAWSVMCGBT4YEDH4PK64TZ3IRTXDHZJ6PDSSUARFLKRG2H3MUBLTVQ6C` | | [Realio Token (RIO)](https://realio.fund/) | `GBNLJIYH34UWO5YZFA3A3HD3N76R6DOI33N4JONUOHEEYZYCAYTEJ5AK` | `CB2XLDU74PIXO5DENULX53IIC3DMKGN2UM5IBGMSSI634IAQJ7O3Z3UQ` | | [yBTC by Ultra Capital (yBTC)](https://ultracapital.xyz/) | `GBUVRNH4RW4VLHP4C5MOF46RRIRZLAVHYGX45MVSTKA2F6TMR7E7L6NW` | `CB2XMFB6BDIHFOSFB5IXHDOYV3SI3IXMNIZLPDZHC7ENDCXSBEBZAO2Y` | | [Kenya Shilling (KES)](https://connect.clickpesa.com/) | `GA2MSSZKJOU6RNL3EJKH3S5TB5CDYTFQFWRYFGUJVIN5I6AOIRTLUHTO` | `CBAANNZVSOH6I42ZVVFTVB2JNVSHVBYUDWGGJHB4BLCXARYTNN3ODU3F` | | [IXINIUM (XXA)](https://ixinium.io/) | `GC4HS4CQCZULIOTGLLPGRAAMSBDLFRR6Y7HCUQG66LNQDISXKIXXADIM` | `CBBJTE34KZHJACB2A6OXCRTDDLX4LI7GKYYD6XYJSGMX46BLNAG752HS` | | [Chilean PESO (CLPX)](https://clpx.finance/) | `GDYSPBVZHPQTYMGSYNOHRZQNLB3ZWFVQ2F7EP7YBOLRGD42XIC3QUX5G` | `CBDRPADR3KIBJNUBNRTTO4P7NO5RVPMYKRJB5YCZUZ6B66RKYK324UJY` | | [Brazilian Real (BRL)](https://ntokens.com/) | `GDVKY2GU2DRXWTBEYJJWSFXIGBZV6AZNBVVSUHEPZI54LIS6BA7DVVSP` | `CBF4E5GSTVSITE5Q2ENOTEUQJPBZAU3SBDVLQMSQ7GLBRTSYGUAT722K` | | [ETH by Ultra Capital (ETH)](https://ultracapital.xyz/) | `GBFXOHVAS43OIWNIO7XLRJAHT3BICFEIKOJLZVXNT572MISM4CMGSOCC` | `CBH4M45TQBLDPXOK6L7VYKMEJWFITBOL64BN3WDAIIDT4LNUTWTTOCKF` | | [SSLX Cassator (SSLX)](https://sslx.sl8.online/) | `GBHFGY3ZNEJWLNO4LBUKLYOCEK4V7ENEBJGPRHHX7JU47GWHBREH37UR` | `CBHBD77PWZ3AXPQVYVDBHDKEMVNOR26UZUZHWCB6QC7J5SETQPRUQAS4` | | [Bitcoin Lightning (BTCLN)](https://kbtrading.org/) | `GDPKQ2TSNJOFSEE7XSUXPWRP27H6GFGLWD7JCHNEYYWQVGFA543EVBVT` | `CBHIQPUXLFLC5O44ZJVUTCL5LMZFLVGU5DEIGSYKBSAPFMOGTKOQEPFM` | | [Bitcoin (BTC)](https://interstellar.exchange/) | `GCNSGHUCG5VMGLT5RIYYZSO7VQULQKAJ62QA33DBC5PPBSO57LFWVV6P` | `CBJ6JFEMRMSGMOT2CUH3JIKW7AOOZW3HMFNBQFEVHHCE76PWEXAQAJDB` | | [Reflector xRF (XRF)](https://reflector.network/) | `GCHI6I3X62ND5XUMWINNNKXS2HPYZWKFQBZZYBSMHJ4MIP2XJXSZTXRF` | `CBLLEW7HD2RWATVSMLAGWM4G3WCHSHDJ25ALP4DI6LULV5TU35N2CIZA` | | [Ripple (XRP)](https://stellarport.io/) | `GBVOL67TMUQBGL4TZYNMY3ZQ5WGQYFPFD5VJRWXR72VA33VFNL225PL5` | `CBPMFYWP4FFV7PQUYHXJZBXS75EHR6FXYSYEZWH2UM7AUYSKI2Z3PTCG` | | [YieldBlox (YBX)](https://script3.io/) | `GBUYYBXWCLT2MOSSHRFCKMEDFOVSCAXNIEW424GLN666OEXHAAWBDYMX` | `CBRP2VD3CZLEQIQZ4JMBXGA5AC2U6JE26YU5CCIOICIZCVWPGBO2QRUB` | | [Ripple (XRP)](https://interstellar.exchange/) | `GCNSGHUCG5VMGLT5RIYYZSO7VQULQKAJ62QA33DBC5PPBSO57LFWVV6P` | `CBSEMU4NLPURIO37O5KNATEIHGY2BTF27PBJH46H5ESRB7MREGWNMYKJ` | | [EURo Coin (EURC)](https://mykobo.co/) | `GAQRF3UGHBT6JYQZ7YSUYCIYWAF4T2SAA5237Q5LIQYJOHHFAWDXZ7NM` | `CBVDRT5474OBUEXF5MJB3UGQ5CG7CKGCAH5M4RV5NBCDJUBZ5OXHJLOU` | | [Coin Mex (MXN)](https://anchormxn.com/) | `GCKIK5F6J4KMKF6MKB5EM67S5CK557EZQ3IAMZM5FFAYST63S3HWXVPE` | `CBVJZDNMOPEEPED3OBY4VLQPQKU4IOCKWACDKXPBO4E6TQI432W66BE5` | | [Lumenswap asset (LSP)](https://lumenswap.io/) | `GAB7STHVD5BDH3EEYXPI3OM7PCS4V443PYB5FNT6CFGJVPDLMKDM24WK` | `CBXE6V454EUYWVQCI4TCSOG4CSNPQ2BLYOTKAKXYFHO3KNVX4CXYCY2T` | | [NGNC Coin (NGNC)](https://ngnc.online/) | `GASBV6W7GGED66MXEVC7YZHTWWYMSVYEY35USF2HJZBLABLYIFQGXZY6` | `CBYFV4W2LTMXYZ3XWFX5BK2BY255DU2DSXNAE4FJ5A5VYUWGIBJDOIGG` | | [Zioncoin (Zi)](https://zioncoin.org.uk/) | `GDBNNE67F54PTUZTCTOQYT5CQZFXA2AX6O5DCA5BVR653OP6KCWGG2Z7` | `CBZCBYTP3TFCT7PCDRPJDQK7UY2R3QA4H6MF2P6XDS5CZNXLPF6S6OPB` | | [yXLM by Ultra Capital (yXLM)](https://ultracapital.xyz/) | `GARDNV3Q7YGT4AKSDF25LT32YSCCW4EV22Y2TV3I2PU2MMXJTEDL5T55` | `CBZVSNVB55ANF24QVJL2KJ2F2VC3YHYB5K6FZVDPYZZU6JTA7R7JLE5` | # Wallet Setup and Connection Source: https://docs.soroswap.finance/getting-started/wallet-setup Choosing a Stellar wallet that works with Soroban and Soroswap (Freighter, xBull, LOBSTR, Hana, Albedo and others), choosing mainnet or testnet, and connecting it. ### What to Look For in a Wallet * **Stellar asset support**: Must handle native XLM and custom tokens (stablecoins, NFTs, etc.). * **Clean UI**: Easy to use on browser, desktop, or mobile. * **Non-custodial**: You retain full control of your private keys. * **Backup & recovery options**: For secure account restoration. * **dApp compatibility**: Must work with Soroban-based contracts. * **Multi-account support**: Manage multiple addresses from a single interface. ### ✅ Compatible Wallets with Soroswap.Finance These wallets are fully integrated via [`stellar-wallets-kit`](https://github.com/paltalabs/soroban-react-stellar-wallets-kit) and work seamlessly with the Soroswap app. | Wallet | Type | Best for | Key Features | | - | - | - | - | | [**Freighter**](https://www.freighter.app/) | Browser extension, web | Web users & dApps | Fast, non-custodial, multi-account support | | [**xBull**](https://xbull.app/) | Web, mobile, extension | Advanced users, traders | Multi-asset, built-in swap, open source | | [**Hana Wallet**](https://www.hanawallet.io/) | Mobile, web | Beginners & privacy-first | Simple UI, multi-chain, Stellar NFTs | | [**Lobstr**](https://lobstr.co/) | Mobile, web | Everyday use | Friendly UI, social payments, widely used | | [**Rabet**](https://rabet.io/download) | Extension, desktop, mobile | Desktop-first new users | Lightweight, intuitive, good for dApp interaction | | [**Albedo**](https://albedo.link/) | Web (redirect) | Quick signing | No install needed, fast transaction authorization | | [**HOT Wallet**](https://hot-labs.org/) | Telegram mini app | Multi-chain web3 users | Non-custodial, \$HOT token mining, EVM support | > **Quick Recommendations**\ - Using Soroswap in the browser? → Go with **Freighter**, **Rabet**, or **xBull**\ - Prefer mobile? → Try **Lobstr**, **Hana**, or **HOT Wallet**\ - Need fast signing with no setup? → Use **Albedo** ### 🌐 Connecting to the Stellar Network Connecting your wallet to Stellar is required to interact with tokens, perform swaps, and use Soroban dApps. ### [Choosing the Network](https://developers.stellar.org/docs/learn/fundamentals/networks) * **Mainnet**: For real transactions and assets.\ Passphrase: `Public Global Stellar Network ; September 2015` * **Testnet**: For development and testing (used with Soroswap test version).\ Passphrase: `Test SDF Network ; December 2014` > To learn how to use [Soroswap Testnet Overview](/tutorials/soroswap-testnet-overview). ### Configuring Your Wallet * **Select network**: Most wallets allow switching between Mainnet and Testnet in settings. * **Enter** [**passphrase**](https://developers.stellar.org/docs/networks): Required by some wallets to connect correctly. * **(Optional)** [**Set custom Horizon node**](https://developers.stellar.org/docs/data/horizon/horizon-providers#ecosystem-horizon-providers): Advanced users can configure a custom Horizon URL (e.g., ANKR node). # The Swap Aggregator for Stellar Source: https://docs.soroswap.finance/index The Soroswap Aggregator API at https://api.soroswap.finance quotes Soroswap, Aquarius, SushiSwap V3, Comet and SDEX, splits each trade for the best price and returns a transaction to sign. Bearer sk_ key; the free plan allows 1 request/s. One API call gets your users the best price on Stellar. The Soroswap Aggregator API quotes Soroswap, Aquarius, SushiSwap V3, Comet and the Stellar classic DEX (SDEX), splits the trade across them when that pays, and returns a route your app can execute right away. > **[Get an API key](https://api.soroswap.finance/register)** > > Register and generate a key in a minute. The free plan needs no approval. > **[Quickstart](/api/quickstart)** > > Your first quote and swap on mainnet in five minutes. > **[API Reference](/api-reference/quote/quote)** > > Every endpoint, request and response. > **[Monetize your flow](/api/fees)** > > Set a partner fee on your swaps and get your share in USDC every month. ## What you get * **Best execution across Stellar.** Every quote prices the Soroban venues and, when you ask for it, the classic order book. A single trade can split across several pools and hops. See [supported venues](/aggregator/supported-amms). * **Gasless trustline.** A user who has never held the output asset, and has no XLM to open a trustline, can still receive it in one swap: a sponsor account you control pays for the trustline. See [Gasless Trustline](/api/gasless-trustline). * **Revenue on every swap.** Configure a partner fee once on your account. It is collected on-chain and settled to you in USDC each month, with a statement per token. See [Fees and revenue share](/api/fees). * **Two ways to execute.** Let the API build the transaction for your user to sign, or take the route and execute it from your own Soroban contract. See [Quickstart](/api/quickstart). ## How to start 1. [Register](https://api.soroswap.finance/register) and generate an API key (it starts with `sk_`). 2. Call `POST /quote` with the two assets, the amount and the venues you want in `protocols`. 3. Call `POST /quote/build` to get the transaction, have your user sign it, and submit it with `POST /send`. ```bash theme={null} curl -X POST "https://api.soroswap.finance/quote?network=mainnet" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "assetIn": "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA", "assetOut": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", "amount": "100000000", "tradeType": "EXACT_IN", "protocols": ["soroswap", "aqua", "sushi", "comet", "sdex"] }' ``` The base URL is `https://api.soroswap.finance` for both mainnet and testnet; pick the network with `?network=`. Request limits depend on your plan, see [Plans and rate limits](/api/plans). ## Building on the AMM instead If you provide liquidity, or your contract needs to trade directly against Soroswap pools, the [Soroswap AMM](/amm) section documents the pair, factory and router contracts. ## Support Developer questions go to [dev@paltalabs.io](mailto:dev@paltalabs.io) or the developer channels on [Discord](https://discord.gg/A2rQ3cm9XG). For a large integration or a commercial agreement, see [Business Partnerships](/resources/partnerships/business-partnerships). ## Video walkthrough [Soroswap API walkthrough](https://www.youtube.com/embed/5KtXqx2I0A0) # About Us Source: https://docs.soroswap.finance/resources/about-us Soroswap's mission, core values, and community channels, built on Stellar by PaltaLabs. [**Soroswap**](https://soroswap.finance) is the first decentralized exchange (DEX) and exchange aggregator built on the [Stellar ](https://stellar.org)blockchain, developed by [**PaltaLabs**](https://paltalabs.io).\ Our mission is to unlock financial access by providing a powerful, easy-to-integrate platform for seamless token swaps, liquidity provision, and global DeFi participation. At Soroswap, we believe in: * **Innovation:** Combining smart routing and Stellar’s speed to deliver the best trading experience. * **Accessibility:** Empowering anyone with a Stellar wallet to trade directly, without intermediaries. * **Transparency:** Building open-source technology and maintaining full transparency with our community. * **Decentralization:** Enabling permissionless finance through smart contracts and decentralized infrastructure. Our team, powered by PaltaLabs, consists of passionate blockchain developers, builders, and innovators committed to shaping the future of decentralized finance on Stellar. ### 🤝 Soroswap Community Join our communities to get help from other users, stay updated, and participate in events:\ \ 🔹 [Launch App](https://app.soroswap.finance) 🔹[**Official Discord**](https://discord.gg/A2rQ3cm9XG) 🔹[**Twitter (X)**](https://x.com/SoroswapFinance) # Additional Resources Source: https://docs.soroswap.finance/resources/additional-resources Official Soroswap links: website, analytics, GitHub, Discord, Twitter, LinkedIn, YouTube, and blogs. ### :globe\_with\_meridians: **Official Channels** * Official Website: [https://soroswap.finance/](https://soroswap.finance/) * Soroswap Info: [https://dune.com/paltalabs/soroswap](https://dune.com/paltalabs/soroswap) * GitHub Repository: [https://github.com/soroswap/](https://github.com/soroswap/) * Discord : [https://discord.gg/A2rQ3cm9XG](https://discord.gg/A2rQ3cm9XG) * Twitter: [https://x.com/SoroswapFinance](https://x.com/SoroswapFinance) * LinkedIn: [https://www.linkedin.com/company/soroswap/](https://www.linkedin.com/company/soroswap/) * YouTube: [https://www.youtube.com/@Soroswap.Finance](https://www.youtube.com/@Soroswap.Finance) * Blog: [https://dev.to/soroswap](https://dev.to/soroswap) * Medium: [https://medium.com/soroswap](https://medium.com/soroswap) # General FAQ Source: https://docs.soroswap.finance/resources/general-faq Common questions about Soroswap, including what the protocol is and how to connect a wallet. **What is Soroswap.Finance?**\ Soroswap.Finance is the swap aggregator for Stellar. Its [Aggregator API](/) quotes every Stellar venue and returns the best route, and the [Soroswap AMM](/amm) provides permissionless token swaps and liquidity provision without intermediaries. **How do I connect my wallet?**\ Go to [app.soroswap.finance](https://app.soroswap.finance), then select and approve your Stellar/Soroban-compatible wallet (e.g., Freighter, xBull, Lobstr). For additional information and resources, visit:\ [Website](https://www.soroswap.finance) | [GitHub](https://github.com/soroswap) | [Twitter](https://x.com/SoroswapFinance) | [Documentation](/) # Support & Resources Source: https://docs.soroswap.finance/resources/index Where to get help with Soroswap: support, the FAQ, the team, partnerships, and the official links and community channels. ### 🛠️ Support Center Having an issue or need personalized assistance?\ Join our **Discord** and head to the **support section** — our team will be ready to assist you. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2025-04-28-a-las-20-28-00.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=6ad04d71e899f1fb7724aa600ae53c1f) ### 🤝 Soroswap Community Join our communities to get help from other users, stay updated, and participate in events: * [**Official Discord** ](https://discord.gg/A2rQ3cm9XG)— Technical support channels, updates, and general discussions. * [**Twitter (X)**](https://x.com/SoroswapFinance) — Latest news, announcements, and events. # Business Partnerships Source: https://docs.soroswap.finance/resources/partnerships/business-partnerships How to reach the Soroswap team about integrations, listings and commercial work. Soroswap is built and maintained by [PaltaLabs](/resources/about-us). If you are building a wallet, an exchange or another product that wants to route trades through Soroswap, or you want to discuss a listing or a joint integration, get in touch: > **[Book a call](https://paltalabs.io/esteban/cal)** > > For an integration agreement, Enterprise limits or a commercial conversation. > **[Email](mailto:dev@paltalabs.io)** > > [dev@paltalabs.io](mailto:dev@paltalabs.io) > **[Discord](https://discord.gg/A2rQ3cm9XG)** > > Ask in the developer channels. If what you need is the swap API rather than a commercial conversation, start at the [Soroswap Aggregator API](/) instead. It is self-serve: [register](https://api.soroswap.finance/register) and you get a key. # Partnerships Source: https://docs.soroswap.finance/resources/partnerships/index Who Soroswap works with, starting with the indexers behind its data. Soroswap first started collaborating with two indexers, Mercury and SubQuery, which supply the on-chain data the app and the API read from. > **[Mercury and SubQuery](/resources/partnerships/mercury-subquery)** > > Why an AMM needs an indexer, and what these two do for Soroswap. > **[Business Partnerships](/resources/partnerships/business-partnerships)** > > Working with Soroswap commercially. # Collaboration with Mercury and SubQuery Source: https://docs.soroswap.finance/resources/partnerships/mercury-subquery Soroswap's work with the Mercury and SubQuery indexers, with setup steps for both. This article outlines the outcomes and walkthroughs of our collaborative projects with Mercury and SubQuery indexers. We aim to achieve two primary objectives: 1. Introduce developers from the Stellar and Soroban ecosystems to the capabilities of indexer services. 2. Assist indexers in refining their offerings by reporting bugs and providing documentation feedback we've uncovered through our usage. ## The Importance of Indexers in Blockchain Ecosystems Utilizing indexers is crucial for efficient data retrieval, thereby enabling developers to offer a seamless user experience. Although blockchains inherently ensure transactional integrity, extracting an entire history directly from a node is computationally intensive. Therefore, indexers serve as invaluable tools. This article serves two main goals: * Demonstrate practical uses of the indexer as an example or initial repository. * Test various indexer solutions, offering constructive feedback on bugs and documentation. ## Mercury: Subscription-Based Indexing Solutions Mercury offers a subscription-based indexing service equipped with numerous features such as alert systems and code execution environments. As a fully managed service, Mercury obviates the need for developers to manage any GraphQL service themselves. In our repository, you'll find a Node.js client pre-configured with queries to inspect a specific contract, specified through the repository's environment variables. ### Getting Started with Mercury **Prerequisites**: Docker installation on your machine 1. Clone the repository: `https://github.com/paltalabs/mercury-client.git` 2. Duplicate `.env.example` as `.env` 3. Request Mercury access [here](https://developers.mercurydata.app/requesting-access) 4. Populate the `MERCURY_ACCESS_TOKEN` in `.env`. For usage exceeding 7 days, also complete `MERCURY_TESTER_EMAIL` and `MERCURY_TESTER_PASSWORD`. 5. Update `CONTRACT_ADDRESS` if you are interested in a contract other than the Soroswap's Factory contract. 6. Run the container: `bash run.sh` 7. Install dependencies: `yarn` 8. Use predefined scripts to interact with the contract: ``` node scripts/subscribeToEntries.js node scripts/getAllEntriesForContract.js node scripts/getEntrySubscriptions.js node scripts/getEventSubscriptions.js ``` ## SubQuery: Versatile Indexing for Multiple Platforms SubQuery is a comprehensive indexer that interfaces seamlessly with multiple blockchain platforms, including Stellar and Soroban. We've developed a customized SubQuery project capable of IPFS deployment and management through SubQuery's Managed Service. However, storing blockchain history requires access to an archive node, which adds an operational dependency: you either run one yourself or rely on a provider that does. This is the main trade-off to weigh against a fully managed indexing service. ### Getting Started with SubQuery **Requirements**: Docker Our setup incorporates Docker inside a Node.js image to manage the required Node.js version and to prevent the need for a global `subql` package installation. If you prefer to install node, docker and subql locally you can start from step 4. 1. Run the container: `bash run.sh` 2. Install Docker and SubQuery: ``` bash install_docker.sh bash setup_subquery.sh ``` 3. Navigate to `subql-starter` directory and execute: ``` yarn dockerd & ``` 4. In another terminal ``` bash connect.sh ``` and (if you prefer to install node, docker and subql locally you can start from here) ``` cd subql-starter yarn codegen yarn build yarn start:docker ``` 5. Begin querying by navigating to `http://localhost:3000/graphql` 6. To publish your project to IPFS, run: ``` cd subql-starter subql publish ``` ### Conclusion We've demonstrated how to leverage both Mercury and SubQuery indexers, reporting bugs and providing documentation feedback along the way. Each service offers unique approaches to solving data availability issues. While SubQuery provides a more mature and UI-rich solution, its requirement for an archive node adds complexity. Mercury, however, simplifies the process by only requiring subscriptions and queries, thereby eliminating the need to manage additional infrastructure. # Adding Liquidity Source: https://docs.soroswap.finance/tutorials/adding-liquidity Step by step walkthrough for supplying liquidity to a Soroswap pool, including minting test tokens on testnet. Now that we have our wallet set up correctly in the testnet, we are ready to use the [Soroswap app](https://app.soroswap.finance). Let's start by adding liquidity to a pool. ## Step 1: Setup > [!NOTE] > > This setup step is testnet only. Minting test tokens and funding with Friendbot exist so you can practice without real funds. On mainnet you fund your wallet with real XLM and acquire real assets instead, and then continue from Step 2. To begin adding liquidity on testnet, we need to first mint test tokens to interact with the app. 1. Go to the Balances page. 2. Click the "Mint test tokens" button. The screenshot below is the testnet Balances page, which is where the minting button lives: ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/adding-liquidity/balances.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=99bb55178afccdd05d51955889120f16) After minting your test tokens, ensure that your wallet is funded with at least 1XLM in Stellar. To fund your wallet in the testnet, you can use the friendbot. Simply go to your wallet and click "Fund with Friendbot". ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/adding-liquidity/fund.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=38fa366a64964f6a85ff25160ca0644b) Verify that your account has been funded with XLM, and now we are ready to continue navigating the app. ## Step 2: Go to the Add Liquidity page 1. Navigate to the Liquidity page, where you will find all the liquidity positions. 2. Click the "Add Liquidity" button. This will take you to a page like this: ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/adding-liquidity/addliquidity.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=309e06654cd7406a8af5621b42e71b03) ## Step 3: Select the tokens and provide liquidity 1. Click the token selectors. 2. Select the tokens for which you would like to provide liquidity. 3. Enter the amount you want to provide. 4. Click "Supply". This will open a modal where you can view information about the liquidity position, such as the amount of Liquidity Pool Tokens that will be created and your share percentage of the pool. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/adding-liquidity/addliquiditymodal.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=aaf769c5859ae46753be0a48e803d25b) 5. Click the "Add liquidity" button, and you will see a transaction in your Freighter wallet. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/adding-liquidity/transaction.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=0de97a026be8f98b74f9196ac73617e9) 6. Click "Approve" and wait for the transaction to be confirmed. 7. Once the transaction is complete, you will see a success message like this: ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/adding-liquidity/success.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=6248bd9847316febf1d9dc8eeedcc098) Congratulations! You have successfully added liquidity. Now you can view your liquidity position on the Liquidity page. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/adding-liquidity/liquiditypage.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=8061ba7b3330e71f58a4aca3104c7efe) Now we can continue with the tutorial. The next steps will involve swapping tokens for the ones you have provided liquidity for. # Conclusions Source: https://docs.soroswap.finance/tutorials/conclusions Summary of the Soroswap guide: interface, adding liquidity, swapping tokens, removing liquidity, and where to go next. In this guide, we have explored the key features and benefits of using Soroswap. Soroswap is a decentralized exchange (DEX) on Stellar that allows users to trade and provide liquidity for various tokens in a user-friendly and intuitive manner. We walked through the flow on testnet, where you can experiment freely without risking real funds. The same interface and the same steps apply on mainnet, where Soroswap is live today. Throughout this guide, we have covered the following topics: 1. **User-Friendly Interface:** Soroswap offers a user-friendly interface that makes it easy to navigate and interact with the platform. The intuitive design ensures a smooth experience for users. 2. **Adding Liquidity:** Users can contribute to the stability and efficiency of the Soroswap ecosystem by adding liquidity to the pools. This allows for efficient trading and transactions on the platform. 3. **Performing Swaps:** Soroswap enables users to exchange one token for another within the liquidity pool. This feature provides flexibility and diversification options for users. 4. **Removing Liquidity:** Users can manage their liquidity positions by removing liquidity from the pools. This allows users to effectively control their positions and receive tokens in return. The testnet remains a valuable learning tool: it is a safe environment to rehearse a swap or a liquidity position, try a strategy, and get comfortable with the mechanics before you do the same thing with real funds. ## Where to go next * **Use the live app:** head to [app.soroswap.finance](https://app.soroswap.finance), connect your wallet, and switch your wallet to Mainnet when you are ready to trade with real funds. * **Build on top of Soroswap:** if you want to integrate swaps, quotes, or liquidity into your own product, start with the [Soroswap API](/). We hope this guide has given you a comprehensive overview of Soroswap. Now it's time for you to dive in, explore the platform, and experience the exciting world of decentralized finance. Happy trading! # Doing Swap Source: https://docs.soroswap.finance/tutorials/doing-swap Performing a token swap on Soroswap, covering wallet connection, trustlines, confirmation, and best practices. Now that you have successfully added liquidity to a pool, let's move on to the next step: doing swaps. Swapping tokens on Soroswap allows you to exchange one token for another within the liquidity pool. Follow the steps below to perform a swap: > [!NOTE] > > The screenshots on this page were captured in 2024. The steps still describe the flow, but the interface may look different from what you see today. ## Step 1: Access Soroswap Open the Soroswap app at [app.soroswap.finance](https://app.soroswap.finance). This takes you straight to the swap interface. ## Step 2: Connect Your Wallet to Soroswap.Finance 1. **Choose a Stellar Wallet:** Select a wallet compatible with the Stellar network. For assistance, refer to this article: [Guide to Choosing a Stellar Wallet](https://dev.to/soroswap/guide-to-choosing-wallet-for-the-stellar-network-53k1). ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-18-21-42.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=8ea8e68baa759b5e8a4b57e516b6b8e0) 2. **Acquire Stellar (XLM):** Purchase XLM from a reputable exchange or transfer it from another wallet if you already possess some. 3. **Deposit XLM into Your Wallet:** Transfer the XLM to your Stellar wallet address. > If this is your first time, ensure you acquire and transfer Stellar (XLM) to your wallet. ## Step 3: Navigate the Soroswap Finance Interface Once your Stellar wallet is connected, familiarize yourself with the Soroswap dashboard. It displays your account details, token balances, and options for swapping tokens and managing your assets. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-18-44-15.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=aee00be8d8caa7ab30f8f0df801a43c4) ## Step 4: Execute a Token Swap 1. **Select Tokens:** Choose the tokens you wish to swap from and to. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-18-28-46-1.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=d69644be99633dae1a0444a053041a20) 2. **Enter Amount:** Specify the amount of tokens you want to exchange. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-18-51-21.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=a244d15be824ed1207a628e077f90b23) ## Step 5: Trustlines When swapping a new token for the first time on Soroswap.Finance, you’ll need to sign a "trustline" transaction. Trustlines authorize your account to hold and exchange the specific asset. 1. **Transaction Request:** A transaction request will be sent to authorize your wallet to hold and trade the new token. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-18-30-06.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=f67edb57174bb67e7a59214331584856) 2. **Sign the Trustline Transaction:** Sign the transaction to authorize the trustline, allowing your wallet to manage the new asset. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-19-16-39.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=93e7e87b85214fb416f3ac19e2896a6d) For more information, refer to the official documentation on [trustlines](/concepts/trustlines). ## Step 6: Confirm the transaction 1. **Sign the Transaction:** Your wallet will display the transaction details. Review and approve it to confirm the swap. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-16-20-22.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=17ed4a518db702e5c3636353d7c2cb1a) Congratulations! You have successfully completed a swap on Soroswap.Finance Now.\\ ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-21-01-45.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=00566c6e3525ee813c2a33465ec92ca2) ## Step 7: Check That Your Transaction Went Through After signing the transaction, it's important to confirm that everything went smoothly. To do this, check the details in your wallet or by using one of the blockchain explorers. This step ensures that the token swap has been completed and that the new tokens are now visible in your balance. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/captura-de-pantalla-2024-09-18-a-las-21-58-54.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=ce56954918eb1efba320f326ef9febd3) Once the transaction is confirmed, you’ll see a success message indicating the swap is complete. At this point, you can review the transaction details by selecting one of these options: * [**View in Stellar.Expert**](https://stellar.expert/explorer) * [**View in StellarChain**](https://stellarchain.io/) ### **Best Practices** 1. **Sufficient XLM for Fees:** Ensure your wallet contains enough XLM to cover transaction fees. 2. **Review Trustlines:** Confirm that all necessary trustlines are established before initiating a swap. 3. **Check Transaction History:** Verify in your wallet that the swap is complete and that the new tokens are reflected in your balance. # Tutorials Source: https://docs.soroswap.finance/tutorials/index Step-by-step walkthroughs of the Soroswap app: wallet setup, swapping, providing liquidity and working with Stellar Classic assets. Hands-on walkthroughs of the Soroswap interface, in the order most people need them. > **[Soroswap Testnet Overview](/tutorials/soroswap-testnet-overview)** > > What the testnet is for and how to get funded before you start. > **[Installing Freighter](/tutorials/installing-freighter)** > > Set up the browser wallet and point it at the right network. > **[Soroswap Sections](/tutorials/soroswap-sections)** > > A tour of the app: swap, liquidity, balances. > **[Adding Liquidity](/tutorials/adding-liquidity)** > > Deposit a token pair and receive pool tokens. > **[Doing a Swap](/tutorials/doing-swap)** > > Execute your first trade, end to end. > **[Removing Liquidity](/tutorials/remove-liquidity)** > > Redeem pool tokens for your share of the pair. > **[Stellar Classic Assets](/tutorials/stellar-classic-assets)** > > Wrap, swap and test Stellar Classic assets on Soroban. # Installing Freighter Source: https://docs.soroswap.finance/tutorials/installing-freighter Installing the Freighter browser wallet, securing the recovery phrase, and choosing which Stellar network to use. In this section, we will guide you through the installation process of Freighter, a non-custodial Stellar wallet extension that will empower you to engage with Soroswap.Finance ## Step 1: Installing Freighter on your Browser Freighter works as a Extension in Chrome (Google Chrome, Brave) and Firefox: 1. Visit the [Freighter Official Website](https://www.freighter.app/), to install Freighter into your browser. 2. Click on "Create Account" and choose a secure password. ## Step 2: Safeguarding Your Wallet Upon creating your account, you will be presented with a secret recovery phrase. This phrase acts as a backup for your wallet's private key, allowing you to restore your wallet in case of loss, theft, or device damage. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/installing-freighter/phrase.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=6a86fddd7b6fab38b7b0f8dfe24c6dfd) Follow these important guidelines to protect your funds: * **Save the Seed Phrase**: Keep your seed phrase in a safe place. Treat it as a valuable key to your funds and store it confidentially. * **Secure Storage**: Store the seed phrase in a secure location, such as a locked drawer or a password-protected digital storage solution. Ensure it is shielded from unauthorized access and potential loss. * **Exercise Caution**: Remember, anyone with access to your seed phrase can gain control over your wallet and its contents. Take the necessary precautions to safeguard it diligently. ## Step 3: Confirming Your Seed Phrase To ensure accuracy, you will be asked to confirm your seed phrase in the same order. This step guarantees that you have correctly noted down your recovery phrase. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/installing-freighter/confirm-phrase.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=8f93926c26ba41cb18e70eebc0a8986a) ## Step 4: Accessing Your Wallet Congratulations! You are now ready to use your Freighter wallet. Follow these final steps: 1. Open the Freighter extension in your browser. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/installing-freighter/account.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=b5dbb413e53ab911b06ac84accd138d0) 2. Your account will be displayed, labeled as "Account 1," along with your unique account address. This address serves as an identifier for receiving funds or transactions from other users or entities on the blockchain network. ## Step 5: Choosing a Network Soroban is the smart contract environment of Stellar, not a separate network, so there is nothing extra to enable in Freighter. All you need to do is pick which network your wallet connects to. 1. Open Freighter's network selector and choose a network: * **Mainnet**: the live Stellar network, where transactions move real funds. * **Testnet**: a test network with no real value, meant for experimenting. 2. Soroswap is live on Mainnet. This tutorial uses Testnet so you can explore every feature without risking real funds. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/installing-freighter/testnet.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=28eca736eb24ea1a9d54ee0b72632633) ## Step 6: Let's Dive into the Tutorial Congratulations! You are now fully equipped to embark on an exciting journey with Soroswap on the testnet. Get ready to explore and make the most of Soroswap's comprehensive range of features within this secure testing environment. Let's dive right into the tutorial and unlock the full potential of your Soroswap experience! # Remove Liquidity Source: https://docs.soroswap.finance/tutorials/remove-liquidity Removing liquidity from a Soroswap pool, from selecting a position to confirming the transaction. Now that we have learned how to add liquidity and perform swaps on Soroswap, let's explore how to remove liquidity from our liquidity positions. ## Step 1: Navigate to the Liquidity page 1. Go to the Soroswap app and click on the "Liquidity" tab in the navigation menu. ![Liquidity page](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/remove-liquidity/liquidity1.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=c18d91ead0fd079806420c9615e1e39f) ## Step 2: Select the liquidity position 1. Find the liquidity position from which you want to remove liquidity. 2. Click on the liquidity position to view its details. ![Select liquidity position](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/remove-liquidity/liquidity2.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=e645371023cc2e3701604df8fb6a6bdd) You will see a modal displaying the liquidity pool information, balances, and your share of the pool. 3. Click the "Remove" button to proceed to the remove liquidity page. ## Step 3: Remove liquidity 1. In the "Remove Liquidity" section, use the slider or enter the amount of liquidity you would like to remove. ![Remove liquidity](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/remove-liquidity/liquidity3.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=5dae3dd7d934d13dcc052f97935627d3) 2. Click the "Remove" button. ![Confirm removal](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/remove-liquidity/liquidity4.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=046a72518defc0e9c98470c02f418360) 3. Review the details of the removal, including the amount of tokens you will receive. 4. If everything looks correct, click the "Confirm" button. ## Step 4: Confirm the transaction 1. Your Freighter wallet will open, displaying the transaction details. 2. Review the transaction information and click "Approve" to confirm the removal. 3. Wait for the transaction to be confirmed on the blockchain. ## Step 5: View the transaction details 1. Once the transaction is confirmed, you will see a success message indicating that the liquidity removal was completed. 2. You can also view the transaction details with the transaction hash or by clicking "View on explorer". Congratulations! You have successfully removed liquidity from a pool on Soroswap. You can now explore other liquidity positions or continue to engage with the exciting world of decentralized finance. # Soroswap sections Source: https://docs.soroswap.finance/tutorials/soroswap-sections Tour of the Soroswap interface: the Balance, Swap, and Liquidity sections and what each does. Soroswap offers a user-friendly interface with various sections to enhance your crypto experience. Let's explore the key sections available: ## Balance Section In the Balance section, you can conveniently view your token balances. This feature allows you to keep track of your assets and monitor their availability for transactions within the app. > [!NOTE] > > When you are connected to testnet, this section also lets you mint test tokens so you can interact with the app without real funds. That minting button is a testnet only affordance, it is not available on mainnet. The screenshot below shows the Balance section on testnet, with the minting option present: ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/soroswap-sections/balance.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=63ae6735d443b3c2d3343858077c430f) ## Swap Section The Swap section is where the magic happens! Here, you can easily swap tokens that are part of the liquidity pool. Whether you're looking to exchange one token for another or simply diversify your portfolio, the Swap section provides a straightforward and efficient solution. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/soroswap-sections/swap.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=7a75c04f29c381a52dc063469c8b987a) ## Liquidity Section The Liquidity section empowers you to add or remove liquidity from the pools. By participating in liquidity provision, you contribute to the stability and efficiency of the Soroswap ecosystem. This section allows you to manage your liquidity positions effectively, ensuring a smooth and rewarding experience. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/soroswap-sections/liquidity.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=406da195c50f0a74d7a0553d3f6f7229) **Select a liquidity pool:** In this section, you can choose a specific liquidity pool to view its information and take further actions such as adding or removing liquidity. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/soroswap-sections/liquidity2.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=da57b0c1f9e81e85c88630e5a4896ea6) **Adding liquidity:** By accessing the "Adding liquidity" section, you can contribute to a liquidity pool by depositing values of two different tokens. This process helps maintain the balance and liquidity within the pool, allowing for efficient trading and transactions. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/soroswap-sections/addliquidity.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=725fcb32d48d550acff9b11fe579285b) **Removing liquidity:** In the "Removing liquidity" section, you have the option to withdraw your share of liquidity from a specific pool. This action allows you to reclaim your tokens and adjust your liquidity positions as needed. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/soroswap-sections/removeliquidity.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=1b33ee6348705666362cb3d60d7dd399) With these three essential sections, Soroswap offers a comprehensive platform for you to explore, trade, and engage with the world of cryptocurrencies. Now that we have familiarized ourselves with these sections, let's continue with the tutorial, where we will delve into each section in more detail. Get ready to dive deeper into Soroswap and discover the exciting features it has to offer! # Soroswap Testnet Overview Source: https://docs.soroswap.finance/tutorials/soroswap-testnet-overview Overview of Soroswap as an AMM on Stellar, its main sections and benefits, and what you can try on testnet. Soroswap is an Automated Market Maker (AMM) built on Soroban, designed to facilitate token trading and liquidity provision. With its user-friendly interface, Soroswap offers a seamless and intuitive experience for users to engage with decentralized finance (DeFi) on the Soroban and Stellar ## User-Friendly Interface Soroswap boasts a user-friendly interface that ensures a smooth and enjoyable trading experience. The intuitive design caters to both beginners and experienced traders, making it easy to navigate the platform and interact with its features. ## Key Features and Benefits **Soroswap is live on Stellar mainnet, and it also runs on testnet, where you can try everything without risking real funds.** In both environments it provides the same key features and benefits: * **Balance Section:** Conveniently view your token balances in the Balance section. This feature allows you to keep track of your assets and monitor their availability for transactions within the app. * **Swap Section:** Easily exchange one token for another within the liquidity pool using Soroswap's Swap section. This feature provides a straightforward and efficient solution for diversifying your portfolio or making specific token swaps. * **Liquidity Section:** Empowers users to add or remove liquidity from the pools. By participating in liquidity provision, you contribute to the stability and efficiency of the Soroswap ecosystem. Effectively manage your liquidity positions in the Liquidity section for a smooth and rewarding experience. Soroswap on testnet allows users to fully explore and test the platform using test tokens. This enables you to experience the complete functionality of the app without risking real funds. It provides an opportunity to familiarize yourself with the features, understand the process of adding liquidity, performing swaps, and removing liquidity. Test tokens come from the faucet at [faucet.soroswap.finance](https://faucet.soroswap.finance), and testnet XLM from [Friendbot](https://friendbot.stellar.org). By utilizing Soroswap on the testnet, you can gain hands-on experience with decentralized exchanges and DeFi concepts. It serves as a valuable learning tool and a safe environment to experiment with different trading strategies and liquidity provision. In the following sections, we will provide step-by-step instructions on how to use Soroswap on the testnet, including installing the Freighter wallet, adding liquidity, performing swaps, and removing liquidity. These instructions will help you navigate the platform and make the most of the features and benefits offered by Soroswap. Let's dive in! Did you like it? Please give us feedback. Join our [Discord channel](https://discord.gg/A2rQ3cm9XG). # Importing Testnet Tokens Source: https://docs.soroswap.finance/tutorials/stellar-classic-assets/importing-testnet-tokens Breakdown of the Bash script that imports and wraps Stellar Classic testnet assets for Soroban. ### Tutorial: Importing Testnet Tokens for Use in Soroban > [!NOTE] > > The Stellar CLI used to be called `soroban`. Commands on this page use the current `stellar` name, so if you are reading an older copy of the script you may see `soroban lab token id` where this page shows `stellar contract id asset`. They do the same thing. #### Introduction This tutorial will guide you through the process of importing testnet tokens, specifically Stellar Classic assets, and wrapping them for use with Soroban. We will use a Bash script to automate this process. #### Prerequisites * Basic understanding of blockchain concepts and the Stellar network. * A system with Bash shell. * The `jq` tool installed for JSON processing. * The [Stellar CLI](https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli) installed. #### Overview of the Script The provided Bash script performs several key functions: * Reading and merging asset lists from JSON files. * Wrapping each asset to be compatible with Soroban. * Updating a token list with new Soroban addresses. you can find this script here [setup\_stellar\_classic\_assets.sh](https://github.com/soroswap/core/blob/main/scripts/setup_stellar_classic_assets.sh) #### Script Breakdown 1. **Setting Environment Variables and Directories** ```bash theme={null} ASSETS_DIRECTORY="/path/to/known_stellar_classic_assets.json" GENERATED_STELLAR_ASSETS="/path/to/generated_stellar_assets.json" ``` * `ASSETS_DIRECTORY` and `GENERATED_STELLAR_ASSETS` point to the JSON files containing asset information. * `known_stellar_classic_assets.json` example ```json theme={null} { "tokens": [ { "symbol": "USDC", "name": "centre.ioUSDCoin", "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", "asset": "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" } ] } ``` 2. **Reading and Merging Asset Lists** ```bash theme={null} CLASSIC_ASSETS_JSON=$(jq '.tokens' "$ASSETS_DIRECTORY") GENERATED_ASSETS_JSON=$(jq '.tokens' "$GENERATED_STELLAR_ASSETS") ``` These commands extract the token lists from JSON files and merge them. 3. **Wrapping Assets** ```bash theme={null} TOKEN_ADDRESS=$(stellar contract id asset --network "$NETWORK" --asset "$ASSET") ``` This is the critical line where each asset is wrapped. The `stellar contract id asset` command deploys the built in Stellar Asset Contract for the asset and returns its contract address, which is how Soroban contracts reach that same asset. 4. **Looping Through Assets** The script loops through each asset in the merged list, wrapping them and updating the token list with their new Soroban addresses. 5. **Updating the Token List** The updated token addresses are saved back to the `TOKENS_DIRECTORY`, ensuring that the assets are now usable in Soroban. 6. **Error Handling and Outputs** The script includes checks to handle errors and provide informative output messages throughout the process. #### Running the Script 1. Ensure you have the necessary JSON files in the specified directories. 2. Run the script in your Bash shell: ```bash theme={null} ./setup_stellar_classic_assets.sh [network] ``` Replace `[network]` with the desired network type (e.g., `testnet`). #### Conclusion Following these steps, you can efficiently wrap Stellar Classic assets for use with Soroban. This script automates the process, simplifying the integration of various assets into the Soroban ecosystem. # Using Stellar Classic Assets Source: https://docs.soroswap.finance/tutorials/stellar-classic-assets/index Index of the guides for wrapping, swapping, and testing Stellar Classic assets on Soroswap. These guides will help you seamlessly integrate and interact with Stellar Classic assets on Soroswap ## Wrapping Stellar Classic Assets Learn the importance of wrapping and how to convert your Stellar Classic assets into a Soroban-compatible format for full functionality within the network. This process is vital for engaging with smart contracts and trading on Soroswap. [Start Wrapping](/tutorials/stellar-classic-assets/wrapping) ## Swap Stellar Classic Assets Discover how to exchange Stellar Classic assets with other tokens on Soroswap. This guide walks you through the swap interface and the simple steps to add Stellar Classic assets to your trading repertoire. [Begin Swapping](/tutorials/stellar-classic-assets/swapping) ## Test Stellar Classic Assets on Testnet Ensure your assets and trading strategies are sound by testing them on the Soroswap testnet. Follow our instructions to use the provided API endpoint for accessing test tokens and simulating the wrapping and swapping processes. [Test Assets Now](/tutorials/stellar-classic-assets/testing) Dive into these tutorials to unlock the full potential of Soroswap and the Soroban network, and elevate your trading experience with Stellar Classic assets. # Swap Stellar Classic Assets Source: https://docs.soroswap.finance/tutorials/stellar-classic-assets/swapping Swapping Stellar Classic assets on Soroswap by pasting a CODE:ISSUER pair or Soroban address. With Soroswap you can now swap stellar classic assets, this allows you to exchange one token for a stellar assets or viceversa. Follow the steps below to perform a swap: ## Step 1: Navigate to the Swap page 1. In the swap page you can now paste/use stellar assets CODE:ISSUER or its corresponding Soroban address. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/swapping/swap1.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=60c9e2c31da324b263c2e9bf56645bc3) ## Step 2: Select the token if the token isn't known for us we will ask you to accept the following popup, this will also add the token to your tokens list. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/swapping/swa-add-token.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=5cfc5cccd92b173b70f2f94a5d443bbf) ## Step 3: Continue You can follow the steps on [Doing Swap](/tutorials/doing-swap) Congratulations! you have successfully swapped a Stellar Classic Assets # Test Stellar Classic Assets Source: https://docs.soroswap.finance/tutorials/stellar-classic-assets/testing Getting test tokens from the random_tokens API endpoint and testing asset wrapping on testnet. > [!NOTE] > > This flow is testnet only. The random tokens endpoint hands out throwaway test assets, so nothing here involves real funds. Experimenting with Stellar Classic assets on the Soroswap testnet is straightforward. Our API endpoint `/api/random_tokens` provides you with a variety of test tokens, including Stellar Classic assets that can be wrapped and used by Soroban contracts. ## Accessing Test Tokens To access the test tokens, follow these steps: 1. Navigate to the API endpoint for random tokens: `https://api.soroswap.finance/api/random_tokens` 2. The API will return a JSON object containing an array of tokens indexed by network. Tokens with only the `asset` and `symbol` keys are Stellar Classic assets, whereas Soroban tokens will have additional information such as `address`, `name`, and `decimals`. To test remember to select the tokens from the network you are on. Example response from the API: ```json theme={null} { "tokens": [ // Soroban tokens with detailed information { "address": "CANYI627Y23JOJKXWMRSRIJLWUFBW5YPFMHGBMEX5A4OEH6QJYYSDVMS", "name": "axlnia", "symbol": "AXLN", "logoURI": "", "decimals": 7 }, // Stellar Classic assets with just asset code and issuer { "asset": "JAMN:GAYIZTTI7QKLRKA5GXH5OFFCW3QLU4LNSN5N3XIYXB4P56HHBIXC73X6", "symbol": "JAMN" } // ... more tokens ] } ``` ## Testing Wrapping Functionality To test the wrapping functionality with Stellar Classic assets: 1. From the API's response, copy a Stellar Classic asset in the format `CODE:ISSUER`. 2. Go to the Soroswap testnet interface. 3. Paste the copied `CODE:ISSUER` into the search input from the Swap or Add liquidity pages, and select the asset from the results. 4. The interface will recognize that the asset is a Stellar Classic asset and offer you the option to wrap it so Soroban contracts can use it. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/wrapping/wrapping-process-modal.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=e754ac4ace1733358d48d0fb32dc3a0f) 5. Click on the asset, and a wrapping modal will appear, guiding you through the wrapping process. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/wrapping/wrapping-view.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=ef7bb00b42644ba7967aa865edea063f) 6. Once you initiate the wrapping process, you will be prompted to sign the transaction with your wallet. After wrapping, the asset will be available in your Soroswap testnet account, ready for trading or adding to liquidity pools. It is still the same asset, now reachable through its Stellar Asset Contract, all within the safety of the testnet environment. Testing Stellar Classic assets on Soroswap testnet is not only a valuable step for ensuring your assets are ready for live trading but also provides a risk-free way to familiarize yourself with the wrapping process and the broader Soroban ecosystem. # Wrapping Stellar Classic Assets Source: https://docs.soroswap.finance/tutorials/stellar-classic-assets/wrapping Why Stellar Classic assets need a Stellar Asset Contract to work with Soroban, and how to wrap one from Soroswap. ## Understanding the Importance of Wrapping Stellar Assets for Soroban Soroban is the smart contract environment of Stellar. It is not a separate network: contracts run on Stellar itself, alongside the classic operations that have always existed there. Assets issued the traditional way, often called "Stellar Classic" assets, are not automatically reachable from a smart contract, though. To make one usable by contracts and dApps, it needs a contract interface. This is what "wrapping" means. ### What is Wrapping? Wrapping deploys a **Stellar Asset Contract** (SAC) for a Stellar Classic asset. The Stellar Asset Contract is a built in contract interface that exposes the **same** asset to Soroban, so a smart contract can hold and move it using the standard token interface. The important part: this does **not** create a second token, and there is no bridge and no peg. There is one asset with one issuer and one supply. A balance is the same balance whether it is touched by a classic payment or by a contract call, and the trustline rules of the original asset still apply. Wrapping simply gives the existing asset an address that contracts can call. Wrapping only needs to happen once per asset. If somebody has already deployed the Stellar Asset Contract for an asset, it is already usable, and there is nothing left for you to do. ### Why Wrap Stellar Assets? #### Seamless Integration Once the Stellar Asset Contract exists, the asset can be used by smart contracts and dApps the same way any Soroban token can, with no change to how it behaves in classic operations. #### Trading and Liquidity Wrapping an asset makes it usable in contract based venues like Soroswap, opening it up to liquidity pools and to new markets. #### Enhanced Functionality Contracts can hold, transfer, and program around the asset, which is not possible for an asset that has no contract interface. ### How Does Wrapping Benefit You? #### Accessibility Enjoy access to a wider range of tokens and trading pairs on Soroswap, enhancing your trading experience. #### Efficiency Execute transactions and smart contract operations with the speed and low cost of Stellar. #### Opportunity Participate in the financial services and products being built with Soroban contracts on Stellar. Wrapping is a simple yet vital step in making Stellar Classic assets available to smart contracts. By wrapping an asset, you are not just preparing it for trading on Soroswap, you are making it usable across everything built on Soroban. *** ## Wrapping Assets Directly on Soroswap Soroswap simplifies the wrapping process by integrating it directly into the user interface. If you're looking to swap or provide liquidity with a Stellar Classic asset, Soroswap's intuitive search modal will assist you every step of the way. ### Wrapping via the Search Modal Soroswap's search modal allows you to find your desired token. If you enter a `CODE:ISSUER` combination for a Stellar asset that does not have its Stellar Asset Contract deployed yet: 1. Soroswap will detect this and prompt you with a suggestion to wrap the asset. 2. A modal will appear, presenting you with the option to "Wrap Asset." 3. Upon confirming your choice, you will be prompted to sign the transaction with your wallet. 4. Once signed, the transaction is submitted, and the asset's Stellar Asset Contract is deployed. ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/wrapping/wrapping-process-modal.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=e754ac4ace1733358d48d0fb32dc3a0f) ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/wrapping/wrapping-view.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=ef7bb00b42644ba7967aa865edea063f) ### Post-Wrapping: What’s Next? After wrapping, the asset will appear in your Soroswap token list, ready to be traded or added to liquidity pools. It is still the same asset it always was, now reachable by contracts as well as by classic operations. *You will probably be shown this warning view if it is an unknown asset for us* ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/wrapping/warning.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=f368ee2562b2c9c58eab53211fcdfb63) *If everything goes as expected you should now have your token selected* ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/tutorials/stellar-classic-assets/wrapping/swap-view.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=2cf911bb041c37d5d65dc7fd444906a0) By integrating asset wrapping into the swapping process, Soroswap ensures that users have a seamless experience, closing the gap between Stellar Classic assets and Soroban contracts. This feature not only enhances the usability of Soroswap but also enriches the overall ecosystem by fostering interoperability and ease of use. Wrap your Stellar Classic assets on Soroswap today, and unlock the full potential of your trading experience on Stellar. # What is Soroswap Finance? Source: https://docs.soroswap.finance/what-is-soroswap Soroswap Finance is an open source protocol on Stellar: the Aggregator API that routes swaps across every Stellar venue, and the Soroswap AMM. [Soroswap.Finance](/) is an open-source protocol designed for liquidity provision and token trading on the Soroban blockchain. It operates without the need for intermediaries, ensuring a secure, transparent, and efficient trading environment. The protocol is immutable and built to resist censorship. Development of Soroswap.Finance and its interface is managed by [PaltaLabs.](https://paltalabs.io) ![](https://mintcdn.com/soroswap/1xzCGA5zGx88_Gb9/images/home.png?fit=max\&auto=format\&n=1xzCGA5zGx88_Gb9\&q=85\&s=d6853ee5f5d8b8f8a6658fa5af61041a) Soroswap.Finance includes: * [**Soroswap Aggregator API**](/): one API that quotes Soroswap, Aquarius, SushiSwap V3, Comet and the Stellar Classic DEX, and returns the best route ready to execute. * [**Soroswap AMM**](/amm): an Automated Market Maker that facilitates token swaps and liquidity provision. ### How to Use Soroswap.Finance To interact with [Soroswap.Finance](/), you can mint test tokens, create [liquidity pools](/concepts/liquidity-pools), [provide liquidity](/tutorials/adding-liquidity), or [swap tokens](/tutorials/doing-swap) using the Soroswap Interface. Connect your [Freighter wallet ](/tutorials/installing-freighter)to perform these actions. Note that transactions on Soroban require Lumens (XLM), so ensure [you have test ](/tutorials/soroswap-testnet-overview)Lumens, which can be obtained from Friendbot. ### How Soroswap.Finance Works Soroswap.Finance functions as an Automated Market Maker (AMM) through a network of smart contracts that standardize liquidity pool creation, liquidity provision, and asset swapping. * **Liquidity Pools**: Each pool contains two assets. The pool’s reserves and pricing are managed dynamically by the protocol, updating with each trade. * **Trading**: Unlike traditional exchanges, Soroswap does not use a central order book or private matching engines. Instead, reserves are automatically rebalanced, allowing continuous trading without the need for counterparties. For more detailed information, refer to the [**Concepts**](/concepts) and [**Getting Started**.](/getting-started)