Building a Decentralized Exchange on Stellar: From Concept to Code

Welcome to this comprehensive guide on building a Decentralized Exchange (DEX) on the Stellar network! Whether you’re a blockchain novice or an experienced developer, this tutorial will walk you through creating a functional DEX from scratch.


This content originally appeared on DEV Community and was authored by cauhlins

Welcome to this comprehensive guide on building a Decentralized Exchange (DEX) on the Stellar network! Whether you're a blockchain novice or an experienced developer, this tutorial will walk you through creating a functional DEX from scratch.

Conceptual illustration of a decentralized exchange

Table of Contents

  1. Introduction to Decentralized Exchanges
  2. Understanding Stellar's Core Concepts
  3. Setting Up the Development Environment
  4. Designing the DEX Smart Contract
  5. Implementing the Smart Contract
  6. Creating Custom Tokens for Trading
  7. Deploying the Smart Contract
  8. Building the Frontend
  9. Testing the DEX
  10. Advanced Features and Optimizations
  11. Security Considerations
  12. Conclusion and Next Steps

Introduction to Decentralized Exchanges

A Decentralized Exchange (DEX) is a type of cryptocurrency exchange that operates without a central authority. Instead, it uses smart contracts to facilitate trades directly between users.

Key Terms:

  • Decentralized Exchange (DEX): A peer-to-peer marketplace for cryptocurrencies without a central authority.
  • Smart Contract: Self-executing code that automatically implements the terms of an agreement between parties.
  • Liquidity: The ease with which an asset can be bought or sold without affecting its price significantly.

Understanding Stellar's Core Concepts

Before we dive into building our DEX, let's review some core Stellar concepts:

  1. Accounts: Represent participants in the Stellar network. Each account has a public key and can hold balances in various assets.

  2. Assets: Represent different types of value on the Stellar network. The native asset is called Lumens (XLM), but custom assets can also be created.

  3. Operations: Individual commands that modify the ledger state, such as creating accounts, sending payments, or making offers.

  4. Transactions: Groups of operations that are submitted to the network as a single unit.

  5. Orderbook: A record of outstanding offers to buy or sell assets on the Stellar network.

  6. Path Payments: Allows sending one type of asset and having the recipient receive another type, with the network automatically handling the conversion.

Setting Up the Development Environment

Let's set up our development environment:

  1. Install Node.js and npm (if not already installed)
  2. Install the Stellar SDK:
   npm install stellar-sdk
  1. Install Soroban CLI for smart contract development:
   cargo install --locked --version 20.0.0-rc2 soroban-cli
  1. Set up a new project:
   mkdir stellar-dex
   cd stellar-dex
   npm init -y

Designing the DEX Smart Contract

Our DEX will need the following core functionalities:

  1. Create and manage order books for different trading pairs
  2. Place buy and sell orders
  3. Match and execute orders
  4. Manage user balances
  5. Withdraw funds

Let's design our smart contract structure:

pub struct DEX {
    order_books: Map<AssetPair, OrderBook>,
    balances: Map<Address, Map<Asset, i128>>,
}

pub struct AssetPair {
    base_asset: Asset,
    quote_asset: Asset,
}

pub struct OrderBook {
    buy_orders: Vec<Order>,
    sell_orders: Vec<Order>,
}

pub struct Order {
    user: Address,
    amount: i128,
    price: i128,
    is_buy: bool,
}

pub struct Asset {
    code: Symbol,
    issuer: Option<Address>,
}

Implementing the Smart Contract

Now, let's implement our DEX smart contract. Create a new file src/lib.rs:

#![no_std]
use soroban_sdk::{contractimpl, Address, Env, Map, Symbol, Vec};

mod types;
use types::{DEX, AssetPair, OrderBook, Order, Asset};

const PRECISION: i128 = 10000000; // 7 decimal places

#[contractimpl]
impl DEX {
    pub fn init(env: Env) -> Self {
        Self {
            order_books: Map::new(&env),
            balances: Map::new(&env),
        }
    }

