Skip to content

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 into contractId below. 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 sent

Nothing 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 advances

If 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.