Create a react app that uses stellar blockchain to send and receive funds.

Create a react app that uses stellar blockchain to send and receive funds.

Introduction

What is blockchain

Blockchain is a distributed database or ledger that is shared among the nodes of a computer network.

What is stellar blockchain

Stellar is a payment protocol based on the distributed ledger technology. It allows quick, cross-border transactions between any pair of currencies. It is among the top 10 most popular blockchains and it is performing well in the cryptocurrency market.

According to their website, Stellar blockchain “is a platform that connects banks, payment systems, and people” and is intended to “move money quickly, reliably, and at almost no cost.”

Stellar was made to support digital representations of any currency, Native cryptocurrency for stellar is Stellar lumen (XLM) that powers the entire range of operations on the blockchain network.

Lumen is considered a stable coin since it can’t be minted and nearly 20 Billion lumens are out in the open market and is only increased by 1% annually.

Introduction to Stellar sdk

Stellar Software Development Kit is a library that lets you interact with stellar blockchain in your preferred language. Currently stellar maintains JavaScript, Java, and Go SDK, the rest of SDKs are maintained by a community of developers. In this article we will look into Javascript Stellar SDK Javascript Stellar SDK is a Javascript library for communicating with a Stellar Horizon server. It is used for building Stellar apps either on Node.js or in the browser.

Integrating Stellar SDK with reactjs

Create react app using

npx create-react-app stellar

Navigate to project folder and install stellar sdk package, run

npm install stellar-sdk

Now we have everything we need, let’s create magic. What does our end product do? A- Create new stellar wallets (non-custodial wallet) B- Allow user to enter wallet keys (public and secret keys) C- Fetch wallet details and balances D- Send funds to any blockchain E- Receive funds from any blockchain

Wallet creation

Stellar wallets, just like other wallets, contain addresses (public key) and secret key. Public key is the key that can be shared to anyone and can be used by anyone to verify your account and your transactions. Secret key is like a password, only the wallet owner uses it to perform different wallet operations such as sending funds to other wallets.

Before account creating we need to create wallet keys

import StellarSdk from "stellar-sdk";

// create new and unique key pair
const pair = StellarSdk.Keypair.random();

// public key
pair.publicKey();

// private key
pair.secret();

A valid keypair, however, does not make an account: in order to prevent unused accounts from bloating the ledger, Stellar requires accounts to hold a minimum balance of 1 XLM before they actually exist. Until it gets a bit of funding, your keypair doesn't warrant space on the ledger. In a live environment the next step should be acquiring XLM to activate it. On the test network, we can run the following function with the public key we created and get 10,000 XLM.

export async function createWallet() {
  try {
    const response = await fetch(
      `https://friendbot.stellar.org?addr=${encodeURIComponent(publicKey)}`
    );
    const data = await response.json();
        return {
          …data,
          secretKey: pair.secret()
        }
    } catch (e) {
        return e;
  }
}

Congratulations, you have created your first wallet. Lets integrate this with react, In the src folder create a file, call it stellar.js where we will put all our stellar operations. In the stellar.js file put all the codes above. In App.jsx import createWallet function, create a button that calls createWallet onClick ie.

import { useState } from "react";
import { createWallet } from "./stellar";

const App = () => {
  const [wallet, setWallet] = useState({
    publicKey: "",
    secretKey: "",
  });

  return (
    <div>
      {/* Conditional rendering */}
      {!wallet.publicKey ? (
        <button
          onClick={async () => {
            const data = await createWallet();
            if (data?.source_account) {
              setWallet({
                ...wallet,
                publicKey: data.source_account,
                secretKey: data.secretKey,
              });
            }
          }}
        >
          Create wallet
        </button>
      ) : (
        <div>{wallet.publicKey}</div>
      )}
    </div>
  );
};

export default App;

When a user clicks the button after a moment they should see the wallet public key displaying on their browser.

Lets allow users to enter their wallet details as well if they already have stellar accounts.

import { useState } from "react";
import { createWallet } from "./stellar";

