top of page
  • Writer's pictureEko Lance

How to Build a Simple DeFi Application on the Stellar Network with JavaScript

The Building Blocks of DeFi: Blockchain Technology

Blockchain is a distributed ledger technology that is the foundation for most cryptocurrencies and decentralized finance (DeFi) applications. It's a system of recording information that makes it difficult or impossible to change, hack, or cheat the system.


Key Characteristics of Blockchain:

1. Decentralization: Unlike traditional databases managed by a central authority, a blockchain is typically managed by a network of computers (nodes) working together.

2. Transparency: All transactions on a public blockchain are visible to anyone participating in the network.

3. Immutability: Once data is recorded on the blockchain, it's extremely difficult to alter without consensus from the network.

4. Security: Cryptographic techniques ensure the integrity and authenticity of transactions.


How Blockchain Works:

An image list rating how the Blockchain works.

1. Transaction Initiation: A user initiates a transaction (e.g., sending cryptocurrency).

2. Transaction Broadcasting: The transaction is broadcast to a network of computers (nodes).

3. Verification: Nodes verify the transaction's validity.

4. Block Creation: Verified transactions are combined with other transactions to create a new block of data.

5. Block Addition: The new block is added to the existing chain of blocks, creating a permanent and unalterable record.

6. Transaction Completion: The transaction is complete and recorded on the blockchain.


Types of Blockchains:

1. Public Blockchains: Open to anyone (e.g., Bitcoin, Ethereum).

2. Private Blockchains: Restricted to specific participants.

3. Consortium Blockchains: Partially decentralized, controlled by a group of organizations.


The Building Blocks of DeFi: Smart Contracts

An image detailing how a smart contract works.

Smart contracts are self-executing contracts with the terms of the agreement directly written into code. They automatically execute, control, or document legally relevant events and actions according to the terms of the contract or agreement.


Key Features of Smart Contracts:

1. Automation: Execute automatically when predefined conditions are met.

2. Transparency: The code is visible to all parties involved.

3. Immutability: Once deployed, the contract can't be changed (unless specifically designed to be upgradable).

4. Efficiency: Reduce the need for intermediaries, saving time and costs.


How Smart Contracts Work:

1. Contract Creation: Developers write the smart contract code, defining the rules and conditions.

2. Deployment: The contract is deployed to a blockchain network.

3. Execution: When the predefined conditions are met, the contract automatically executes its programmed actions.

4. Verification: The network verifies the execution, ensuring it follows the contract's rules.

5. Recording: The execution results are recorded on the blockchain.


Understanding Decentralized Finance (DeFi)

Decentralized Finance, or DeFi, represents a revolutionary approach to financial services. It aims to create an open, global economic system that operates without traditional intermediaries like banks or brokers. 


Instead, DeFi leverages blockchain technology and smart contracts to provide transparent and accessible financial services to anyone with an internet connection.


Key Components of DeFi:

1. Blockchain Technology: The foundation of DeFi, providing a secure and transparent way to record transactions.

2. Smart Contracts: Self-executing contracts with the agreement terms directly written into code, enabling trustless operations.

3. Decentralized Applications (dApps): User interfaces for interacting with DeFi services.

4. Tokens and Cryptocurrencies: These include utility tokens and stablecoins, which facilitate transactions and provide stability.


Core DeFi Services:

  • Lending and Borrowing: Platforms allowing users to lend or borrow cryptocurrencies.

  • Decentralized Exchanges (DEXs): Enabling direct peer-to-peer trading of cryptocurrencies.

  • Yield Farming and Liquidity Mining: Methods for earning rewards by providing liquidity to protocols.

  • Stablecoins and Synthetic Assets: Providing stability and tokenized versions of real-world assets.

  • Governance: Decentralized decision-making through DAOs (Decentralized Autonomous Organizations).


Introduction to the Stellar Network

Stellar is an open-source blockchain network designed for fast, secure, and low-cost cross-border transactions. Created in 2014, Stellar aims to make financial services accessible to everyone, especially in underserved regions.


Key Features of Stellar:

1. Stellar Consensus Protocol (SCP): A unique consensus mechanism that's faster and more energy-efficient than traditional methods.

