Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion skills/agentic-payments/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ Always test on testnet first. To switch a working setup to mainnet, change only

**OZ Channels 401 on testnet or mainnet**
- Symptom: facilitator rejects with 401, server logs `Failed to initialize: no supported payment kinds loaded from any facilitator`
- Fix: an API key is required on **both** networks (this is a recent change). Generate one at [channels.openzeppelin.com/testnet/gen](https://channels.openzeppelin.com/testnet/gen) (testnet) or [channels.openzeppelin.com/gen](https://channels.openzeppelin.com/gen) (mainnet), then set `OZ_API_KEY` and pass it via `createAuthHeaders` (see the Seller example).
- Fix: an API key is required on **both** networks. Generate one at [channels.openzeppelin.com/testnet/gen](https://channels.openzeppelin.com/testnet/gen) (testnet) or [channels.openzeppelin.com/gen](https://channels.openzeppelin.com/gen) (mainnet), then set `OZ_API_KEY` and pass it via `createAuthHeaders` (see the Seller example).

**Trustline missing on the recipient**
- Symptom: `op_no_trust` during settlement, even though the client has USDC
Expand Down
19 changes: 4 additions & 15 deletions skills/assets/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,30 +406,19 @@ image = "https://example.com/token-logo.png"

### SEP-0010 (Web Authentication)

Authenticate users with their Stellar accounts:

```typescript
// Server generates challenge
// Client signs with wallet
// Server verifies signature
```
Authenticate users with their Stellar accounts. Flow: the server generates a challenge transaction, the client signs it with their wallet, and the server verifies the signature. See [SEP-0010](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md).

### SEP-0024 (Hosted Deposit/Withdrawal)

For fiat on/off ramps:

```typescript
// Interactive flow for deposits/withdrawals
// Anchor handles KYC and fiat transfer
```
For fiat on/off ramps: an interactive webview flow where the anchor handles KYC and the fiat transfer. See [SEP-0024](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md).

### SEP-0045 (Web Auth for Contract Accounts)

Extends SEP-10 to support contract accounts (`C...` addresses) for web authentication. Required for smart wallet / passkey-based anchor integrations. See [SEP-0045](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0045.md).
Extends SEP-10 to support contract accounts (`C...` addresses) for web authentication. Required for smart wallet / passkey-based anchor integrations. Draft status; verify current status in [SEP-0045](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0045.md).

### SEP-0050 (Non-Fungible Tokens)

Standard contract interface for NFTs on Stellar. Reference implementations available in [OpenZeppelin Stellar Contracts](https://github.com/OpenZeppelin/stellar-contracts) with Base, Consecutive, and Enumerable variants. See [SEP-0050](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0050.md).
Standard contract interface for NFTs on Stellar. Reference implementations available in [OpenZeppelin Stellar Contracts](https://github.com/OpenZeppelin/stellar-contracts) with Base, Consecutive, and Enumerable variants. Draft status; verify current status in [SEP-0050](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0050.md).

## Best Practices

Expand Down
176 changes: 130 additions & 46 deletions skills/dapp/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ Client-side development with `@stellar/stellar-sdk`, wallet connection, signing,

## Recommended Dependencies

> **Requires Node.js 20+** — the Stellar SDK dropped Node 18 support.
> **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](https://stellar.github.io/js-stellar-sdk/guides/00-migration).

```bash
npm install @stellar/stellar-sdk @stellar/freighter-api
# Or for multi-wallet support:
npm install @stellar/stellar-sdk @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](https://stellar.github.io/js-stellar-sdk/) (which also publish [`llms.txt`](https://stellar.github.io/js-stellar-sdk/llms.txt) / [`llms-full.txt`](https://stellar.github.io/js-stellar-sdk/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 [api-rpc-horizon.md](../data/SKILL.md).
> For the full API reference (RPC methods, Horizon endpoints, migration guide), see the [data skill](../data/SKILL.md).

### Basic Setup
```typescript
Expand Down Expand Up @@ -86,20 +88,29 @@ const requireEnv = (name: string): string => {
return value;
};

export const config = {
testnet: {
horizonUrl: "https://horizon-testnet.stellar.org",
rpcUrl: "https://soroban-testnet.stellar.org",
networkPassphrase: StellarSdk.Networks.TESTNET,
friendbotUrl: "https://friendbot.stellar.org",
},
mainnet: {
horizonUrl: "https://horizon.stellar.org",
rpcUrl: requireEnv("NEXT_PUBLIC_STELLAR_MAINNET_RPC_URL"),
networkPassphrase: StellarSdk.Networks.PUBLIC,
friendbotUrl: null,
},
}[NETWORK]!;
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,
};
Comment on lines +95 to +99
case "mainnet":
return {
horizonUrl: "https://horizon.stellar.org",
// Resolved lazily so testnet runs don't require the mainnet env var
rpcUrl: requireEnv("NEXT_PUBLIC_STELLAR_MAINNET_RPC_URL"),
networkPassphrase: StellarSdk.Networks.PUBLIC,
friendbotUrl: null,
};
default:
throw new Error(`Unknown network: ${network}`);
}
}

export const config = getConfig(NETWORK);

export const horizon = new StellarSdk.Horizon.Server(config.horizonUrl);
export const rpc = new StellarSdk.rpc.Server(config.rpcUrl);
Expand Down Expand Up @@ -263,7 +274,56 @@ export async function buildPaymentTx(
}
```

### Smart Contract Invocation
### Smart Contract Invocation (`contract.Client`)

The canonical way to call a Soroban 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.

```typescript
import { contract, rpc, Networks } from "@stellar/stellar-sdk";

// Describe just the methods you call. `Client.from<T>()` uses this to type
// the returned client, so calls are checked and autocompleted — no codegen.
// For a contract with many methods, generate this interface from its spec
// with the SDK's binding CLI instead of writing it by hand.
interface CounterContract {
increment: (
options?: contract.MethodOptions,
) => Promise<contract.AssembledTransaction<number>>;
}

// `signTransaction` comes from the wallet (e.g. Freighter/Wallets Kit in the
// browser). `contract.basicNodeSigner(keypair, networkPassphrase)` is the
// Node equivalent for scripts and tests.
export async function getCounterClient(
contractId: string,
publicKey: string,
signTransaction: contract.ClientOptions["signTransaction"],
) {
return contract.Client.from<CounterContract>({
contractId,
rpcUrl: "https://soroban-testnet.stellar.org",
networkPassphrase: Networks.TESTNET,
publicKey,
signTransaction,
});
}
Comment on lines +297 to +309

// Preview (free simulation) then sign + send to apply on-chain.
export async function increment(client: contract.Client & CounterContract) {
const tx = await client.increment();
console.log("preview:", tx.result); // predicted return value, no signature
const sent = await tx.signAndSend(); // submits and polls to completion
return sent.result;
}
```

`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](https://stellar.github.io/js-stellar-sdk/guides/06-invoke-a-contract) and [Authorize a Contract Call](https://stellar.github.io/js-stellar-sdk/guides/07-contract-auth).

<details>
<summary><b>Advanced: low-level invocation without a client</b></summary>

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.

```typescript
import * as StellarSdk from "@stellar/stellar-sdk";
import { rpc, config } from "@/lib/stellar";
Expand All @@ -275,39 +335,28 @@ export async function invokeContract(
args: StellarSdk.xdr.ScVal[]
) {
const account = await rpc.getAccount(sourceAddress);

const contract = new StellarSdk.Contract(contractId);

let transaction = new StellarSdk.TransactionBuilder(account, {
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: config.networkPassphrase,
})
Comment on lines +340 to 343
.addOperation(contract.call(method, ...args))
.setTimeout(180)
.build();

// Simulate to get resource estimates
const simulation = await rpc.simulateTransaction(transaction);

if (StellarSdk.rpc.Api.isSimulationError(simulation)) {
throw new Error(`Simulation failed: ${simulation.error}`);
}

// Assemble with proper resources
transaction = StellarSdk.rpc.assembleTransaction(
transaction,
simulation
).build();

return transaction.toXDR();
// `prepareTransaction` simulates and applies footprint/auth/fees in one step.
// (Equivalent to simulateTransaction + rpc.assembleTransaction.)
const prepared = await rpc.prepareTransaction(transaction);
return prepared.toXDR();
}
```

### Building ScVal Arguments
**Building ScVal arguments by hand** (only needed for the low-level path — `contract.Client` converts native JS args for you):

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

// Common conversions
const addressVal = StellarSdk.Address.fromString(address).toScVal();
const i128Val = StellarSdk.nativeToScVal(BigInt(amount), { type: "i128" });
const u32Val = StellarSdk.nativeToScVal(42, { type: "u32" });
Expand All @@ -325,10 +374,15 @@ const structVal = StellarSdk.nativeToScVal(
}
);