const App = () => {
  const [loading, setLoading] = useState(false);
  const [wallet, setWallet] = useState({
    publicKey: "",
    secretKey: "",
  });
  return (
    <div>
      {/* Conditional rendering */}
      {!wallet.publicKey ? (
        <>
          {/* new user */}
          <button
            onClick={async () => {
              const data = await createWallet();
              setLoading(true);
              if (data?.source_account) {
                setWallet({
                  ...wallet,
                  publicKey: data.source_account,
                  secretKey: data.secretKey,
                });
              }
              setLoading(false);
            }}
            disabled={loading}
          >
            Create wallet
          </button>

          {/* existing user */}
          <h2>Or do you already have an account?</h2>
          <form
            onSubmit={async (e) => {
              e.preventDefault();
              setLoading(true);
              if (walletKeys.privateKey && walletKeys.publicKey) {
                if (newAccount?.balances) {
                  setWallet({
                    ...wallet,
                  });
                }
              }
              setLoading(false);
            }}
          >
            <div>
              <label htmlFor="publicKey">Public Key</label>
              <br />
              <input
                type="text"
                name="publicKey"
                onChange={(e) =>
                  setWallet({
                    ...wallet,
                    publicKey: e.target.value,
                  })
                }
              />
            </div>
            <div>
              <label htmlFor="publicKey">
                Private Key "Enter at your own risk"
              </label>
              <br />
              <input
                type="password"
                name="privateKey"
                onChange={(e) =>
                  setWallet({
                    ...wallet,
                    secretKey: e.target.value,
                  })
                }
              />
            </div>
            <button disabled={loading}>Submit</button>
          </form>
        </>
      ) : (
        <div>{wallet.publicKey}</div>
      )}
    </div>
  );
};

export default App;

Now that we have wallet details by creating a new wallet / allowing users to enter their details, lets load wallet balances and display it on the app.

In stellar.js, use stellar sdk to load stellar horizon server, and create a function that loads account details ie.

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

// get wallet details
export async function getAccount(publicKey) {
  try {
    const account = await server.accounts().accountId(publicKey).call();
    return account;
  } catch (error) {
    return error;
  }
}

Create another, call it Account in Account.jsx, this will be used to load and display wallet details when the wallet exists. In this file, import getAccount from stellar.js

import { useEffect } from "react";
import { useState } from "react";
import { getAccount } from "./stellar";

const Account = ({ wallet }) => {
  const [loading, setLoading] = useState(false);
  const [walletDetails, setWalletDetails] = useState(null);

  const fetchDetails = async () => {
    setLoading(true);
    const data = await getAccount(wallet?.publicKey);
    if (data?.balances) {
      setWalletDetails(data);
    }
    setLoading(false);
  };

  // fetch wallet details
  useEffect(() => {
    fetchDetails();
  }, [wallet]);

  return (
    <div>
      {loading ? (
        <p>Please Wait</p>
      ) : (
        <div>
          {/* display wallet balances */}
          <p>Your Balance: {walletDetails?.[0]?.balance} XLM</p>
          <p>Wallet Address: {wallet?.publicKey}</p>
        </div>
      )}
    </div>
  );
};

export default Account;

In App.jsx, import Account and

…
<Account wallet={wallet} />
…

Now that we can display wallet balance and public key, you can use the displayed address to deposit funds to your wallet from other accounts or blockchains. For testing we can use Stellar Laboratory to get free lumens.

For the final part of this article let's look at how we can use our wallets to send funds from the app we created. For this we’ll need a destination wallet, and a secret key from the sending wallet which we already have.

In the stellar.js file, create another function that runs when user sends funds