2. Low Transaction Costs: Fees are a fraction of a cent, making Stellar ideal for microtransactions and remittances.

3. Anchors and Fiat Integration: Entities that bridge traditional finance with the Stellar blockchain.

4. Stellar Lumens (XLM): The native cryptocurrency used for transaction fees and facilitating multi-currency transactions.

5. Built-in Decentralized Exchange (DEX): Allows trading of any token issued on the network.

6. Simple Smart Contracts: While less complex than Ethereum, Stellar supports multi-signature accounts and basic conditional operations.


Stellar's Use Cases:

  • Cross-Border Payments and Remittances

  • Asset Tokenization

  • Micropayments

  • Emerging DeFi Applications


How to Build a Simple DeFi Application on Stellar with Javascript

An image showing the text “How to Build a Simple DeFi Application on the Stellar Network with JavaScript”

In this tutorial, we'll build a simple DeFi application on the Stellar network. We'll create a liquidity pool, fund it, perform a swap, and then withdraw from the pool. 


What is a Liquidity Pool?

Every asset issued on Stellar starts with zero liquidity. "Liquidity" is a fancy finance word that means how much of an asset in a market is available to buy or sell. 


To create and sustain a market, people must be willing to put capital into the network and use that capital to facilitate asset conversion. 


Originally on Stellar, there was only one way to do that: by creating orders on the order books. 


However, the desire to improve liquidity and make cross-border payments faster, cheaper, and more user-friendly led to the Core Advancement Proposals (Protocol 18 and CAP-0038) to add automated market maker (AMM) functionality to Stellar.


Automated Market Makers (AMMs) are a type of decentralized exchange (DEX) that uses algorithmic mechanisms to facilitate the trading of digital assets. 


Unlike traditional financial markets that rely on buyers and sellers, AMMs aim to maintain liquidity in the DeFi ecosystem through liquidity pools.


This guide is designed for beginners and will explain each step and term in detail.


Prerequisites

Before we begin, ensure you have the following installed:

  • Node.js (version 12 or higher): A JavaScript runtime that allows you to run JavaScript on your computer.

  • npm (Node Package Manager): A tool that comes with Node.js to help you install and manage JavaScript packages.


Step 1: Setting Up the Project

Let's start by creating a new directory for our project and initializing it:

mkdir stellar-defi-app 
cd stellar-defi-app
npm init -y

These commands create a new directory, navigate into it, and initialize a new Node.js project.

Now, let's install the necessary dependencies:

npm install @stellar/stellar-sdk node-fetch

Here, we're installing two packages:

  • @stellar/stellar-sdk: The official Stellar SDK for JavaScript, which provides tools to interact with the Stellar network.

  • node-fetch: A module that brings the fetch function (typically available in browsers) to Node.js, allowing us to make HTTP requests.


Step 2: Understanding the Stellar Testnet

Before we dive into the code, let's discuss the Stellar Testnet:

  • The Testnet is a separate network from the Stellar Mainnet, used for testing and development.

  • It uses test tokens that have no real value, allowing developers to experiment safely.

  • The Testnet has a "Friendbot" service that provides free test tokens to fund new accounts.


Step 3: Writing the Application

Create a new file called app.js. We'll add our code to this file piece by piece.


Importing Dependencies

First, let's import the necessary modules:

import {
  Keypair,
  SorobanRpc,
  TransactionBuilder,
  Asset,
  Operation,
  LiquidityPoolAsset,
  getLiquidityPoolId,
  BASE_FEE,
  Networks
} from '@stellar/stellar-sdk';

import fetch from 'node-fetch';

Let's break down what each of these imported items does:

  • Keypair: Used to generate public/private key pairs for Stellar accounts.

  • SorobanRpc: Allows interaction with the Stellar network.

  • TransactionBuilder: Helps construct transactions on the Stellar network.

  • Asset: Represents assets (like currencies) on the Stellar network.

  • Operation: Defines various operations that can be performed on the Stellar network.

  • LiquidityPoolAsset: Represents a liquidity pool on Stellar.

  • getLiquidityPoolId: A function to generate a unique ID for a liquidity pool.

  • BASE_FEE: The base fee for transactions on the Stellar network.

  • Networks: Contains information about different Stellar networks (like Testnet and Mainnet).