    pub fn deposit(&mut self, env: Env, user: Address, asset: Asset, amount: i128) {
        user.require_auth();
        let balance = self.balances.get(user).unwrap_or(Map::new(&env));
        let new_balance = balance.get(asset).unwrap_or(0) + amount;
        balance.set(asset, new_balance);
        self.balances.set(user, balance);
    }

    pub fn withdraw(&mut self, env: Env, user: Address, asset: Asset, amount: i128) {
        user.require_auth();
        let mut balance = self.balances.get(user).unwrap();
        let current_balance = balance.get(asset).unwrap();
        if current_balance < amount {
            panic!("Insufficient balance");
        }
        balance.set(asset, current_balance - amount);
        self.balances.set(user, balance);
        // Here we would typically initiate a Stellar transaction to send the assets
    }

    pub fn place_order(&mut self, env: Env, user: Address, pair: AssetPair, amount: i128, price: i128, is_buy: bool) {
        user.require_auth();
        let order = Order {
            user: user.clone(),
            amount,
            price,
            is_buy,
        };
        let mut order_book = self.order_books.get(pair).unwrap_or(OrderBook::new(&env));
        if is_buy {
            order_book.buy_orders.push_back(order);
            order_book.buy_orders.sort_by(|a, b| b.price.cmp(&a.price));
        } else {
            order_book.sell_orders.push_back(order);
            order_book.sell_orders.sort_by(|a, b| a.price.cmp(&b.price));
        }
        self.order_books.set(pair, order_book);
        self.match_orders(env, pair);
    }

    fn match_orders(&mut self, env: Env, pair: AssetPair) {
        let mut order_book = self.order_books.get(pair).unwrap();
        while !order_book.buy_orders.is_empty() && !order_book.sell_orders.is_empty() {
            let buy_order = order_book.buy_orders.first().unwrap();
            let sell_order = order_book.sell_orders.first().unwrap();
            if buy_order.price < sell_order.price {
                break;
            }
            let trade_price = (buy_order.price + sell_order.price) / 2;
            let trade_amount = buy_order.amount.min(sell_order.amount);
            self.execute_trade(env, &pair, &buy_order.user, &sell_order.user, trade_amount, trade_price);
            // Update or remove orders
            if buy_order.amount > trade_amount {
                order_book.buy_orders.set(0, Order {
                    amount: buy_order.amount - trade_amount,
                    ..buy_order
                });
            } else {
                order_book.buy_orders.remove(0);
            }
            if sell_order.amount > trade_amount {
                order_book.sell_orders.set(0, Order {
                    amount: sell_order.amount - trade_amount,
                    ..sell_order
                });
            } else {
                order_book.sell_orders.remove(0);
            }
        }
        self.order_books.set(pair, order_book);
    }

    fn execute_trade(&mut self, env: Env, pair: &AssetPair, buyer: &Address, seller: &Address, amount: i128, price: i128) {
        let base_amount = amount;
        let quote_amount = amount * price / PRECISION;
        // Transfer base asset from seller to buyer
        self.transfer(env, seller, buyer, pair.base_asset, base_amount);
        // Transfer quote asset from buyer to seller
        self.transfer(env, buyer, seller, pair.quote_asset, quote_amount);
    }

    fn transfer(&mut self, env: Env, from: &Address, to: &Address, asset: Asset, amount: i128) {
        let mut from_balance = self.balances.get(from).unwrap();
        let mut to_balance = self.balances.get(to).unwrap_or(Map::new(&env));
        let from_amount = from_balance.get(asset).unwrap();
        let to_amount = to_balance.get(asset).unwrap_or(0);
        from_balance.set(asset, from_amount - amount);
        to_balance.set(asset, to_amount + amount);
        self.balances.set(from, from_balance);
        self.balances.set(to, to_balance);
    }
}

This implementation covers the core functionalities of our DEX:

  • Depositing and withdrawing assets
  • Placing buy and sell orders
  • Matching and executing orders
  • Managing user balances

Creating Custom Tokens for Trading

To create a more interesting trading environment, let's create two custom tokens:

  1. SpaceBucks (SPC)
  2. LunarCredits (LNC)

