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:
- Create a new Stellar wallet.
- View wallet details like balance.
- Send payments.
- 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:
- Open your terminal.
- Run the following commands:
npx create-react-app stellar-wallet
cd stellar-wallet
-
npx create-react-app stellar-wallet
: This command creates a new React project namedstellar-wallet
. -
cd stellar-wallet
: This command changes your working directory to the newly created project.
- 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
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:
- In your
src
directory, create a new file calledstellar.js
. - 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");
-
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:
- 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;
}
}
-
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:
- 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;
}
}
-
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:
- 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;
});
}
-
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:
- 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: [] };
}
}
- 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:
- Create a new file called
App.jsx
in thesrc
directory. - 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;
-
handleCreateWallet
: Calls thecreateAndFundWallet
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:
- Create a new file called
Account.jsx
in thesrc
directory. - 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;
- 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:
- Run the following command in your terminal:
npm start
- 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!