Skip to main content

Comprehensive frontend guide for Stellar dapps

Pre-requisites:

  • Basic knowledge of React, Tailwind CSS, and related web technologies
  • Basic understanding of the Stellar blockchain
  • Node.js and npm installed
  • Web browser with Freighter Wallet extension installed

1. Introduction

The role of frontend in Stellar dapps

Frontend development plays a crucial role in decentralized applications (dapps) built on the Stellar network. It serves as the primary interface between users and the underlying blockchain technology. A well-designed frontend not only makes your dapp accessible and user-friendly but also helps users interact seamlessly with complex blockchain operations.

In Stellar dapps, the frontend is responsible for:

  1. Presenting blockchain data in a human-readable format
  2. Facilitating user interactions with smart contracts and Stellar operations
  3. Managing user accounts and keys securely
  4. Providing real-time updates on transaction status and account balances
  5. Guiding users through complex processes like multi-signature transactions or claimable balance operations

Importance of user interface and user experience

The importance of a good user interface (UI) and user experience (UX) in Stellar dapps cannot be overstated. Blockchain technology can be intimidating for many users, and a well-designed UI/UX can make the difference between a successful dapp and one that users find frustrating or confusing.

Key aspects of UI/UX in Stellar dapps include:

  1. Simplicity: Presenting complex blockchain concepts in an easy-to-understand manner
  2. Transparency: Providing clear information about transaction fees, network status, and operation outcomes
  3. Feedback: Offering immediate and clear feedback on user actions and transaction progress
  4. Error Handling: Gracefully managing and explaining errors in a user-friendly way
  5. Performance: Ensuring quick load times and responsive interactions, even when dealing with blockchain operations

By focusing on these aspects, you can create Stellar dapps that are not only functional but also enjoyable to use, encouraging wider adoption of your application and the Stellar network as a whole.

2. Setting up the development environment

Before we start building our Stellar dapp, we need to set up our development environment. We'll be using React with Next.js for our frontend framework, Tailwind CSS for styling, and the Stellar SDK for interacting with the Stellar network.

Installing Node.js and npm

First, make sure you have Node.js and npm (Node Package Manager) installed on your system. You can download and install them from the official Node.js website: https://nodejs.org/

To verify your installation, open a terminal and run:

node --version
npm --version

Both commands should return version numbers if the installation was successful.

Setting up a Next.js project

Next.js is a React framework that provides features such as server-side rendering and routing out of the box. To create a new Next.js project, run the following commands in your terminal:

npx create-next-app@latest stellar-dapp
cd stellar-dapp

When prompted, choose the following options:

  • Would you like to use TypeScript? Yes
  • Would you like to use ESLint? Yes
  • Would you like to use Tailwind CSS? Yes
  • Would you like to use src/ directory? No
  • Would you like to use App Router? Yes
  • Would you like to customize the default import alias? No

Setup HTTPS on Localhost

Freighter wallet requires a secure connection (HTTPS) to interact with your dapp. To enable HTTPS on localhost, you can use a tool like mkcert. Fortunately, Next.js provides built-in support for HTTPS.

To enable HTTPS in your Next.js project, open the package.json file and edit the scripts section as follows:

"scripts": {
"dev": "next dev --experimental-https",
}

Installing Stellar SDK and other dependencies

To interact with the Stellar network, we'll need to install the Stellar SDK and some additional dependencies:

npm install stellar-sdk @stellar/freighter-api bignumber.js
  • stellar-sdk: The official Stellar SDK for interacting with the Stellar network
  • @stellar/freighter-api: A library for integrating with the Freighter wallet (a popular Stellar wallet browser extension)
  • bignumber.js: A library for arbitrary-precision decimal and non-decimal arithmetic

Now that we have our development environment set up, we're ready to start building our Stellar dapp!

3. Building basic interface elements

In this section, we'll create reusable components for our Stellar dapp and implement forms and inputs using React and Tailwind CSS.

Creating reusable components

Let's start by creating a button component that we'll use throughout our application. Create a new file components/Button.tsx:

import React from 'react';

interface ButtonProps {
onClick: () => void;
children: React.ReactNode;
disabled?: boolean;
className?: string;
}