Create a new file src/tokens.rs:

use soroban_sdk::{contractimpl, token, Address, Env, String};

pub struct Token;

#[contractimpl]
impl Token {
    pub fn initialize(env: Env, admin: Address, decimal: u32, name: String, symbol: String) {
        let token = token::Interface::new(&env, &env.current_contract_address());
        token.initialize(&admin, &decimal, &name, &symbol);
    }

    pub fn mint(env: Env, to: Address, amount: i128) {
        let token = token::Interface::new(&env, &env.current_contract_address());
        token.mint(&to, &amount);
    }

    pub fn balance(env: Env, id: Address) -> i128 {
        let token = token::Interface::new(&env, &env.current_contract_address());
        token.balance(&id)
    }

    pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
        let token = token::Interface::new(&env, &env.current_contract_address());
        token.transfer(&from, &to, &amount);
    }
}

Deploying the Smart Contract

Now, let's deploy our smart contracts to the Stellar testnet:

  1. Build the contracts:
   soroban contract build
  1. Create a Stellar account for deployment:
   soroban config identity generate admin
   soroban config network add testnet --rpc-url https://soroban-testnet.stellar.org
   soroban config identity fund admin --network testnet
  1. Deploy the DEX contract:
   soroban contract deploy --wasm target/wasm32-unknown-unknown/release/dex.wasm --source admin --network testnet
  1. Deploy the token contracts:
   soroban contract deploy --wasm target/wasm32-unknown-unknown/release/token.wasm --source admin --network testnet
   soroban contract deploy --wasm target/wasm32-unknown-unknown/release/token.wasm --source admin --network testnet

Make note of the contract IDs returned for each deployment.

Building the Frontend

For our frontend, we'll use React with the Stellar SDK. Create a new React app:

npx create-react-app stellar-dex-frontend
cd stellar-dex-frontend
npm install stellar-sdk @stellar/freighter-api

Replace the content of src/App.js with:

import React, { useState, useEffect } from 'react';
import { Server } from 'stellar-sdk';
import { isConnected, getPublicKey } from '@stellar/freighter-api';

const server = new Server('https://horizon-testnet.stellar.org');
const dexContractId = 'YOUR_DEX_CONTRACT_ID';
const spcTokenId = 'YOUR_SPC_TOKEN_CONTRACT_ID';
const lncTokenId = 'YOUR_LNC_TOKEN_CONTRACT_ID';

function App() {
  const [account, setAccount] = useState(null);
  const [spcBalance, setSpcBalance] = useState(0);
  const [lncBalance, setLncBalance] = useState(0);
  const [orderBooks, setOrderBooks] = useState({ buy: [], sell: [] });

  useEffect(() => {
    checkConnection();
  }, []);

  const checkConnection = async () => {
    const connected = await isConnected();
    if (connected) {
      const publicKey = await getPublicKey();
      setAccount(publicKey);
      fetchBalances(publicKey);
      fetchOrderBooks();
    }
  };

  const fetchBalances = async (publicKey) => {
    const spcBalance = await server.loadAccount(publicKey).then(account => {
      return account.balances.find(b => b.asset_code === 'SPC')?.balance || '0';
    });
    const lncBalance = await server.loadAccount(publicKey).then(account => {
      return account.balances.find(b => b.asset_code === 'LNC')?.balance || '0';
    });
    setSpcBalance(spcBalance);
    setLncBalance(lncBalance);
  };

  const fetchOrderBooks = async () => {
    // In a real implementation, you would fetch this data from your smart contract
    setOrderBooks({
      buy: [
        { price: 1.2, amount: 100 },
        { price: 1.1, amount: 200 },
      ],
      sell: [
        { price: 1.3, amount: 150 },
        { price: 1.4, amount: 300 },
      ],
    });
  };

  const placeOrder = async (isBuy, amount, price) => {
    // Implementation of placing an order via smart contract
    console.log(`Placing ${isBuy ? 'buy' : 'sell'} order: ${amount} @ ${price}`);
  };

  return (
    <div className="App">
      <h1>Stellar DEX</h1>
      {account ? (
        <>
          <p>Connected: {account}</p>
          <p>SPC Balance: {spcBalance}</p>
          <p>LNC Balance: {lncBalance}</p>
          <h2>Order Books</h2>
          <div style={{ display: 'flex', justifyContent: 'space-around' }}>
            <div>
              <h3>Buy Orders</h3>
              <ul>
                {orderBooks.buy.map((order, index) => (
                  <li key={index}>
                    {order.amount} SPC @ {order.price} LNC
                  </li>
                ))}
              </ul>
            </div>
            <div>
              <h3>Sell Orders</h3>
              <ul>
                {orderBooks.sell.map((order, index) => (
                  <li key={index}>
                    {order.amount} SPC @ {order.price} LNC
                  </li>
                ))}
              </ul>
            </div>
          </div>
          <h2>Place Order</h2>
          <form onSubmit={(e) => {
            e.preventDefault();
            const formData = new FormData(e.target);
            placeOrder(
              formData.get('type') === 'buy',
              parseFloat(formData.get('amount')),
              parseFloat(formData.get('price'))
            );
          }}>
            <select name="type">
              <option value="buy">Buy</option>
              <option value="sell">Sell</option>
            </select>
            <input type="number" name="amount" placeholder="Amount" step="0.01" required />
            <input type="number" name="price" placeholder="Price" step="0.01" required />
            <button type="submit">Place Order</button>
          </form>
        </>
      ) : (
        <p>Please connect your Stellar wallet</p>
      )}
    </div>
  );
}

