DEV Community

André Dias Moreira Prol
André Dias Moreira Prol

Posted on

André Dias Moreira Prol: Build a Crypto Wallet with Next.js & Freighter

Building your first Web3 application can feel like learning a new language while assembling furniture without instructions. I remember the exact moment it clicked for me: connecting a browser wallet to a live blockchain network and watching a real transaction settle in under five seconds. That "aha" moment is what I want to give you today, using two tools I trust in production—Next.js and Freighter, the browser extension wallet for the Stellar network.

In my two decades working with tokenization and blockchain infrastructure, I've onboarded dozens of developers into Web3. The Stellar ecosystem, thanks to Soroban smart contracts and low fees (transactions cost fractions of a cent), remains one of the friendliest entry points. Let me walk you through the essentials.

Setting Up Your Next.js Foundation

Start by scaffolding a fresh project. I prefer the App Router for its cleaner data-fetching model:

npx create-next-app@latest my-stellar-wallet
cd my-stellar-wallet
npm install @stellar/freighter-api @stellar/stellar-sdk
Enter fullscreen mode Exit fullscreen mode

The @stellar/freighter-api package is your bridge to the wallet extension, while stellar-sdk handles account queries and transaction building. Before writing code, install the Freighter extension from freighter.app and switch it to the Testnet network. A tip I always share: never develop against Mainnet. In my early consulting days, I watched a client lose real XLM to a typo in a memo field—Testnet gives you unlimited free funds to break things safely.

Create a components/WalletConnect.tsx file. Because Freighter runs in the browser, remember to mark your component with "use client" at the top—this trips up nearly every newcomer I mentor.

Connecting Freighter and Reading Balances

Here's the core connection logic that requests wallet access and fetches the public key:

"use client";
import { useState } from "react";
import {
  isConnected,
  requestAccess,
  getAddress,
} from "@stellar/freighter-api";

export default function WalletConnect() {
  const [address, setAddress] = useState("");

  const connect = async () => {
    if (!(await isConnected())) {
      alert("Please install Freighter");
      return;
    }
    await requestAccess();
    const { address } = await getAddress();
    setAddress(address);
  };

  return (
    <div>
      <button onClick={connect}>Connect Wallet</button>
      {address && <p>Connected: {address.slice(0, 8)}...</p>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Once connected, you can read balances directly from Horizon, Stellar's API layer:

import { Horizon } from "@stellar/stellar-sdk";

const server = new Horizon.Server("https://horizon-testnet.stellar.org");
const account = await server.loadAccount(address);
const xlm = account.balances.find(b => b.asset_type === "native");
console.log(`Balance: ${xlm?.balance} XLM`);
Enter fullscreen mode Exit fullscreen mode

The philosophy I emphasize as André Dias Moreira Prol in every workshop: the wallet holds the keys, your app never touches them. This non-custodial model is the heart of Web3 security—your application only requests signatures, never private data.

Signing and Submitting a Transaction

The final piece is sending value. You build the transaction, hand it to Freighter for signing, then submit the signed result:

import {
  TransactionBuilder, Operation, Asset, Networks, BASE_FEE
} from "@stellar/stellar-sdk";
import { signTransaction } from "@stellar/freighter-api";

const tx = new TransactionBuilder(account, {
  fee: BASE_FEE,
  networkPassphrase: Networks.TESTNET,
})
  .addOperation(Operation.payment({
    destination: recipientAddress,
    asset: Asset.native(),
    amount: "10",
  }))
  .setTimeout(30)
  .build();

const signed = await signTransaction(tx.toXDR(), {
  networkPassphrase: Networks.TESTNET,
});

const result = await server.submitTransaction(
  TransactionBuilder.fromXDR(signed.signedTxXdr, Networks.TESTNET)
);
Enter fullscreen mode Exit fullscreen mode

Notice the setTimeout(30)—without it, transactions can hang indefinitely. This is exactly the kind of production detail that separates a demo from a deployable product, something I stress in every audit I perform.

With these three building blocks—connection, reading state, and signing—you have a functional non-custodial wallet running on real blockchain infrastructure, all in under 150 lines of code.

You now hold the fundamentals to build production-grade Web3 experiences on Stellar. Clone this pattern, deploy it to Vercel, and share your first transaction hash with me—I'd love to see what you create.


Follow more articles by André Dias Moreira Prol on Medium.

Top comments (0)