// Vec
const vecVal = StellarSdk.nativeToScVal([1, 2, 3], { type: "i128" });
// Vec of i128 — the element type is applied to each item
const vecVal = StellarSdk.nativeToScVal(
[1, 2, 3].map((n) => BigInt(n)),
{ type: "i128" }
);
```

</details>

## Transaction Submission

### Submit and Wait for Confirmation
Expand Down Expand Up @@ -360,15 +414,13 @@ async function submitSorobanTransaction(signedXdr: string) {
const response = await rpc.sendTransaction(transaction);

if (response.status === "ERROR") {
throw new Error(`Transaction failed: ${response.errorResult}`);
throw new Error(`Send failed: ${response.errorResult}`);
}

// Poll for completion
let getResponse = await rpc.getTransaction(response.hash);
while (getResponse.status === "NOT_FOUND") {
await new Promise((resolve) => setTimeout(resolve, 1000));
getResponse = await rpc.getTransaction(response.hash);
}
// Poll for completion. pollTransaction handles the retry loop (default 5
// attempts, 1s apart — tune with { attempts, sleepStrategy }) instead of a
// hand-rolled while loop that can spin forever.
const getResponse = await rpc.pollTransaction(response.hash);

if (getResponse.status === "SUCCESS") {
return {
Expand Down Expand Up @@ -441,6 +493,7 @@ export function ConnectButton() {
import { useState } from "react";
import { useFreighter } from "@/hooks/useFreighter";
import { buildPaymentTx, submitTransaction } from "@/lib/transactions";
import { config } from "@/lib/stellar";

export function SendPayment() {
const { address, sign } = useFreighter();
Expand Down Expand Up @@ -541,6 +594,7 @@ export default function RootLayout({

### Account Balance
```typescript
import { NotFoundError } from "@stellar/stellar-sdk";
import { horizon } from "@/lib/stellar";