export default App;

This completes our basic frontend implementation. It provides a user interface for viewing balances, order books, and placing orders.

Testing the DEX

To test our DEX, we'll need to:

  1. Deploy the smart contracts (if not already done)
  2. Create and fund test accounts
  3. Mint some test tokens
  4. Place orders and observe the matching process

Here's a script to help with testing (test_dex.js):

const StellarSdk = require('stellar-sdk');
const { Contract, Server } = StellarSdk;

const server = new Server('https://horizon-testnet.stellar.org');
const dexContractId = 'YOUR_DEX_CONTRACT_ID';
const spcTokenId = 'YOUR_SPC_TOKEN_CONTRACT_ID';
const lncTokenId = 'YOUR_LNC_TOKEN_CONTRACT_ID';

async function createTestAccount() {
  const pair = StellarSdk.Keypair.random();
  await server.friendbot(pair.publicKey()).call();
  return pair;
}

async function mintTestTokens(account, tokenId, amount) {
  const contract = new Contract(tokenId);
  const tx = new StellarSdk.TransactionBuilder(account, { 
    fee: StellarSdk.BASE_FEE,
    networkPassphrase: StellarSdk.Networks.TESTNET
  })
    .addOperation(contract.call('mint', account.publicKey(), amount))
    .setTimeout(30)
    .build();

  tx.sign(account);
  await server.submitTransaction(tx);
}

async function placeOrder(account, isBuy, amount, price) {
  const contract = new Contract(dexContractId);
  const tx = new StellarSdk.TransactionBuilder(account, { 
    fee: StellarSdk.BASE_FEE,
    networkPassphrase: StellarSdk.Networks.TESTNET
  })
    .addOperation(contract.call('place_order', {
      user: account.publicKey(),
      pair: { base_asset: spcTokenId, quote_asset: lncTokenId },
      amount: amount,
      price: price,
      is_buy: isBuy
    }))
    .setTimeout(30)
    .build();

  tx.sign(account);
  await server.submitTransaction(tx);
}

async function runTest() {
  const alice = await createTestAccount();
  const bob = await createTestAccount();

  await mintTestTokens(alice, spcTokenId, '1000');
  await mintTestTokens(bob, lncTokenId, '1000');

  await placeOrder(alice, true, '100', '1.2');  // Alice places a buy order
  await placeOrder(bob, false, '100', '1.2');   // Bob places a matching sell order

  console.log('Test completed successfully!');
}

runTest().catch(console.error);

Run this script with Node.js to test your DEX functionality.

Advanced Features and Optimizations

