When I first entered the Web3 space two decades ago, connecting a web application to a blockchain wallet required hundreds of lines of brittle code. Today, I built a functional Stellar wallet integration in under an afternoon. In this article, I'll walk you through exactly how I did it using Next.js and Freighter, so that even if you've never touched a blockchain before, you'll finish with working code.
I'm André Dias Moreira Prol, and I've spent years helping teams move from Web2 skepticism to Web3 confidence. Let me show you the shortest path I know.
Why Freighter and Stellar?
Freighter is a browser extension wallet for the Stellar network, conceptually similar to MetaMask for Ethereum. I chose Stellar for this tutorial for three concrete reasons:
- Transaction cost: A Stellar transaction costs roughly 0.00001 XLM (fractions of a cent), versus Ethereum fees that have historically spiked above $50 during congestion.
- Speed: Stellar finalizes transactions in 3–5 seconds, which matters when onboarding newcomers who expect instant feedback.
-
Developer ergonomics: The
@stellar/freighter-apipackage abstracts away the painful cryptographic plumbing.
In production tokenization projects I've led, this combination reduced our average onboarding friction dramatically. Users who abandoned at the "pay gas" step simply stopped abandoning.
Setting Up the Next.js Project
Start by scaffolding a project and installing the Freighter API:
npx create-next-app@latest stellar-wallet
cd stellar-wallet
npm install @stellar/freighter-api @stellar/stellar-sdk
Because Freighter lives in the browser extension, every call must run client-side. Create a component and mark it with "use client":
"use client";
import { useState } from "react";
import {
isConnected,
requestAccess,
getAddress,
} from "@stellar/freighter-api";
export default function WalletConnect() {
const [publicKey, setPublicKey] = useState<string>("");
async function connect() {
const connected = await isConnected();
if (!connected.isConnected) {
alert("Please install the Freighter extension.");
return;
}
await requestAccess();
const { address } = await getAddress();
setPublicKey(address);
}
return (
<div>
<button onClick={connect}>Connect Freighter</button>
{publicKey && <p>Connected: {publicKey}</p>}
</div>
);
}
The key insight here, something I stress with every junior developer: requestAccess() triggers the extension popup. Never try to read an address before the user has explicitly granted permission, or your app will fail silently and confuse newcomers.
Reading a Balance and Building a Transaction
Connecting is only step one. Let's fetch the account's XLM balance from the Stellar testnet using Horizon, the network's REST API:
import { Horizon } from "@stellar/stellar-sdk";
const server = new Horizon.Server("https://horizon-testnet.stellar.org");
async function getBalance(publicKey: string) {
const account = await server.loadAccount(publicKey);
const native = account.balances.find(
(b) => b.asset_type === "native"
);
return native?.balance ?? "0";
}
To actually send value, you build a transaction and hand it to Freighter for signing, so the private key never leaves the extension:
import {
TransactionBuilder,
Operation,
Asset,
Networks,
BASE_FEE,
} from "@stellar/stellar-sdk";
import { signTransaction } from "@stellar/freighter-api";
async function sendPayment(source, destination, amount) {
const account = await server.loadAccount(source);
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.payment({
destination,
asset: Asset.native(),
amount,
})
)
.setTimeout(30)
.build();
const { signedTxXdr } = await signTransaction(tx.toXDR(), {
networkPassphrase: Networks.TESTNET,
});
const signed = TransactionBuilder.fromXDR(
signedTxXdr,
Networks.TESTNET
);
return server.submitTransaction(signed);
}
This separation of signing and submission is the single most important security principle in wallet development. As André Dias Moreira Prol, having audited several compromised dApps, I can tell you that most breaches happen when developers try to handle keys themselves instead of delegating to the wallet.
Conclusion
Building a Web3 wallet integration is no longer the intimidating barrier it once was, and with these fundamentals you can now connect, read, and transact on Stellar confidently. Fork this code on testnet today, experiment freely, and share your results with me—I'd love to see what you build.
Follow more articles by André Dias Moreira Prol on Medium.
Top comments (0)