Invoke a Contract
This guide calls a method on a deployed Soroban contract from JavaScript. You will connect over RPC, preview a call for free with simulation, then sign and send it to change on-chain state. Everything runs on testnet, so it is free and safe to repeat.
Prerequisites
- A funded testnet account and its keypair. If you need one, see Connect and Fund an Account.
- A deployed contract and its contract ID (a
C...string). This guide uses the increment contract. Deploying is a one-time setup with a different toolchain (the Stellar CLI and Rust): follow Stellar’s Deploy the Increment Contract tutorial once (about 20 to 30 minutes), then paste the contract ID it prints intocontractIdbelow. You will not touch the CLI again in this guide. Generating a typed client from a contract is covered later in the series. - The examples use testnet RPC at
https://soroban-testnet.stellar.org.
Connect and load the contract
Contracts are reached over
Soroban RPC, not Horizon, so
this guide connects over RPC instead of using the Horizon.Server from earlier
guides. Build a contract.Client
from your deployed contract ID. The client reads the contract’s interface from
the network, which is what lets you call its methods by name:
import { contract, Keypair, Networks } from "@stellar/stellar-sdk";
const rpcUrl = "https://soroban-testnet.stellar.org";const networkPassphrase = Networks.TESTNET;
const { signTransaction } = contract.basicNodeSigner(keypair, networkPassphrase);
const client = await contract.Client.from({ contractId, rpcUrl, networkPassphrase, publicKey: keypair.publicKey(), signTransaction,});Here keypair is your funded account from
Connect and Fund an Account and contractId is
your deployed contract’s C... ID. Because the client is built from the live
contract at runtime, its methods are not typed: TypeScript does not know
client.increment exists, so calls below use (client as any). A fully typed
client comes from generating bindings, covered later in the series.
Preview a call with simulation
Calling a contract method does not send anything yet: it builds a transaction and
simulates it. The RPC server runs the call against the current ledger state
and returns the result without committing anything, so a preview is free and needs
no signature. Read the predicted return value from
tx.result:
const tx = await (client as any).increment();
tx.result; // the value the call would return; nothing has been sentNothing changed on-chain: simulate again and you get the same answer. A read-only
method (one that does not change state) stops here. tx.isReadCall is true, and
tx.result is your final answer with no signing or fee, because a read touches no
state and needs no authorization. increment does change state, so tx.isReadCall
is false and this is only a preview. To apply it, you sign and send.
Sign and send to apply it
To apply the state change, sign and send the transaction. The signer is
the basicNodeSigner you
passed to the client (a simple Node signer for scripts and tests; a browser app
swaps in a wallet such as Freighter). signAndSend submits the transaction and
waits for the network, returning a
SentTransaction whose
result is the value the contract returned on-chain:
const sent = await tx.signAndSend();
sent.result; // the applied result; send again and the counter advancesIf a method depends on contract state that has expired, pass restore: true in
the method options and simulation will restore it before the call; see
State Archival.
Put it together
The whole flow as one runnable script. Set contractId to your deployed
increment contract (see Prerequisites); the script funds a throwaway source
account with friendbot so it runs end to end. In your app, replace the
Keypair.random() call with your existing funded keypair.
import { contract, rpc, Keypair, Networks } from "@stellar/stellar-sdk";
const rpcUrl = "https://soroban-testnet.stellar.org";const networkPassphrase = Networks.TESTNET;const contractId = "C..."; // your deployed increment contract (see Prerequisites)
async function main() { const server = new rpc.Server(rpcUrl); const keypair = Keypair.random(); const { signTransaction } = contract.basicNodeSigner( keypair, networkPassphrase, );
try { // Fund a throwaway account to invoke from (the RPC-side friendbot). await server.fundAddress(keypair.publicKey());
const client = await contract.Client.from({ contractId, rpcUrl, networkPassphrase, publicKey: keypair.publicKey(), signTransaction, });
// Preview the call for free with simulation. const tx = await (client as any).increment(); console.log("preview:", tx.result);
// Sign and send to apply it on-chain. const sent = await tx.signAndSend(); console.log("applied:", sent.result); } catch (e) { console.error("Invocation failed:", e); }}
main().catch(console.error);You can now read from and write to a deployed contract from JavaScript. Next, learn to authorize calls that more than one account must sign.