Why We Need Token For Transactions

Every instruction on the blockchain is a transaction. It has to be validated before it gets added to the network. 

Before your transaction can be approved, you must pay the miners a gas fee in the native token of that blockchain for this validation.


On the Stellar network, you need tokens (XLM) to pay for transaction fees. Since we're developing on the testnet, we can get free test tokens from Friendbot instead of using real XLM.


Funding Function

Next, let's create a function to fund our accounts using Friendbot:

async function fundAccountWithFriendbot(address) {
  const friendbotUrl = `https://friendbot.stellar.org?addr=${address}`;
  try {
    let response = await fetch(friendbotUrl);
    if (response.ok) {
      console.log(`Account ${address} successfully funded.`);
      return true;
    } else {
      console.log(`Something went wrong funding account: ${address}.`);
      return false;
    }
  } catch (error) {
    console.error(`Error funding account ${address}:`, error);
    return false;
  }
}

This function does the following:

  1. It takes a Stellar address as input.

  2. It constructs a URL to request funds from Friendbot.

  3. It sends a request to Friendbot using fetch.

  4. If successful, it logs a success message; otherwise, it logs an error.


Main Function

Now, let's create our main function that will perform all our DeFi operations:

async function runDeFiOperations() {
  // We'll add our operations here
}

We'll fill this function with our DeFi operations step by step.


Setting up the Stellar Server

Inside runDeFiOperations, add:

const server = new SorobanRpc.Server('https://soroban-testnet.stellar.org');

This creates a new instance of the Stellar server, connecting to the Testnet.


Creating and Funding Accounts

Next, let's create and fund our liquidity provider account:

const defiKeypair = Keypair.random();
console.log("DeFi Provider Public Key:", defiKeypair.publicKey());
await fundAccountWithFriendbot(defiKeypair.publicKey());
const defiAccount = await server.getAccount(defiKeypair.publicKey());

This code:

1. Generates a new keypair for our liquidity provider.

2. Funds this account using Friendbot.

3. Retrieves the account details from the Stellar network.


Creating Assets and Liquidity Pool

Now, let's create our custom asset and set up the liquidity pool:

const ekoLanceAsset = new Asset('EkoLance', defiKeypair.publicKey());

const lpAsset = new LiquidityPoolAsset(Asset.native(), ekoLanceAsset, 30);

const liquidityPoolId = getLiquidityPoolId('constant_product', lpAsset).toString('hex');

console.log("Custom Asset:", ekoLanceAsset);
console.log("Liquidity Pool Asset:", lpAsset);
console.log("Liquidity Pool ID:", liquidityPoolId);

This code:

1. Creates a custom asset called "EkoLance".

2. Defines a liquidity pool asset that pairs our custom asset with the native XLM asset and the fee on every trade is set at 30 bps (basis points), meaning 0.3% is required whenever the pool is used during a trade.

3. Generates a unique ID for our liquidity pool.


Creating and Funding the Liquidity Pool

Next, let's create a transaction to establish and fund the liquidity pool:

const lpDepositTransaction = new TransactionBuilder(defiAccount, {
  fee: BASE_FEE,
  networkPassphrase: Networks.TESTNET
})
  .addOperation(Operation.changeTrust({
    asset: lpAsset
  }))
  .addOperation(Operation.liquidityPoolDeposit({
    liquidityPoolId: liquidityPoolId,
    maxAmountA: '100',
    maxAmountB: '100',
    minPrice: { n: 1, d: 1 },
    maxPrice: { n: 1, d: 1 }
  }))
  .setTimeout(30)
  .build();

lpDepositTransaction.sign(defiKeypair);

try {
  const result = await server.sendTransaction(lpDepositTransaction);
  console.log("Liquidity Pool Created. Transaction URL:", `https://stellar.expert/explorer/testnet/tx/${result.hash}`);
} catch (error) {
  console.log(`Error creating Liquidity Pool: ${error}`);
  return;
}