export async function getBalance(address: string) {
Expand All @@ -551,15 +605,43 @@ export async function getBalance(address: string) {
);
return nativeBalance?.balance || "0";
} catch (error) {
if (error.response?.status === 404) {
return "0"; // Account not funded
// loadAccount rejects with the typed NotFoundError for an unfunded account.
if (error instanceof NotFoundError) {
return "0"; // Account not funded yet
}
throw error;
}
}
```

> For submission failures, Horizon returns result codes under `error.response?.data?.extras?.result_codes` (`transaction` + per-`operation`). See [Handle Errors](https://stellar.github.io/js-stellar-sdk/guides/05-handle-errors).

### Contract State

For a read-only contract call, `rpc.Server` has one-line shortcuts that build the contract interface for you (including the built-in spec for Stellar Asset Contracts), so no client setup or manual ScVal work is needed:

```typescript
import { rpc } from "@/lib/stellar";

// Run a read-only method and get the decoded result directly.
const { result: balance, isReadCall } = await rpc.queryContract<bigint>(
tokenId,
"balance",
{ id: "G..." } // named args, keyed by parameter name; omit for no-arg methods
);

// Discover a contract's callable methods from just its ID.
const methods = await rpc.getContractMethods(tokenId);
// [{ name: "balance", inputs: [{ name: "id", type: "Address" }], outputs: ["I128"] }, ...]
```

`isReadCall` is per-call: `false` means the `result` is only a simulation preview of a call that would change state (apply it by signing a transaction via `contract.Client`).

<details>
<summary><b>Advanced: read a raw ledger entry</b></summary>

Reach for `getLedgerEntries` only when you need a specific storage key that isn't exposed as a contract method.

```typescript
import * as StellarSdk from "@stellar/stellar-sdk";
import { rpc } from "@/lib/stellar";
Expand Down Expand Up @@ -588,6 +670,8 @@ export async function getContractData(
}
```

</details>

## Smart Accounts (Passkey Wallets)

For passwordless authentication using WebAuthn passkeys, use Smart Account Kit.
Expand Down
22 changes: 13 additions & 9 deletions skills/data/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ See the full indexer directory: https://developers.stellar.org/docs/data/indexer

## Network Configuration

> For a React/Next.js-specific setup, see [frontend-stellar-sdk.md](../dapp/SKILL.md).
> For a React/Next.js-specific setup, see the [dapp skill](../dapp/SKILL.md).
> For mainnet RPC, set `STELLAR_MAINNET_RPC_URL` from a provider in the [RPC providers directory](https://developers.stellar.org/docs/data/apis/rpc/providers).

### Environment-Based Setup
Expand All @@ -449,29 +449,33 @@ const requireEnv = (name: string): string => {
return value;
};

const configs: Record<string, NetworkConfig> = {
mainnet: {
// Lazy per-network factories: requireEnv only runs for the selected network,
// so testnet/local work without the mainnet env var set.
const configs: Record<string, () => NetworkConfig> = {
mainnet: () => ({
rpcUrl: requireEnv("STELLAR_MAINNET_RPC_URL"),
horizonUrl: "https://horizon.stellar.org",
networkPassphrase: StellarSdk.Networks.PUBLIC,
friendbotUrl: null,
},
testnet: {
}),
testnet: () => ({
rpcUrl: "https://soroban-testnet.stellar.org",
horizonUrl: "https://horizon-testnet.stellar.org",
networkPassphrase: StellarSdk.Networks.TESTNET,
friendbotUrl: "https://friendbot.stellar.org",
},
local: {
}),
local: () => ({
rpcUrl: "http://localhost:8000/soroban/rpc",
horizonUrl: "http://localhost:8000",
networkPassphrase: "Standalone Network ; February 2017",
friendbotUrl: "http://localhost:8000/friendbot",
},
}),
};

const network = process.env.STELLAR_NETWORK || "testnet";
export const config = configs[network];
const makeConfig = configs[network];
if (!makeConfig) throw new Error(`Unknown network: ${network}`);
export const config = makeConfig();

export const rpc = new StellarSdk.rpc.Server(config.rpcUrl);
export const horizon = new StellarSdk.Horizon.Server(config.horizonUrl);
Expand Down
2 changes: 1 addition & 1 deletion skills/smart-contracts/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub enum DataKey {
## Data types

```rust
use soroban_sdk::{Address, Bytes, BytesN, Map, String, Symbol, Vec};
use soroban_sdk::{symbol_short, vec, Address, Bytes, BytesN, Map, String, Symbol, Vec};

let addr: Address = env.current_contract_address();
let sym: Symbol = symbol_short!("transfer"); // ≤9 chars; Symbol max is 32
Expand Down
2 changes: 1 addition & 1 deletion skills/smart-contracts/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Layers, fastest first:
#![cfg(test)]
use soroban_sdk::{
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, Env,
Address, Env, IntoVal,
};
use crate::{Contract, ContractClient};

Expand Down
Loading