DEV Community

Cover image for Stellar Wallet App: A Step-by-Step Guide from Zero to Hero
chintanonweb
chintanonweb

Posted on

Stellar Wallet App: A Step-by-Step Guide from Zero to Hero

Step of building the Stellar wallet app with detailed explanations. This way, even if you're a beginner, you'll be able to understand what each part of the code does and how it fits into the overall project.

Overview

In this guide, we'll create a Stellar wallet app using React. The app will allow users to:

  1. Create a new Stellar wallet.
  2. View wallet details like balance.
  3. Send payments.
  4. View transaction history.

We'll be using the Stellar SDK to interact with the Stellar network. This SDK provides all the tools needed to create and manage Stellar accounts, send transactions, and retrieve account information.

Step 1: Setting Up Your React Project

Explanation:
We start by setting up a React project using Create React App, which is a popular tool for creating new React projects with a good default setup.

Steps:

  1. Open your terminal.
  2. Run the following commands:
npx create-react-app stellar-wallet
cd stellar-wallet
Enter fullscreen mode Exit fullscreen mode
  • npx create-react-app stellar-wallet: This command creates a new React project named stellar-wallet.
  • cd stellar-wallet: This command changes your working directory to the newly created project.
  1. Now, we need to install the Stellar SDK, which is a library that will allow us to interact with the Stellar blockchain.
npm install @stellar/stellar-sdk
Enter fullscreen mode Exit fullscreen mode

Step 2: Creating the Stellar Service File

Explanation:
This file (stellar.js) will contain all the functions that interact with the Stellar network. By separating these functions into their own file, we keep our code organized and easy to manage.

Steps:

  1. In your src directory, create a new file called stellar.js.
  2. Add the following code to stellar.js:
import * as StellarSdk from "@stellar/stellar-sdk";