This code:

  1. Creates a new transaction using TransactionBuilder.

  2. Adds a "change trust" operation to allow our account to hold the liquidity pool asset.

  3. Adds a "liquidity pool deposit" operation to create and fund the pool.

  4. Signs and submits the transaction.


Performing a Swap

Now, let's create a trader account and perform a swap:

const traderKeypair = Keypair.random();
console.log("Trader Public Key:", traderKeypair.publicKey());

await fundAccountWithFriendbot(traderKeypair.publicKey());
const traderAccount = await server.getAccount(traderKeypair.publicKey());

const pathPaymentTransaction = new TransactionBuilder(traderAccount, {
  fee: BASE_FEE,
  networkPassphrase: Networks.TESTNET
})
  .addOperation(Operation.changeTrust({
    asset: ekoLanceAsset,
    source: traderKeypair.publicKey()
  }))
  .addOperation(Operation.pathPaymentStrictReceive({
    sendAsset: Asset.native(),
    sendMax: '1000',
    destination: traderKeypair.publicKey(),
    destAsset: ekoLanceAsset,
    destAmount: '50',
    source: traderKeypair.publicKey()
  }))
  .setTimeout(30)
  .build();

pathPaymentTransaction.sign(traderKeypair);

try {
  const result = await server.sendTransaction(pathPaymentTransaction);
  console.log("Swap Performed. Transaction URL:", `https://stellar.expert/explorer/testnet/tx/${result.hash}`);
} catch (error) {
  console.log(`Error performing swap: ${error}`);
}

This code:

  1. Creates and funds a new trader account.

  2. Creates a transaction that first establishes trust for our custom asset.

  3. Then performs a path payment (swap) operation, exchanging the native asset (XLM) for our custom EkoLance asset.


Withdrawing from the Liquidity Pool

Finally, let's withdraw from the liquidity pool:

const lpWithdrawTransaction = new TransactionBuilder(defiAccount, {
  fee: BASE_FEE,
  networkPassphrase: Networks.TESTNET
})
  .addOperation(Operation.liquidityPoolWithdraw({
    liquidityPoolId: liquidityPoolId,
    amount: '50',
    minAmountA: '0',
    minAmountB: '0'
  }))
  .setTimeout(30)
  .build();

lpWithdrawTransaction.sign(defiKeypair);

try {
  const result = await server.sendTransaction(lpWithdrawTransaction);
  console.log("Withdrawal Successful. Transaction URL:", `https://stellar.expert/explorer/testnet/tx/${result.hash}`);
} catch (error) {
  console.log(`Error withdrawing from Liquidity Pool: ${error}`);
}

This code creates a transaction to withdraw funds from the liquidity pool.


Running the Application

At the end of your app.js file, add:

runDeFiOperations().catch(console.error);

This line calls our main function and logs any errors that occur.


Step 4: Running the Application

To run the application, use the following command in your terminal:

node app.js

This will execute all the DeFi operations in sequence. You'll see console output for each step, including transaction URLs that you can use to view the operations on the Stellar testnet explorer.


Adding a Frontend with Vite and Material UI

This guide provides a step-by-step process for creating a basic frontend for your Stellar liquidity pool application using Vite and Material UI. It includes explanations of each step and suggestions for further improvements.


Step 1: Set up the Vite project

1. Open your terminal and navigate to your project directory.

2. Run the following command to create a new Vite project:

npm create vite@latest stellar-defi-frontend -- --template react

3. Navigate into the new directory:

cd stellar-defi-frontend

4. Install dependencies:

npm install

Step 2: Install additional dependencies

Install Material UI and Stellar SDK:

npm install @mui/material @emotion/react @emotion/styled @stellar/stellar-sdk

Step 3: Set up the basic app structure

Replace the content of src/App.jsx with the following:

import React from 'react';
import { Container, Typography, Box } from '@mui/material';
import LiquidityPoolForm from './components/LiquidityPoolForm';

function App() {
  return (
    <Container maxWidth="sm">
      <Box sx={{ my: 4 }}>
        <Typography variant="h4" component="h1" gutterBottom>
          Stellar Liquidity Pool
        </Typography>
        <LiquidityPoolForm />
      </Box>
    </Container>
  );
}