export async function sendFunds(destinationID, secretKey, amount) {
  try {
    const sourceKeys = StellarSdk.Keypair.fromSecret(secretKey);
    let transaction;
    server
      // checks if destination account exists
      .loadAccount(destinationID)
      // If the account is not found, surface a nicer error message for logging.
      .catch(function (error) {
        if (error instanceof StellarSdk.NotFoundError) {
          throw new Error("The destination account does not exist!");
        } else return error;
      })
      // If there was no error, load up-to-date information on your account.
      .then(function () {
        return server.loadAccount(sourceKeys.publicKey());
      })
      .then(function (sourceAccount) {
        // Start building the transaction.
        transaction = new StellarSdk.TransactionBuilder(sourceAccount, {
          fee: StellarSdk.BASE_FEE,
          networkPassphrase: StellarSdk.Networks.TESTNET,
        })
          .addOperation(
            StellarSdk.Operation.payment({
              destination: destinationID,
              // Because Stellar allows transaction in many currencies, you must
              // specify the asset type. The special "native" asset represents Lumens.
              asset: StellarSdk.Asset.native(),
              amount: amount.toString(),
            })
          )
          // A memo allows you to add your own metadata to a transaction. It's
          // optional and does not affect how Stellar treats the transaction.
          .addMemo(StellarSdk.Memo.text("Test Transaction"))
          // Wait a maximum of three minutes for the transaction
          .setTimeout(180)
          .build();
        // Sign the transaction to prove you are actually the person sending it.
        transaction.sign(sourceKeys);
        // And finally, send it off to Stellar!
        return server.submitTransaction(transaction);
      })
      .then(function (result) {
        return result;
      })
      .catch(function (error) {
        // If the result is unknown (no response body, timeout etc.) we simply resubmit
        // already built transaction:
        // server.submitTransaction(transaction);
        return error;
      });
  } catch (error) {
    return error;
  }
}

In Account.tsx, import sendFunds function and create a form that submits destination public key and amount

import { getAccount, sendFunds } from "./stellar";
…

…
const [destination, setDestination] = useState({
    publicKey: "",
    amount: 0,
  });
…

…
<p>Send Funds</p>
<form
    onSubmit={async (e) => {
      e.preventDefault();
      setLoading(true);
      const sent = await sendFunds(
        destination?.publicKey,
        wallet?.secretKey,
        destination?.amount
      );
      // unless an error, transactions always take few seconds / minutes to be completely done
      setLoading(false);
      console.log(sending);
    }}
  >
    <input
      type="text"
      name="publicKey"
      onChange={(e) =>
        setDestination({
          ...destination,
          publicKey: e.target.value,
        })
      }
    />
    <input
      type="number"
      name="amount"
      onChange={(e) =>
        setDestination({
          ...destination,
          amount: e.target.value,
        })
      }
    />
    <button disabled={loading}>Send Payments</button>
  </form>

If you click on Send Funds after few minutes the destination address should receive funds

Combining the Account.tsx codes we have

import { useEffect } from "react";
import { useState } from "react";
import { getAccount, sendFunds } from "./stellar";

const Account = ({ wallet }) => {
  const [loading, setLoading] = useState(false);
  const [walletDetails, setWalletDetails] = useState(null);

  const [destination, setDestination] = useState({
    publicKey: "",
    amount: 0,
  });

  const fetchDetails = async () => {
    setLoading(true);
    const data = await getAccount(wallet?.publicKey);
    if (data?.balances) {
      setWalletDetails(data);
    }
    setLoading(false);
  };

  // fetch wallet details
  useEffect(() => {
    fetchDetails();
  }, [wallet]);

  return (
    <div>
      {loading ? (
        <p>Please Wait</p>
      ) : (
        <div>
          {/* display wallet balances */}
          <p>Your Balance: {walletDetails?.[0]?.balance} XLM</p>
          <p>Wallet Address: {wallet?.publicKey}</p>
          <p>Send Funds</p>
          <form
            onSubmit={async (e) => {
              e.preventDefault();
              setLoading(true);
              const sent = await sendFunds(
                destination?.publicKey,
                wallet?.secretKey,
                destination?.amount
              );
              // unless an error, transactions always take few seconds / minutes to be completely done
              setLoading(false);
              console.log(sending);
            }}
          >
            <input
              type="text"
              name="publicKey"
              onChange={(e) =>
                setDestination({
                  ...destination,
                  publicKey: e.target.value,
                })
              }
            />
            <input
              type="number"
              name="amount"
              onChange={(e) =>
                setDestination({
                  ...destination,
                  amount: e.target.value,
                })
              }
            />
            <button disabled={loading}>Send Payments</button>
          </form>
        </div>
      )}
    </div>
  );
};

export default Account;

The adding some css, the final product may look like this:-

Stellar Wallet tutorial

If you like this article there are more like this in our blogs, follow us on dev.to/clickpesa, medium.com/clickpesa-engineering-blog and clickpesa.hashnode.dev

Happy Hacking!!