// Set up the Stellar server for the testnet
const server = new StellarSdk.Horizon.Server(("https://horizon-testnet.stellar.org");
Enter fullscreen mode Exit fullscreen mode
  • import * as StellarSdk from "@stellar/stellar-sdk";: This imports the Stellar SDK so we can use its functionality.
  • const server = new StellarSdk.Horizon.Server(("https://horizon-testnet.stellar.org");: This sets up a connection to the Stellar test network. The testnet is a safe environment where you can experiment without risking real money.

Function: Creating and Funding a Wallet

Explanation:
This function creates a new Stellar wallet (a pair of public and private keys) and funds it with some testnet tokens using the Friendbot service.

Steps:

  1. Add the following function to stellar.js:
export async function createAndFundWallet() {
  const pair = StellarSdk.Keypair.random(); // Generate a random pair of public and secret keys
  const publicKey = pair.publicKey(); // Extract the public key
  const secretKey = pair.secret(); // Extract the secret key

  try {
    // Fund the new account using Friendbot
    const response = await fetch(
      `https://friendbot.stellar.org?addr=${encodeURIComponent(publicKey)}`
    );
    await response.json();

    return { publicKey, secretKey };
  } catch (error) {
    console.error("Failed to create and fund wallet:", error);
    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • const pair = StellarSdk.Keypair.random();: This generates a new random keypair (a public key and a secret key).
  • const publicKey = pair.publicKey();: The public key is like your account number—it's used to receive payments.
  • const secretKey = pair.secret();: The secret key is like your password—keep it safe because anyone with this key can access your account.
  • The Friendbot service provides testnet tokens to any public key, which allows us to create and fund new accounts for free on the testnet.

Function: Getting Account Details

Explanation:
This function retrieves details about a Stellar account, such as its balance.

Steps:

  1. Add the following function to stellar.js:
export async function getAccount(publicKey) {
  try {
    const account = await server.loadAccount(publicKey);
    return account;
  } catch (error) {
    console.error("Error loading account:", error);
    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • const account = await server.loadAccount(publicKey);: This fetches the account details from the Stellar network using the public key.

Function: Sending Funds

Explanation:
This function sends funds (in lumens, the native currency of Stellar) from one account to another.

Steps:

  1. Add the following function to stellar.js:
export async function sendFunds(destinationID, secretKey, amount) {
  const sourceKeys = StellarSdk.Keypair.fromSecret(secretKey); // Generate keypair from secret key
  let transaction;

  return server
    .loadAccount(destinationID)
    .catch((error) => {
      if (error instanceof StellarSdk.NotFoundError) {
        throw new Error("The destination account does not exist!");
      } else {
        throw error;
      }
    })
    .then(() => server.loadAccount(sourceKeys.publicKey()))
    .then((sourceAccount) => {
      transaction = new StellarSdk.TransactionBuilder(sourceAccount, {
        fee: StellarSdk.BASE_FEE,
        networkPassphrase: StellarSdk.Networks.TESTNET,
      })
        .addOperation(
          StellarSdk.Operation.payment({
            destination: destinationID,
            asset: StellarSdk.Asset.native(),
            amount: amount.toString(),
          })
        )
        .addMemo(StellarSdk.Memo.text("Test Transaction"))
        .setTimeout(180)
        .build();

      transaction.sign(sourceKeys);
      return server.submitTransaction(transaction);
    })
    .then((result) => result)
    .catch((error) => {
      console.error("Error submitting transaction:", error);
      throw error;
    });
}
Enter fullscreen mode Exit fullscreen mode
  • const sourceKeys = StellarSdk.Keypair.fromSecret(secretKey);: This generates the keypair from your secret key, which allows you to sign transactions.
  • The transaction is built using the TransactionBuilder, which allows us to specify the details of the transaction (e.g., the recipient, amount, etc.).

Function: Fetching Payments

Explanation:
This function fetches a list of transactions for a given account, including payments sent and received.

Steps:

  1. Add the following function to stellar.js:
export async function fetchPayments(accountId) {
  try {
    const response = await fetch(
      `https://horizon-testnet.stellar.org/accounts/${accountId}/operations`
    );
    const data = await response.json();

    const payments = data._embedded.records.map((op) => ({
      type: op.type,
      amount: op.amount,
      asset: op.asset_type === "native" ? "lumens" : `${op.asset_code}:${op.asset_issuer}`,
      from: op.from,
      to: op.to,
      timestamp: op.created_at,
    }));

    const sentPayments = payments.filter((payment) => payment.from === accountId);
    const receivedPayments = payments.filter((payment) => payment.to === accountId);

    return { sentPayments, receivedPayments };
  } catch (error) {
    console.error("Error fetching payments:", error);
    return { sentPayments: [], receivedPayments: [] };
  }
}
Enter fullscreen mode Exit fullscreen mode
  • This function makes a request to the Horizon API to retrieve all operations (transactions) related to a given account.

Step 3: Building the React Components

Now that we have our Stellar service functions, let's build the React components that will use them.

3.1 The App.jsx Component

Explanation:
This is the main component of our application. It handles the creation of a new wallet or the use of an existing one.

Steps:

  1. Create a new file called App.jsx in the src directory.
  2. Add the following code:
import { useState } from "react";
import { createAndFundWallet } from "./stellar";
import Account from './Account';

const App = () => {
  const [loading, setLoading] = useState(false);
  const [wallet, setWallet] = useState({ publicKey: "", secretKey: "" });
  const [inputKeys, setInputKeys] = useState({ publicKey: "", secretKey: "" });

  const handleCreateWallet = async () => {
    setLoading(true);
    try {
      const { publicKey, secretKey } = await createAndFundWallet();
      setWallet({ publicKey, secretKey });
    } catch (error) {
      console.error("Failed to create and fund wallet:", error);
    }
    setLoading(false);
  };

  const handleUseExistingWallet = () => {
    if (inputKeys.publicKey && inputKeys.secretKey) {
      setWallet(inputKeys);
    } else {
      alert("Please enter both public and secret keys.");
    }
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    setInputKeys((prev) => ({ ...prev, [name]: value }));
  };

  return (
    <div>
      {!wallet.publicKey ? (
        <>
          <button onClick={handleCreateWallet} disabled={loading}>
            Create Wallet
          </button>
          <div>
            <h3>Or Use an Existing Wallet</h3>
            <input
              type="text"
              name="publicKey"
              placeholder="Enter Public Key"
              value={inputKeys.publicKey}
              onChange={handleChange}
            />
            <input
              type="text"
              name="secretKey"
              placeholder

="Enter Secret Key"
              value={inputKeys.secretKey}
              onChange={handleChange}
            />
            <button onClick={handleUseExistingWallet} disabled={loading}>
              Use Wallet
            </button>
          </div>
        </>
      ) : (
        <Account publicKey={wallet.publicKey} secretKey={wallet.secretKey} />
      )}
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode
  • handleCreateWallet: Calls the createAndFundWallet function to generate a new wallet and sets the returned public and secret keys in the state.
  • handleUseExistingWallet: Allows the user to enter their existing public and secret keys.

3.2 The Account.jsx Component

Explanation:
This component displays the wallet's details, sends payments, and shows transaction history.

Steps:

  1. Create a new file called Account.jsx in the src directory.
  2. Add the following code:
import { useState, useEffect } from "react";
import { getAccount, sendFunds, fetchPayments } from "./stellar";

const Account = ({ publicKey, secretKey }) => {
  const [account, setAccount] = useState(null);
  const [transactions, setTransactions] = useState([]);
  const [destination, setDestination] = useState({ id: "", amount: "" });
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const loadAccountData = async () => {
      try {
        const accountData = await getAccount(publicKey);
        setAccount(accountData);
      } catch (error) {
        console.error("Failed to load account data:", error);
      }
    };

    loadAccountData();
  }, [publicKey]);

  useEffect(() => {
    const loadPayments = async () => {
      try {
        const { receivedPayments } = await fetchPayments(publicKey);
        setTransactions(receivedPayments);
      } catch (error) {
        console.error("Failed to fetch payments:", error);
      }
    };

    loadPayments();
  }, [publicKey]);

  const handleSendFunds = async (e) => {
    e.preventDefault();
    setLoading(true);
    try {
      await sendFunds(destination.id, secretKey, destination.amount);
      alert("Funds sent successfully!");
    } catch (error) {
      console.error("Failed to send funds:", error);
      alert("Failed to send funds. See console for details.");
    }
    setLoading(false);
  };

  return (
    <div>
      <h3>Wallet Details</h3>
      {account ? (
        <div>
          <p>
            <b>Public Key:</b> {publicKey}
          </p>
          <p>
            <b>Balance:</b>{" "}
            {account.balances.map((balance, index) => (
              <span key={index}>
                {balance.balance} {balance.asset_type === "native" ? "lumens" : balance.asset_code}
              </span>
            ))}
          </p>
          <hr />
          <h4>Send Funds</h4>
          <form onSubmit={handleSendFunds}>
            <input
              type="text"
              placeholder="Recipient Public Key"
              value={destination.id}
              onChange={(e) =>
                setDestination({ ...destination, id: e.target.value })
              }
            />
            <input
              type="number"
              placeholder="Amount"
              value={destination.amount}
              onChange={(e) =>
                setDestination({ ...destination, amount: e.target.value })
              }
            />
            <button disabled={loading} type="submit">
              Send Funds
            </button>
          </form>
          <hr />
          <div>
            <h4>Transactions</h4>
            <div>
              {transactions.length ? (
                transactions.map((transaction, index) => (
                  <div key={index}>
                    <p>
                      <b>Type:</b> {transaction.type}
                    </p>
                    <p>
                      <b>Amount:</b> {transaction.amount} {transaction.asset}
                    </p>
                    <p>
                      <b>To:</b> {transaction.to}
                    </p>
                    <p>
                      <b>Timestamp:</b> {transaction.timestamp}
                    </p>
                  </div>
                ))
              ) : (
                <p>No transactions found.</p>
              )}
            </div>
          </div>
        </div>
      ) : (
        <p>Loading account data...</p>
      )}
    </div>
  );
};

export default Account;
Enter fullscreen mode Exit fullscreen mode
  • The Account component displays the wallet's public key and balance.
  • It includes a form to send funds by entering a recipient's public key and the amount to send.
  • It also shows a list of transactions that were received by the account.

Step 4: Running Your App

Explanation:
Now that everything is set up, we can start the React application and see our Stellar wallet in action.

Steps:

  1. Run the following command in your terminal:
npm start
Enter fullscreen mode Exit fullscreen mode
  1. Open a web browser and go to http://localhost:3000. You should see your Stellar wallet app!

What You'll See:

  • A button to create a new wallet and inputs to use an existing wallet.
  • After creating or using a wallet, you'll see the wallet's public key, balance, a form to send funds, and a list of transactions.

Additional Enhancements

Once you've completed the basic setup, you can explore additional features such as:

  • Adding better error handling and validation.
  • Enhancing the user interface with more styling and animations.
  • Integrating additional Stellar network features like multi-signature accounts or trustlines.

This step-by-step guide should give you a solid foundation for building and understanding a Stellar wallet app using React!

Top comments (2)

Collapse
 
jjbb profile image
Jason Burkes

This is a fantastic guide for anyone looking to get started with Stellar and React. I appreciate the detailed explanations throughout. I'm curious, though—would you consider doing a follow-up post on handling real-world use cases like creating multi-signature accounts?

Collapse
 
chintanonweb profile image
chintanonweb

Tysm! Will try