export default App;

Step 4: Create the LiquidityPoolForm component

Create a new file src/components/LiquidityPoolForm.jsx:

import React, { useState } from 'react';
import { TextField, Button, Box } from '@mui/material';
import * as StellarSdk from '@stellar/stellar-sdk';

const server = new StellarSdk.Server('https://horizon-testnet.stellar.org');

function LiquidityPoolForm() {
  const [secretKey, setSecretKey] = useState('');
  const [amount, setAmount] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const sourceKeypair = StellarSdk.Keypair.fromSecret(secretKey);
      const sourceAccount = await server.loadAccount(sourceKeypair.publicKey());

      // Create a custom asset (replace with your asset details)
      const customAsset = new StellarSdk.Asset('CustomAsset', sourceKeypair.publicKey());

      // Create a liquidity pool asset
      const lpAsset = new StellarSdk.LiquidityPoolAsset(
        StellarSdk.Asset.native(),
        customAsset,
        StellarSdk.LiquidityPoolFeeV18
      );

      const transaction = new StellarSdk.TransactionBuilder(sourceAccount, {
        fee: StellarSdk.BASE_FEE,
        networkPassphrase: StellarSdk.Networks.TESTNET,
      })
        .addOperation(
          StellarSdk.Operation.changeTrust({
            asset: lpAsset,
          })
        )
        .addOperation(
          StellarSdk.Operation.liquidityPoolDeposit({
            liquidityPoolId: lpAsset.getLiquidityPoolId(),
            maxAmountA: amount,
            maxAmountB: amount,
            minPrice: { n: 1, d: 1 },
            maxPrice: { n: 1, d: 1 },
          })
        )
        .setTimeout(30)
        .build();

      transaction.sign(sourceKeypair);
      const result = await server.submitTransaction(transaction);
      console.log('Transaction successful:', result);
      alert('Liquidity added successfully!');
    } catch (error) {
      console.error('Error:', error);
      alert('Error adding liquidity. Check console for details.');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
        <TextField
          label="Secret Key"
          value={secretKey}
          onChange={(e) => setSecretKey(e.target.value)}
          required
        />
        <TextField
          label="Amount"
          type="number"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          required
        />
        <Button type="submit" variant="contained">
          Add Liquidity
        </Button>
      </Box>
    </form>
  );
}

export default LiquidityPoolForm; 

Step 5: Update the main.jsx file

Replace the content of src/main.jsx with:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { CssBaseline } from '@mui/material'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <CssBaseline />
    <App />
  </React.StrictMode>,
)

Step 6: Run the application

Start the development server:

npm run dev

Conclusion

Congratulations! You've just built and run a simple DeFi application on the Stellar network. This application demonstrates key DeFi concepts like:

  • Creating and funding accounts

  • Establishing liquidity pools

  • Performing asset swaps

  • Withdrawing from liquidity pools


Remember, this runs on the Stellar testnet, so no real assets are exchanged. In a real-world scenario, you'd need to consider additional factors like security, impermanent loss, user interfaces, and proper error handling.


As you continue your DeFi journey, explore more complex operations, implement proper error handling, and consider building a user interface for your application. Happy coding!


About EkoLance

EkoLance revolutionizes the future of work by empowering Web2 and blockchain professionals through its dual offerings. The first is an educational platform that provides quality and comprehensive training programs for upskilling in the blockchain space, ensuring that professionals are equipped with the latest industry knowledge and practical experience. We currently have a diverse community of over 10,000 blockchain professionals, including over 5000 Web3 developers proficient in Solidity and Rust. 


The second offering is our talent platform, techFiesta. It enables companies to launch hackathons, jobs, bounties, and onboard top-tier talent into their ecosystems, fostering innovation and growth. techFiesta has successfully organized over 30 online hackathons and developer challenges for major blockchain networks such as Gnosis chain, Celo, Solana, Concordium, etc.

If you like this article and want to read more educative articles for developers, click on the article link below:



Comments


bottom of page