A comprehensive guide for contributors on how StellarSplit integrates with the Stellar network and Freighter wallet.
- Introduction to Stellar
- Freighter Wallet
- Connecting to Freighter
- Building and Signing Transactions
- Verifying Payments on Horizon
- Testnet vs Mainnet
- Funding Your Testnet Wallet
Stellar is a decentralized, open-source blockchain network designed for fast, low-cost cross-border payments and asset issuance. It enables:
- Fast transactions: 3-5 second settlement time
- Low fees: Average transaction cost is fractions of a cent
- Multi-currency support: Native XLM and custom assets (like USDC)
- Built-in decentralized exchange: Path payments for currency conversion
XLM is the native cryptocurrency of the Stellar network:
- Symbol/Ticker: XLM
- Purpose: Pay transaction fees, maintain minimum account balance (1 XLM)
- Divisibility: 7 decimal places (1 XLM = 10,000,000 stroops)
USDC is a stablecoin pegged to the US Dollar, issued on Stellar by Circle:
- Asset Code: USDC
- Issuer:
GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN - Benefits: Price stability, fast settlement, low fees
| Concept | Description |
|---|---|
| Account | A Stellar address starting with 'G' (e.g., GABCD...) |
| Trustline | Authorization to hold a specific asset (required for USDC) |
| Sequence Number | Unique number for each transaction to prevent replays |
| Memo | Optional note attached to transactions |
Freighter is a browser extension wallet for Stellar that allows users to:
- Store and manage Stellar accounts securely
- Sign transactions without exposing private keys
- Switch between testnet and mainnet networks
- View account balances and transaction history
-
Chrome/Brave/Edge:
https://chromewebstore.google.com/detail/freighter/bcacfldlkkdogcmkkibnjlakofdplcbk -
Firefox:
https://addons.mozilla.org/en-US/firefox/addon/freighter/ -
Verify Installation: Look for the Freighter icon in your browser toolbar after installation
-
Create a New Account:
- Click the Freighter icon
- Select "Create a new wallet"
- Save your recovery phrase securely
-
Switch to Testnet:
- Open Freighter settings
- Change network from "Mainnet" to "Testnet"
- This allows testing without real funds
-
Fund Your Testnet Account:
- Use Friendbot to get free test XLM
StellarSplit uses the official Freighter API package:
npm install @stellar/freighter-apiimport { isFreighterInstalled } from '../config/walletConfig'
const installed = await isFreighterInstalled()
if (!installed) {
console.log('Please install Freighter wallet')
}Implementation Details:
- Uses
postMessagehandshake to detect the extension - Retries up to 4 times with increasing timeouts
- Checks for
window.freighterorwindow.freighterApi
import { connectWallet } from '../config/walletConfig'
try {
const publicKey = await connectWallet()
console.log('Connected:', publicKey) // GABCD...
} catch (error) {
console.error('Connection failed:', error)
}What Happens:
- Freighter popup appears asking user permission
- User approves connection
- Returns the public key (Stellar address)
- App can now request transaction signing
import { getWalletPublicKey } from '../config/walletConfig'
try {
const publicKey = await getWalletPublicKey()
console.log('Already connected:', publicKey)
} catch (error) {
console.log('Not connected')
}import { getFreighterNetworkPassphrase } from '../config/walletConfig'
const passphrase = await getFreighterNetworkPassphrase()
// Returns: "Test SDF Network ; September 2015" (testnet)
// Or: "Public Global Stellar Network ; September 2015" (mainnet)StellarSplit provides a React hook for wallet state management:
import { useWallet } from '../hooks/use-wallet'
function MyComponent() {
const {
publicKey, // Connected wallet address
activeUserId, // User ID considering session state
isConnected, // Boolean connection status
isConnecting, // Connecting state
isRefreshing, // Refreshing state
hasFreighter, // Extension installed?
error, // Error message
networkPassphrase, // Soroban target network passphrase
requiredNetworkPassphrase,// Expected network passphrase
requiredNetworkLabel, // Expected network label (e.g. Testnet)
rpcUrl, // RPC URL
horizonUrl, // Horizon URL
walletNetworkPassphrase, // Current Freighter network passphrase
walletNetworkLabel, // Current Freighter network label
isOnAllowedNetwork, // Valid network?
canTransact, // publicKey && isOnAllowedNetwork
lastConnectedAccount, // Session persistent account
connect, // Connect function
disconnect, // Disconnect function
refresh, // Re-fetch state and network (used for tab sync / recovery)
clearError, // Clear current error
signTransaction, // Sign transaction function (checks network match first)
} = useWallet()
return (
<div>
<button onClick={connect} disabled={isConnecting || isRefreshing}>
{isConnected ? publicKey : 'Connect Wallet'}
</button>
{!canTransact && isConnected && (
<p>Please switch Freighter to {requiredNetworkLabel}. Currently on {walletNetworkLabel}.</p>
)}
</div>
)
}The app includes a pre-built wallet button:
import { WalletButton } from '../components/wallet-button'
function Header() {
return <WalletButton />
}Features:
- Shows "Install Freighter" if not installed
- Shows "Connecting..." during connection
- Displays shortened address when connected
- Shows "Wrong Network" if on unsupported network
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Backend │────▶│ Build Tx │────▶│ Freighter │────▶│ Submit │
│ (Server) │ │ (XDR) │ │ (Sign) │ │ (Horizon) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
The backend builds unsigned transactions using the Stellar SDK:
import { Horizon, Asset, TransactionBuilder, Operation, Networks } from '@stellar/stellar-sdk'
// Initialize Horizon server
const server = new Horizon.Server('https://horizon-testnet.stellar.org')
// Load source account (to get current sequence number)
const account = await server.loadAccount(sourcePublicKey)
// Build transaction
const transaction = new TransactionBuilder(account, {
fee: '100', // Base fee in stroops
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.payment({
destination: destinationPublicKey,
asset: Asset.native(), // XLM
amount: '10.5', // Amount in XLM
})
)
.setTimeout(180) // 3 minutes
.build()
// Convert to XDR (External Data Representation)
const transactionXDR = transaction.toXDR()The frontend requests Freighter to sign the transaction:
import { signWithFreighter } from '../config/walletConfig'
// transactionXDR comes from backend
const signedXDR = await signWithFreighter(transactionXDR, networkPassphrase)What Happens:
- Freighter popup opens showing transaction details
- User reviews amount, destination, and fee
- User enters password (if required)
- Freighter signs with private key (never exposed to app)
- Returns signed transaction XDR
Submit the signed transaction to the Stellar network:
import { Horizon } from '@stellar/stellar-sdk'
const server = new Horizon.Server('https://horizon-testnet.stellar.org')
// Decode signed XDR
const transaction = Horizon.TransactionBuilder.fromXDR(signedXDR, Networks.TESTNET)
// Submit to network
const result = await server.submitTransaction(transaction)
console.log('Transaction hash:', result.hash)StellarSplit supports automatic currency conversion via path payments:
// Example: Pay 10 USDC using XLM (automatic conversion)
const transaction = new TransactionBuilder(account, {
fee: '100',
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.pathPaymentStrictReceive({
sendAsset: Asset.native(), // Send XLM
sendMax: '50', // Max XLM to send
destination: destinationPublicKey,
destAsset: new Asset('USDC', usdcIssuer), // Receive USDC
destAmount: '10', // Exactly 10 USDC
path: [], // Auto-find path
})
)
.setTimeout(180)
.build()Key Points:
sendMax: Maximum amount willing to spend (slippage protection)destAmount: Exact amount recipient receives- Stellar automatically finds the best conversion path
StellarSplit supports Stellar Payment Protocol URIs for deep linking:
import { buildStellarPaymentURI } from '../utils/stellar/paymentUri'
const uri = buildStellarPaymentURI({
destination: 'GABCD...',
amount: 10.5,
assetCode: 'USDC',
assetIssuer: 'GA5ZSE...',
memo: 'Split payment #123',
memoType: 'text',
})
// Result: web+stellar:pay?destination=GABCD...&amount=10.5&asset_code=USDC...For more details on how these URIs are processed, including cross-device QR code scanning and checkout state handling, see the Payment URI and Checkout Flow Guide.
Horizon is Stellar's API server that provides:
- Transaction submission
- Account information
- Transaction history
- Payment path finding
Endpoints:
- Testnet:
https://horizon-testnet.stellar.org - Mainnet:
https://horizon.stellar.org
When a user submits a payment, the backend verifies it:
import { Horizon } from '@stellar/stellar-sdk'
async function verifyTransaction(txHash: string) {
const server = new Horizon.Server('https://horizon-testnet.stellar.org')
// 1. Fetch transaction
const transaction = await server
.transactions()
.transaction(txHash)
.call()
// 2. Check if successful
if (!transaction.successful) {
throw new Error('Transaction failed')
}
// 3. Get operations (payment details)
const operations = await server
.operations()
.forTransaction(txHash)
.call()
// 4. Find payment operation
const payment = operations.records.find(op =>
op.type === 'payment' || op.type.includes('path_payment')
)
return {
valid: true,
amount: payment.amount,
asset: payment.asset_type === 'native' ? 'XLM' : payment.asset_code,
sender: transaction.source_account,
receiver: payment.to,
timestamp: transaction.created_at,
}
}The app includes a StellarService for verification:
// backend/src/stellar/stellar.service.ts
@Injectable()
export class StellarService {
async verifyTransaction(txHash: string) {
// Fetches transaction from Horizon
// Validates success status
// Extracts payment operation details
// Returns structured payment info
}
async isAccountActive(accountId: string): Promise<boolean> {
// Checks if account exists on network
}
async getAccountDetails(accountId: string) {
// Returns account balances, sequence, etc.
}
}User submits payment ──▶ Backend receives txHash
│
▼
┌─────────────────────┐
│ Call Horizon API │
│ Get transaction │
└─────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Check exists Check success Get operations
│ │ │
└───────────────┴───────────────┘
│
▼
┌─────────────────────┐
│ Validate against │
│ expected amount │
└─────────────────────┘
│
▼
Update participant status
| Feature | Testnet | Mainnet |
|---|---|---|
| Purpose | Development & testing | Production use |
| Value | Test XLM has no value | Real XLM has monetary value |
| Funding | Free via Friendbot | Must purchase |
| Stability | May be reset occasionally | Permanent |
| URL | horizon-testnet.stellar.org | horizon.stellar.org |
| Passphrase | "Test SDF Network ; September 2015" | "Public Global Stellar Network ; September 2015" |
- Click the Freighter icon
- Click the settings gear
- Select "Network"
- Choose "Testnet" or "Mainnet"
// Configuration
const NETWORKS = {
TESTNET: {
url: 'https://horizon-testnet.stellar.org',
passphrase: 'Test SDF Network ; September 2015',
},
MAINNET: {
url: 'https://horizon.stellar.org',
passphrase: 'Public Global Stellar Network ; September 2015',
},
}
// Use environment variable
const network = process.env.STELLAR_NETWORK === 'mainnet'
? NETWORKS.MAINNET
: NETWORKS.TESTNET# .env file
STELLAR_NETWORK=testnet # or 'mainnet' for production- Never use mainnet for development: Always test on testnet first
- Validate network: Check user's wallet network matches app expectation
- Clear separation: Use different database/environment for testnet vs mainnet
- User warnings: Clearly indicate which network is active in UI
Friendbot is a service that funds testnet accounts with free XLM for development.
- Get your testnet public key from Freighter (starts with 'G')
- Visit:
https://laboratory.stellar.org/#account-creator?network=test - Enter your public key
- Click "Create account"
curl -X POST https://friendbot.stellar.org?addr=YOUR_PUBLIC_KEYExample:
curl -X POST https://friendbot.stellar.org?addr=GDZST3XVCDTUJ76ZAV2HA72KYQODXXZ5PTMAPZGDHZ6CS7RO7MGG3DBMasync function fundTestnetAccount(publicKey: string) {
const response = await fetch(
`https://friendbot.stellar.org?addr=${publicKey}`
)
if (response.ok) {
const result = await response.json()
console.log('Account funded!', result)
return result
} else {
throw new Error('Funding failed')
}
}- 10,000 XLM (standard Friendbot funding)
- Activated account (minimum balance requirement met)
- Ability to create trustlines and send payments
After funding, check your balance:
- In Freighter: Open the extension to see balance
- Via Horizon API:
curl https://horizon-testnet.stellar.org/accounts/YOUR_PUBLIC_KEY
- In StellarSplit: Connect wallet to see balance in app
To receive USDC, you need a trustline:
- Get testnet USDC issuer address
- In Freighter: Click "Add asset"
- Enter USDC code and issuer
- Sign the trustline transaction
// Check if Freighter is installed
import { isFreighterInstalled } from '@stellar/freighter-api'
const installed = await isFreighterInstalled()
// Connect wallet
import { requestAccess } from '@stellar/freighter-api'
const { address } = await requestAccess()
// Sign transaction
import { signTransaction } from '@stellar/freighter-api'
const { signedTxXdr } = await signTransaction(xdr, { networkPassphrase })
// Get network
import { getNetworkDetails } from '@stellar/freighter-api'
const { networkPassphrase } = await getNetworkDetails()const Networks = {
TESTNET: 'Test SDF Network ; September 2015',
PUBLIC: 'Public Global Stellar Network ; September 2015',
FUTURENET: 'Test SDF Future Network ; October 2022',
}| Resource | URL |
|---|---|
| Freighter Extension | https://freighter.app |
| Testnet Horizon | https://horizon-testnet.stellar.org |
| Mainnet Horizon | https://horizon.stellar.org |
| Friendbot | https://friendbot.stellar.org |
| Laboratory | https://laboratory.stellar.org |
frontend/
├── src/
│ ├── config/
│ │ └── walletConfig.ts # Freighter connection logic
│ ├── hooks/
│ │ └── use-wallet.tsx # React wallet hook
│ ├── utils/stellar/
│ │ ├── paymentUri.ts # Payment URI builder
│ │ └── wallet.ts # Wallet utilities
│ └── components/
│ └── wallet-button.tsx # Wallet UI component
backend/
├── src/
│ ├── stellar/
│ │ ├── stellar.service.ts # Transaction verification
│ │ └── stellar.module.ts # Stellar module
│ └── multi-currency/
│ └── path-payment.service.ts # Path payment logic
| Issue | Solution |
|---|---|
| "Freighter not installed" | Install extension from Chrome Web Store |
| "Freighter not connected" | Click wallet button and approve connection |
| "Wrong network" | Switch Freighter to testnet in settings |
| "Account not found" | Fund account with Friendbot |
| Transaction fails | Check account has sufficient XLM for fees |
| "Insufficient balance" | Ensure minimum 1 XLM + transaction fees |
- Install Freighter and create a testnet account
- Fund your account using Friendbot
- Explore the code in
frontend/src/config/walletConfig.ts - Try the wallet connection in the StellarSplit app
- Review
backend/src/stellar/stellar.service.tsfor verification logic
Happy coding on Stellar! 🚀