To take your DEX to the next level, consider implementing these advanced features:

  1. Limit and Market Orders: Implement different order types to provide more trading options.

  2. Order Book Optimization: Use a more efficient data structure (e.g., a binary heap) for faster order matching.

  3. Fee System: Implement a small fee for trades to incentivize liquidity providers.

  4. Liquidity Pools: Add automated market maker (AMM) functionality alongside the order book.

  5. Cross-Asset Trades: Implement path payments to allow trading between any asset pairs.

  6. Price Oracle: Integrate with external price feeds for more accurate pricing.

  7. Trade History: Store and display recent trades for each asset pair.

Security Considerations

When building a DEX, security is paramount. Here are some key considerations:

  1. Smart Contract Audits: Have your smart contracts audited by professional security researchers.

  2. Rate Limiting: Implement rate limiting to prevent spam and potential DoS attacks.

  3. Access Control: Ensure that only authorized users can perform sensitive operations.

  4. Integer Overflow Protection: Use safe math operations to prevent integer overflows.

  5. Reentrancy Guards: Implement checks to prevent reentrancy attacks.

  6. Formal Verification: Consider using formal verification tools to mathematically prove the correctness of your smart contracts.

  7. Upgrade Mechanism: Implement a secure upgrade mechanism to fix bugs and add features without compromising user funds.

Conclusion and Next Steps

Congratulations! You've built a basic decentralized exchange on Stellar. This project has introduced you to:

  • Stellar's core concepts and smart contract platform (Soroban)
  • Implementing complex financial logic in smart contracts
  • Integrating Stellar operations with a web frontend
  • Security considerations for decentralized finance applications

To continue your journey:

  1. Expand the frontend to include more user-friendly features like charts and detailed order history.
  2. Implement the advanced features mentioned above.
  3. Conduct thorough testing, including edge cases and stress tests.
  4. Consider the regulatory implications of running a DEX and consult with legal experts.
  5. Engage with the Stellar community for feedback and potential collaborations.

Remember, building a production-ready DEX requires extensive testing, auditing, and compliance considerations. This tutorial serves as a starting point for your exploration of decentralized finance on Stellar.

Happy coding, and welcome to the exciting world of decentralized exchanges!


This content originally appeared on DEV Community and was authored by cauhlins


Print Share Comment Cite Upload Translate Updates
APA

cauhlins | Sciencx (2024-07-31T18:55:38+00:00) Building a Decentralized Exchange on Stellar: From Concept to Code. Retrieved from https://www.scien.cx/2024/07/31/building-a-decentralized-exchange-on-stellar-from-concept-to-code-2/

MLA
" » Building a Decentralized Exchange on Stellar: From Concept to Code." cauhlins | Sciencx - Wednesday July 31, 2024, https://www.scien.cx/2024/07/31/building-a-decentralized-exchange-on-stellar-from-concept-to-code-2/
HARVARD
cauhlins | Sciencx Wednesday July 31, 2024 » Building a Decentralized Exchange on Stellar: From Concept to Code., viewed ,<https://www.scien.cx/2024/07/31/building-a-decentralized-exchange-on-stellar-from-concept-to-code-2/>
VANCOUVER
cauhlins | Sciencx - » Building a Decentralized Exchange on Stellar: From Concept to Code. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/31/building-a-decentralized-exchange-on-stellar-from-concept-to-code-2/
CHICAGO
" » Building a Decentralized Exchange on Stellar: From Concept to Code." cauhlins | Sciencx - Accessed . https://www.scien.cx/2024/07/31/building-a-decentralized-exchange-on-stellar-from-concept-to-code-2/
IEEE
" » Building a Decentralized Exchange on Stellar: From Concept to Code." cauhlins | Sciencx [Online]. Available: https://www.scien.cx/2024/07/31/building-a-decentralized-exchange-on-stellar-from-concept-to-code-2/. [Accessed: ]
rf:citation
» Building a Decentralized Exchange on Stellar: From Concept to Code | cauhlins | Sciencx | https://www.scien.cx/2024/07/31/building-a-decentralized-exchange-on-stellar-from-concept-to-code-2/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.