const Button: React.FC<ButtonProps> = ({ onClick, children, disabled = false, className = '' }) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700 focus:outline-none focus:shadow-outline ${
disabled ? 'opacity-50 cursor-not-allowed' : ''
} ${className}`}
>
{children}
</button>
);
};

export default Button;

This button component uses Tailwind CSS classes for styling and accepts props for customization.

Next, let's create another helper button to help connect the Freighter wallet. Create a new file components/ConnectWalletButton.tsx:

"use client";
import React from 'react'
import { setAllowed } from '@stellar/freighter-api'

export interface ConnectButtonProps {
label: string
isHigher?: boolean
}

export function ConnectButton({ label }: ConnectButtonProps) {
return (
<button
className={`bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded`}
onClick={setAllowed}
>
{label}
</button>
)
}

This button component uses the setAllowed function from the @stellar/freighter-api library to connect the Freighter wallet when clicked.

Implementing forms and inputs

Next, let's create a reusable input component. Create a new file components/Input.tsx:

import React from 'react';

interface InputProps {
type: string;
placeholder: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
className?: string;
}

const Input: React.FC<InputProps> = ({ type, placeholder, value, onChange, className = '' }) => {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
className={`shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline ${className}`}
/>
);
};

export default Input;

Now, let's create a form component that uses these reusable components. Create a new file components/SendPaymentForm.tsx:

"use client";

import React, { useState } from "react";

import Button from "./Button";
import Input from "./Input";

interface SendPaymentFormProps {
onSubmit: (destination: string, amount: string) => void;
}

const SendPaymentForm: React.FC<SendPaymentFormProps> = ({ onSubmit }) => {
const [destination, setDestination] = useState("");
const [amount, setAmount] = useState("");

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(destination, amount);
};

return (
<form onSubmit={handleSubmit} className='space-y-4'>
<div>
<label
htmlFor='destination'
className='block text-sm font-medium text-gray-700'>
Destination Address
</label>
<Input
type='text'
placeholder='GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
value={destination}
required
onChange={(e) => setDestination(e.target.value)}
/>
</div>
<div>
<label
htmlFor='amount'
className='block text-sm font-medium text-gray-700'>
Amount (XLM)
</label>
<Input
type='number'
placeholder='0.0'
value={amount}
required
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<Button type='submit' disabled={!destination || !amount}>
Send Payment
</Button>
</form>
);
};

export default SendPaymentForm;

Designing responsive layouts with Tailwind CSS

To create a responsive layout for our dapp, we'll use Tailwind CSS utility classes. Let's create a layout component that we can use across our pages. Edit the file app/layout.tsx:

import "./globals.css";

import { Inter } from "next/font/google";
import type { Metadata } from "next";
import React from "react";


export const metadata: Metadata = {
title: "Stellar Payment DApp",
description: "Payment DApp built on Stellar",
};

interface LayoutProps {
children: React.ReactNode;
}

const Layout: React.FC<LayoutProps> = ({ children }) => {
return (
<html lang='en'>
<body>
<div className='min-h-screen bg-gray-100'>
<header className='bg-white shadow'>
<div className='max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8'>
<h1 className='text-3xl font-bold text-gray-900'>Stellar Dapp</h1>
</div>
</header>
<main>
<div className='max-w-7xl mx-auto py-6 sm:px-6 lg:px-8'>
<div className='px-4 py-6 sm:px-0'>
<div className='border-4 border-dashed border-gray-200 rounded-lg h-96'>
{children}
</div>
</div>
</div>
</main>
</div>
</body>
</html>
);
};

export default Layout;

Now, let's update our app/page.tsx file to use this layout and our new components:

"use client"

import React from "react";
import SendPaymentForm from "../components/SendPaymentForm";

export default function Home() {
const handleSendPayment = (destination: string, amount: string) => {
// We'll implement this function in the next section
console.log(`Sending ${amount} XLM to ${destination}`);
};

return (
<div className='max-w-md mx-auto'>
<h2 className='text-2xl font-bold mb-4'>Send Payment</h2>
<SendPaymentForm onSubmit={handleSendPayment} />
</div>
);
}

This setup provides a solid foundation for building the user interface of our Stellar dapp. In the next section, we'll integrate these components with the Stellar SDK to perform actual blockchain operations.

4. Basic Concepts of Soroban Contracts

Before we start integrating smart contract functionality into our Stellar dapp, let's understand the basic concepts of Soroban contracts.

Soroban is a smart contract platform built on the Stellar network. It allows developers to write and deploy smart contracts that run on the Stellar blockchain. Soroban contracts are written in Rust and compiled to XDR (External Data Representation) for execution on the Stellar network.

Data Types

Stellar supports a few data types that can be used in Soroban contracts and we need to do conversions from time to time between those types and the types we have in Javascript. The full list of primitive data types are explained here.

XDR

Values that are passed to contracts and returned from contracts are serialized using XDR. XDR is a standard data serialization format used in Stellar to represent complex data structures. It is used to encode and decode data for transmission over the Stellar network. XDR is mostly represented in Javascript as string and can be converted to other types using the Stellar SDK.

Fees

Gas fees like they are called in the ethereum network are charged differently here. When submitting different types of transactions, the type of fees paid are different. When submitting a transaction to the network that interacts with the network, a Base fee is paid together with the resource fee for the operation. The base fee is a fixed fee that is paid for every transaction on the network. The resource fee is a fee that is paid for the resources used by the operation and is calculated based on the resources used by the operation and the network's current resource price.

Calculating these fees can be cumbersome but the Stellar SDK provides a way to calculate these fees using the server.prepareTransaction method which simulates the transaction, gets the appropriate fees and appends the correct fee settings to the transaction.

ABI or Spec

The ABI or spec is a json file that contains the contract's interface. It defines the functions that can be called on the contract, their parameters, and return types. The ABI is used by clients to interact with the contract and execute its functions. The ABI is generated from the contract's source code and is used to compile the contract into XDR for execution on the Stellar network.

ABI can be genrated for a contract using the stellar contract bindings command and can be used to interact with the contract.

This ABI can also be generated as a typescript library to ease development in your DApp and we'll be looking it later in this guide

5. Integrating with Stellar blockchain

Now that we have our basic UI components in place, let's integrate them with the Stellar blockchain using the Stellar SDK. It is imperative to know that the Stellar blockchain has three major networks:

  • Public network (also called Mainnet): This is the main Stellar network where real transactions take place.
  • Test network: This is a test environment for developers to test their applications without using real lumens.
  • Futurenet network: This is a network for testing new features before they are deployed to the public network.

For this guide, we'll be using the Test network to avoid using real lumens during development. Read more about the Stellar networks here.

Setting up the Stellar SDK

Below is a snippet that shows how to set up the Stellar SDK in your project. We will be using parts of it a lot in this guide so lets try to understand it.

import * as StellarSdk from "@stellar/stellar-sdk";

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

export const server = new SorobanRpc.Server("https://soroban-testnet.stellar.org"); // soroban testnet server

const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE
networkPassphrase: StellarSdk.Networks.TESTNET, // Use appropriate network
})

In the above snippet, we import the Stellar SDK and create a server instance for the Stellar Testnet.

We also create a transaction builder instance with the appropriate network passphrase. The BASE_FEE is the minimum fee required for a transaction on the Stellar network. while the Networks.TESTNET is the network passphrase for the Stellar Testnet. When using the public network, you can replace TESTNET with PUBLIC or FUTURENET for the Futurenet network.

Interacting with the Stellar network

If you are coding along this guide at this point, you should have a minimal web application with a form to send payments. Now, let's integrate this form with the Stellar SDK to send actual payments on the Stellar network.

Before we proceed, please ensure that you have setup the Freighter wallet extension on your browser. You can download it here. Also, ensure that you have lumens on the Testnet account you are using. You can get free lumens from the Stellar Friendbot.

Now, let's update our app/page component to interact with the Stellar network:

"use client";

import React, { useState, useEffect } from "react";
import SendPaymentForm from "../components/SendPaymentForm";
import * as StellarSdk from "@stellar/stellar-sdk";
import { SorobanRpc } from "@stellar/stellar-sdk";
import {
isConnected,
setAllowed,
getPublicKey,
signTransaction,
} from "@stellar/freighter-api";

export default function Home() {
const [publicKey, setPublicKey] = useState<string | null>(null);

useEffect(() => {
const checkFreighter = async () => {
try {
const connected = await isConnected();
if (connected) {
const pubKey = await getPublicKey();
setPublicKey(pubKey);
}
} catch (error) {
console.error("Error checking Freighter connection:", error);
}
};

checkFreighter();
}, []);

const handleConnectWallet = async () => {
try {
await setAllowed();
const pubKey = await getPublicKey();
setPublicKey(pubKey);
} catch (error) {
console.error("Error connecting to Freighter:", error);
}
};

const handleSendPayment = async (destination: string, amount: string) => {
if (!publicKey) {
console.error("Wallet not connected");
return;
}

try {
const server = new SorobanRpc.Server("https://soroban-testnet.stellar.org");
const sourceAccount = await server.getAccount(publicKey);
const transaction = new StellarSdk.TransactionBuilder(sourceAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.payment({
destination: destination,
asset: StellarSdk.Asset.native(),
amount: amount,
})
)
.setTimeout(30)
.build();

const signedTransaction = await signTransaction(transaction.toXDR(), {
networkPassphrase: StellarSdk.Networks.TESTNET,
});

const transactionResult = await server.sendTransaction(
StellarSdk.TransactionBuilder.fromXDR(
signedTransaction,
StellarSdk.Networks.TESTNET
)
);

console.log("Transaction successful:", transactionResult);
alert("Payment sent successfully!");
} catch (error) {
console.error("Error sending payment:", error);
alert("Error sending payment. Please check the console for details.");
}
};

return (
<div className='max-w-md mx-auto'>
<h2 className='text-2xl font-bold mb-4'>Send Payment</h2>
{publicKey ? (
<>
<p className='mb-4'>Connected: {publicKey}</p>
<SendPaymentForm onSubmit={handleSendPayment} />
</>
) : (
<button
onClick={handleConnectWallet}
className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'>
Connect Freighter Wallet
</button>
)}
</div>
);
}

In the updated app/page component, we added a useEffect hook to check if the Freighter wallet is connected and retrieve the public key. We also added a handleConnectWallet function to connect the wallet if it's not already connected.

The handleSendPayment function now interacts with the Stellar network to send a payment. It retrieves the source account details, creates a payment transaction, signs the transaction using the Freighter wallet, and sends the transaction to the Stellar network. If the transaction is successful, it displays an alert to the user.

Notice how we imported the isConnected, setAllowed, getPublicKey, and signTransaction functions from the @stellar/freighter-api library. These functions are used to interact with the Freighter wallet extension and sign transactions securely.

tip

Hurray! You have successfully integrated your Stellar dapp with the Stellar network. You can now send payments using the Freighter wallet extension on the Stellar Testnet.

Interacting with smart contracts

In the above example, we sent a simple payment transaction using the StellarSdk.Operation.payment operation. However, Stellar also supports many other operations, one of which is invokeHostFunction operation which is used to interact with smart contracts on the Stellar network.

We will be working with a deployed version of the counter smart contract on the Stellar Testnet. The smart contract has a single function increment which increments a counter value stored on the Stellar network.

Create a new file app/counter/page.tsx:

"use client";

import {
BASE_FEE,
Contract,
Networks,
SorobanRpc,
Transaction,
TransactionBuilder,
xdr,
} from "@stellar/stellar-sdk";
import React, { useEffect, useState } from "react";
import {
getPublicKey,
isConnected,
signTransaction,
} from "@stellar/freighter-api";

import { ConnectButton } from "@/components/ConnectButton";

// Replace with your actual contract ID and network details
const CONTRACT_ID = "CC6MWZMG2JPQEENRL7XVICAY5RNMHJ2OORMUHXKRDID6MNGXSSOJZLLF";
const NETWORK_PASSPHRASE = Networks.TESTNET;
const SOROBAN_URL = "https://soroban-testnet.stellar.org:443";

export default function CounterPage() {
const [publicKey, setPublicKey] = useState<string | null>(null);
const [count, setCount] = useState<number | null>(null);
const [loading, setLoading] = useState(false);

useEffect(() => {
const checkWallet = async () => {
const connected = await isConnected();
if (connected) {
const pubKey = await getPublicKey();
setPublicKey(pubKey);
}
};

checkWallet();
}, []);

const handleIncrement = async () => {
if (!publicKey) {
console.error("Wallet not connected");
return;
}

setLoading(true);

try {
const server = new SorobanRpc.Server(SOROBAN_URL);
const account = await server.getAccount(publicKey);

const contract = new Contract(CONTRACT_ID);
// const instance = contract.getFootprint();

const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: NETWORK_PASSPHRASE,
})
.addOperation(contract.call("increment"))
.setTimeout(30)
.build();

const preparedTx = await server.prepareTransaction(tx);

const signedXdr = await signTransaction(preparedTx.toEnvelope().toXDR("base64"), {
networkPassphrase: NETWORK_PASSPHRASE,
});

const signedTx = TransactionBuilder.fromXDR(
signedXdr,
NETWORK_PASSPHRASE
) as Transaction;

const txResult = await server.sendTransaction(signedTx);

if (txResult.status !== "PENDING") {
throw new Error("Something went Wrong");
}
const hash = txResult.hash;
let getResponse = await server.getTransaction(hash);
// Poll `getTransaction` until the status is not "NOT_FOUND"

while (getResponse.status === "NOT_FOUND") {
console.log("Waiting for transaction confirmation...");
getResponse = await server.getTransaction(hash);
await new Promise((resolve) => setTimeout(resolve, 1000));
}

if (getResponse.status === "SUCCESS") {
// Make sure the transaction's resultMetaXDR is not empty
if (!getResponse.resultMetaXdr) {
throw "Empty resultMetaXDR in getTransaction response";
}
} else {
throw `Transaction failed: ${getResponse.resultXdr}`;
}

// Extract the new count from the transaction result
const returnValue = getResponse.resultMetaXdr
.v3()
.sorobanMeta()
?.returnValue();
if (returnValue) {
const newCount = returnValue.u32();
setCount(newCount);
}
} catch (error) {
console.error("Error incrementing counter:", error);
alert(
"Error incrementing counter. Please check the console for details."
);
} finally {
setLoading(false);
}
};

return (
<div className='max-w-md mx-auto mt-10'>
<h1 className='text-2xl font-bold mb-4'>
Stellar Smart Contract Counter
</h1>
{publicKey ? (
<div>
<p className='mb-4'>Connected: {publicKey}</p>
<p className='mb-4'>
Current Count: {count === null ? "Unknown" : count}
</p>
<button
onClick={handleIncrement}
disabled={loading}
className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50'>
{loading ? (
<span className='flex items-center'>
<svg
className='animate-spin -ml-1 mr-3 h-5 w-5 text-white'
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'>
<circle
className='opacity-25'
cx='12'
cy='12'
r='10'
stroke='currentColor'
strokeWidth='4'></circle>
<path
className='opacity-75'
fill='currentColor'
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'></path>
</svg>
Processing...
</span>
) : (
"Increment Counter"
)}
</button>
</div>
) : (
<>
<p>Please connect your Freighter wallet to use this app.</p>
<ConnectButton label='Connect Wallet' />
</>
)}
</div>
);
}

In the above code snippet, we created a new page component CounterPage that interacts with the counter smart contract on the Stellar network. The handleIncrement function sends a transaction to the smart contract to increment the counter value. It then retrieves the updated counter value from the transaction result and displays it to the user.

Notice how we used a different operation contract.call("increment") to interact with the smart contract. This operation is a facade for the invokeHostFunction operation on the Stellar network. We also used the prepareTransaction and sendTransaction methods to send the transaction to the network and retrieve the transaction result.

prepareTransaction call is used to prepare a transaction for submission to the network. It simulates the transaction, then uses the appropriate resource fees and other params to create a transaction that is ready to be submitted to the network.

<ConnectButton> is a helper button that connects the Freighter wallet when clicked like we described earlier.

After calling the transaction to increment successfully, we need to poll and then call another method server.getTransaction to get the transaction result. The transaction result contains the new counter value which we extract and display to the user.

The value gotten from the transaction result is stored in a form called scval due to the limited number of types that exist in soroban today. This value is transmitted about in the form of xdr which can be converted to an scVal using the sdk. The value is then parsed as u32 which is a 32-bit unsigned integer. We will learn more about converting these types when working with contract specs

tip

Congratulations! You have successfully interacted with a smart contract on the Stellar network using your dapp.

Reading events from the Stellar network

Events on Stellar are values emitted from contracts grouped by topics. These events are emitted when a contract is called and can be read by any client that is interested in the contract. Events are stored in the Stellar ledger and can be read by any client that is interested in the contract.

In addition to sending transactions and interacting with smart contracts, you can also read events from the Stellar network using the Stellar SDK. This allows you to monitor account changes, transaction status, and other network events in real-time.

This is made possible by using the server.getEvents method which allows you to query events based on topics. We will consider a simple example where we read events from the counter smart contract we interacted with earlier.

We will be editing the CounterPage component to read events from the counter smart contract immediately the page loads to get the initial counter value and update instead of using "Unknown". Before we continue, please take a look at the contract code. In the contract code, an event named increment is emitted whenever the increment function is called. It is published over 2 topics, increment and COUNTER and we need to listen to these topics to get the events.

The topics are stored in a data type called symbol and we will need to convert both increment and COUNTER to symbol before we can use them in the server.getEvents method. By default, soroban RPCs keep track of events for 24 hours and you can query events that happened within the last 24 hours, so if you need to store events for longer, you may need to make use of an indexer.

To use events,we edit our counter page and add the following code:

useEffect(() => {
const checkWallet = async () => {
const connected = await isConnected();
if (connected) {
const pubKey = await getPublicKey();
setPublicKey(pubKey);
}
};
checkWallet();
getInitialCount();
}, []);

const getInitialCount = async () => {
try {
const topic1 = xdr.ScVal.scvSymbol("COUNTER").toXDR("base64");
const topic2 = xdr.ScVal.scvSymbol("increment").toXDR("base64");
const latestLedger = await server.getLatestLedger();
const events = await server.getEvents({
startLedger: latestLedger.sequence - 2000,
filters: [
{
type: "contract",
contractIds: [CONTRACT_ID],
topics: [[topic1, topic2]],
},
],
limit: 20,
});
setCount(events.events.map((e) => e.value.u32()).pop() || null);
} catch (error) {
console.error(error);
}
};

In the above code, we use an RPC method called getEvents to query events from the Stellar network. We pass in the contract ID, topics, and other filters like the ledger to start searching from to 2000 ledgers away to get the events we are interested in. We then extract the counter value from the events and update the state accordingly.

However, we had to convert the topics COUNTER and increment to symbol using the xdr.ScVal.scvSymbol and then convert them to XDR before we can use them in the getEvents method. This is because the topics are stored in the form of symbol and transmitted over XDR format to the network like we discussed earlier. Similarly, the value of the new count is sent over the network as XDR but the SDK helped us convert it to an scVal and then we parsed it as a u32 to get the actual value.

Source code for this program is available here

Now, when the page loads, it will query the events from the Stellar network and update the counter value accordingly.

6. Using Typescript Bindings

The Stellar CLI comes with support for exporting a contract spec to a typescript library. This library is optimized for importing as an npm based module to your application.

The library generated contains helper methods for calling each contract and also does automatic type conversion of the types for any contract method.

How to create the bindings library

To achieve this, you need to

  • Have the Stellar CLI installed.
  • Have either source code or deployed contract ID of the contract.
  • Know the network it was deployed to.

Scenario 1: I have the Contract ID but no code

In this scenario, we need to use the command stellar contract fetch to fetch the wasm code of the contract

Scenario 2: I have the code

The next step here is to build it to a wasm file using the stellar contract build command.

Generating the Library

After getting the wasm, we can now run stellar contract bindings typescript to generate the library which is ready to be published to NPM. The library generated is suited for working with complex contracts.

7. Common Pitfalls

Below are a few common pitfalls that soroban DApp developers might run into

Data Type Conversions

The data types in Javascript are different from the data types in soroban. When working with soroban contracts, you need to be aware of the data types and how to convert them to the types you are familiar with in Javascript. The Stellar SDK provides helper methods for converting between XDR and Javascript types in the xdr namespace.

Fees

Calculating fees for transactions can be tricky. The Stellar SDK provides a way to calculate fees using the server.prepareTransaction method, which simulates the transaction and returns the appropriate fees. Fees are calculated in stroops

SendTransaction and GetTransaction

Results from a smart contract execution or any of the valid transactions on soroban are not immediate. They are kept in a PENDING state until they are confirmed. You need to poll the getTransaction method to get the final result of the transaction.

State Archival

State Archival is a characteristic of soroban contracts where some data stored on the ledger about the contract might be archived. This guide helps to understand how to work with state archival in DApps.

Data Retention

A few things to note about data retention in soroban contracts:

  • Events Data can be queried within 24 hours of the event happening. So you may need an indexer to store events for longer periods.
  • Transaction data is stored with a retention period of 1440 ledgers. This means after 1440 ledgers, the transaction data cannot be queried using the RPC. Again, you may need an indexer to store transaction data for longer periods.