| name | dapp |
| description | Stellar dApp / frontend development. Covers the JavaScript stellar-sdk (browser + Node.js), Freighter wallet, Stellar Wallets Kit (multi-wallet), Wallet Standard, smart accounts with passkeys, transaction building / signing / submission, smart contract invocation from the client, simulation, and error handling. Use when building a React/Next.js/Node.js app that talks to Stellar — classic operations or smart contracts. |
| user-invocable | true |
| argument-hint | [dapp task] |
Stellar dApp / Frontend
Client-side development with @stellar/stellar-sdk, wallet connection, signing, and submitting transactions. Covers both classic Stellar operations and smart contract invocation from the browser or Node.js.
When to use this skill
- Connecting Freighter or other wallets via Stellar Wallets Kit
- Building, simulating, signing, and submitting transactions
- Invoking Stellar smart contracts from a frontend
- Implementing smart accounts with passkeys
- Handling network passphrases (Mainnet / Testnet / local)
Related skills
- Writing the contract being invoked →
../smart-contracts/SKILL.md
- Issuing assets and managing trustlines →
../assets/SKILL.md
- Querying chain state via RPC / Horizon →
../data/SKILL.md
- Building paid APIs or agent payment clients →
../agentic-payments/SKILL.md
- SEPs the wallet/anchor flows depend on →
../standards/SKILL.md
Goals
- Single SDK instance for the app (RPC/Horizon + transaction building)
- Freighter wallet integration (or multi-wallet via Stellar Wallets Kit)
- Clean separation of client/server in Next.js
- Transaction sending with proper confirmation handling
Read the file that matches the task
This file covers SDK setup, wallet connection, and transaction build/sign/submit. The deep dives live alongside it:
Recommended Dependencies
Requires Node.js 22+. As of SDK v16, Node 22 is the minimum (older Node produces an EBADENGINE warning). v16 also folded @stellar/stellar-base into @stellar/stellar-sdk, is ESM-first, and uses native fetch instead of axios. If you still import @stellar/stellar-base directly, switch the import to @stellar/stellar-sdk and uninstall the base package (keeping both breaks instanceof checks). See the migration guide.
npm install @stellar/stellar-sdk @stellar/freighter-api
npx jsr add @creit-tech/stellar-wallets-kit
Sourcing: SDK mechanics below (init, transaction building, contract invocation, submission, data fetching, error handling) track the official JS SDK docs (which also publish llms.txt / llms-full.txt bundles for agents). Wallet integrations (Freighter, Stellar Wallets Kit), passkey smart accounts, and the OpenZeppelin relayer are separate packages, not part of the JS SDK — verify those against their own upstream docs.
SDK Initialization
For the full API reference (RPC methods, Horizon endpoints, migration guide), see the data skill.
Basic Setup
import * as StellarSdk from "@stellar/stellar-sdk";
const testnetServer = new StellarSdk.Horizon.Server("https://horizon-testnet.stellar.org");
const testnetRpc = new StellarSdk.rpc.Server("https://soroban-testnet.stellar.org");
const testnetNetworkPassphrase = StellarSdk.Networks.TESTNET;
const mainnetServer = new StellarSdk.Horizon.Server("https://horizon.stellar.org");
const mainnetRpcUrl = process.env.NEXT_PUBLIC_STELLAR_MAINNET_RPC_URL;
if (!mainnetRpcUrl) throw new Error("Missing NEXT_PUBLIC_STELLAR_MAINNET_RPC_URL");
const mainnetRpc = new StellarSdk.rpc.Server(mainnetRpcUrl);
const mainnetNetworkPassphrase = StellarSdk.Networks.PUBLIC;
Environment Configuration
Use a provider-specific mainnet RPC URL (see: https://developers.stellar.org/docs/data/apis/rpc/providers).
import * as StellarSdk from "@stellar/stellar-sdk";
const NETWORK = process.env.NEXT_PUBLIC_STELLAR_NETWORK || "testnet";
const requireEnv = (name: string): string => {
const value = process.env[name];
if (!value) throw new Error(`Missing required env var: ${name}`);
return value;
};
function getConfig(network: string) {
switch (network) {
case "testnet":
return {
horizonUrl: "https://horizon-testnet.stellar.org",
rpcUrl: "https://soroban-testnet.stellar.org",
networkPassphrase: StellarSdk.Networks.TESTNET,
friendbotUrl: "https://friendbot.stellar.org" as string | null,
};
case "mainnet":
return {
horizonUrl: ,
: (),
: ..,
: ,
};
:
();
}
}
config = ();
horizon = ..(config.);
rpc = ..(config.);
Wallet Integration
Freighter (Primary Browser Wallet)
import { useState, useEffect, useCallback } from "react";
import {
isConnected,
getAddress,
requestAccess,
signTransaction,
getNetwork,
} from "@stellar/freighter-api";
export function useFreighter() {
const [connected, setConnected] = useState(false);
const [address, setAddress] = useState<string | null>(null);
const [network, setNetwork] = useState<string | null>(null);
useEffect(() => {
checkConnection();
}, []);
const checkConnection = async () => {
const { isConnected: installed, error } = await isConnected();
if (error || !installed) return;
const { address: addr, error: addressError } = await getAddress();
if (addressError || !addr) return;
const { network: net, error: networkError } = ();
(networkError) ;
();
(addr);
(net);
};
connect = ( () => {
{ : installed, error } = ();
(error || !installed) {
();
}
{ : addr, : accessError } = ();
(accessError) (accessError.);
{ : net, : networkError } = ();
(networkError) (networkError.);
();
(addr);
(net);
addr;
}, []);
disconnect = ( {
();
();
();
}, []);
sign = (
(: , : ) => {
(!connected) ();
{ signedTxXdr, error } = (xdr, {
networkPassphrase,
});
(error) (error.);
signedTxXdr;
},
[connected]
);
{ connected, address, network, connect, disconnect, sign };
}
Stellar Wallets Kit (Multi-Wallet)
import { useState, useCallback } from "react";
import { StellarWalletsKit, Networks } from "@creit-tech/stellar-wallets-kit";
import { defaultModules } from "@creit-tech/stellar-wallets-kit/modules/utils";
StellarWalletsKit.init({
modules: defaultModules(),
network: Networks.TESTNET,
});
export function useStellarWallet() {
const [address, setAddress] = useState<string | null>(null);
const connect = useCallback(async () => {
const { address } = await StellarWalletsKit.authModal();
setAddress(address);
}, []);
const disconnect = useCallback( () => {
.();
();
}, []);
sign = ( (: ) => {
{ signedTxXdr } = .(xdr);
signedTxXdr;
}, []);
{ address, connect, disconnect, sign };
}
Migrating from v1? (noted July 2026) v1 lived on npm under the dotted scope @creit.tech/stellar-wallets-kit, with new StellarWalletsKit({...}), allowAllModules(), and openModal({ onWalletSelected }). v2 moved to JSR under @creit-tech/stellar-wallets-kit, made the kit fully static, replaced allowAllModules() with defaultModules(), and folded wallet selection + address fetch into authModal(). npm parity is maintained for now, but the maintainers say npm updates will eventually stop — install from JSR. Pre-selecting a wallet (setWallet(FREIGHTER_ID)) still works; the ID constants now live in per-wallet module subpaths like @creit-tech/stellar-wallets-kit/modules/freighter.
Transaction Building
Basic Payment
import * as StellarSdk from "@stellar/stellar-sdk";
import { horizon, config } from "@/lib/stellar";
export async function buildPaymentTx(
sourceAddress: string,
destinationAddress: string,
amount: string,
asset: StellarSdk.Asset = StellarSdk.Asset.native()
) {
const account = await horizon.loadAccount(sourceAddress);
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: config.networkPassphrase,
})
.addOperation(
StellarSdk.Operation.payment({
destination: destinationAddress,
asset: asset,
amount: amount,
})
)
.setTimeout(180)
.build();
return transaction.toXDR();
}
Smart Contract Invocation (contract.Client)
The canonical way to call a Stellar smart contract from JS is the contract.Client, not hand-built Contract.call + assembleTransaction. The client reads the contract's interface from the network, so each method is callable by name and returns an AssembledTransaction. You get a native JS result and don't build ScVals by hand.
import { contract } from "@stellar/stellar-sdk";
import { config } from "@/lib/stellar";
interface CounterContract {
increment: (
options?: contract.MethodOptions,
) => Promise<contract.AssembledTransaction<number>>;
}
export async function getCounterClient(
contractId: string,
publicKey: string,
signTransaction: contract.ClientOptions["signTransaction"],
) {
return contract.Client.from<CounterContract>({
contractId,
rpcUrl: config.rpcUrl,
networkPassphrase: config.networkPassphrase,
publicKey,
signTransaction,
});
}
() {
tx = client.();
.(, tx.);
sent = tx.();
sent.;
}
AssembledTransaction also supports fine-grained control ({ fee, simulate, timeoutInSeconds } as a second arg) and multi-party auth via tx.needsNonInvokerSigningBy() / tx.signAuthEntries(). See Invoke a Contract and Authorize a Contract Call.
Advanced: low-level invocation without a client
Use this only when you need direct control over the transaction (e.g. batching a contract call with classic operations). Otherwise prefer contract.Client above.
import * as StellarSdk from "@stellar/stellar-sdk";
import { rpc, config } from "@/lib/stellar";
export async function invokeContract(
sourceAddress: string,
contractId: string,
method: string,
args: StellarSdk.xdr.ScVal[]
) {
const account = await rpc.getAccount(sourceAddress);
const contract = new StellarSdk.Contract(contractId);
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: config.networkPassphrase,
})
.addOperation(contract.call(method, ...args))
.setTimeout(180)
.build();
prepared = rpc.(transaction);
prepared.();
}
Transaction Submission
Submit and Wait for Confirmation
import * as StellarSdk from "@stellar/stellar-sdk";
import { rpc, horizon, config } from "@/lib/stellar";
export async function submitTransaction(signedXdr: string) {
const transaction = StellarSdk.TransactionBuilder.fromXDR(
signedXdr,
config.networkPassphrase
);
if (transaction.operations.some(op => op.type === "invokeHostFunction")) {
return submitSorobanTransaction(signedXdr);
}
return submitClassicTransaction(signedXdr);
}
async function submitSorobanTransaction(signedXdr: string) {
const transaction = StellarSdk.TransactionBuilder.fromXDR(
signedXdr,
config.networkPassphrase
) as StellarSdk.Transaction;
const response = await rpc.(transaction);
(response. === ) {
();
}
getResponse = rpc.(response.);
(getResponse. === ) {
{
: response.,
: getResponse.,
};
}
();
}
() {
transaction = ..(
signedXdr,
config.
) .;
response = horizon.(transaction);
{
: response.,
: response.,
};
}
Transaction UX Checklist