From 7fb12872ac14ad88352c3fa8cfb19f334555614c Mon Sep 17 00:00:00 2001 From: Jun Luo <4catcode@gmail.com> Date: Sun, 31 May 2026 10:12:09 +0800 Subject: [PATCH] docs: add agent skill for java-stellar-sdk --- .claude-plugin/marketplace.json | 24 ++ .claude-plugin/plugin.json | 10 + CHANGELOG.md | 1 + skills/README.md | 61 +++++ skills/java-stellar-sdk/SKILL.md | 251 ++++++++++++++++++ skills/java-stellar-sdk/references/assets.md | 68 +++++ .../java-stellar-sdk/references/bindings.md | 68 +++++ skills/java-stellar-sdk/references/horizon.md | 128 +++++++++ .../java-stellar-sdk/references/operations.md | 105 ++++++++ .../java-stellar-sdk/references/quickstart.md | 112 ++++++++ skills/java-stellar-sdk/references/sep.md | 112 ++++++++ skills/java-stellar-sdk/references/soroban.md | 194 ++++++++++++++ .../references/transactions.md | 138 ++++++++++ .../references/troubleshooting.md | 83 ++++++ .../java-stellar-sdk/references/xdr_scval.md | 79 ++++++ 15 files changed, 1434 insertions(+) create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 skills/README.md create mode 100644 skills/java-stellar-sdk/SKILL.md create mode 100644 skills/java-stellar-sdk/references/assets.md create mode 100644 skills/java-stellar-sdk/references/bindings.md create mode 100644 skills/java-stellar-sdk/references/horizon.md create mode 100644 skills/java-stellar-sdk/references/operations.md create mode 100644 skills/java-stellar-sdk/references/quickstart.md create mode 100644 skills/java-stellar-sdk/references/sep.md create mode 100644 skills/java-stellar-sdk/references/soroban.md create mode 100644 skills/java-stellar-sdk/references/transactions.md create mode 100644 skills/java-stellar-sdk/references/troubleshooting.md create mode 100644 skills/java-stellar-sdk/references/xdr_scval.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 000000000..da9b1b2e3 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,24 @@ +{ + "name": "lightsail-network", + "owner": { + "name": "lightsail-network" + }, + "metadata": { + "description": "Agent skills for the Stellar Java SDK (network.lightsail:stellar-sdk)." + }, + "plugins": [ + { + "name": "java-stellar-sdk", + "source": "./", + "description": "Teaches AI coding agents how to build Stellar blockchain applications using the Java Stellar SDK: transactions, operations, Horizon API, Soroban smart contracts, XDR/SCVal, and SEP protocols.", + "version": "0.1.0", + "author": { + "name": "overcat" + }, + "homepage": "https://github.com/lightsail-network/java-stellar-sdk", + "license": "Apache-2.0", + "category": "development", + "tags": ["stellar", "blockchain", "java", "sdk", "soroban", "agent-skill"] + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 000000000..d5c4e9a51 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "java-stellar-sdk", + "description": "Agent skill that teaches AI coding agents how to build Stellar blockchain applications with the Java Stellar SDK (network.lightsail:stellar-sdk): transactions, operations, Horizon, Soroban, XDR/SCVal, and SEP protocols.", + "version": "0.1.0", + "author": { + "name": "overcat" + }, + "homepage": "https://github.com/lightsail-network/java-stellar-sdk", + "license": "Apache-2.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d1565ac..dd99139f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Pending ### Update +- docs: add an [Agent Skill](https://agentskills.io/) for the Java Stellar SDK under `skills/`, plus Claude Code plugin manifests in `.claude-plugin/`. The skill gives AI coding agents concise, Stellar-specific guidance (transactions, operations, Horizon, Soroban, XDR/SCVal, and SEP protocols) when generating application code with `stellar-sdk`. - feat: add SEP-0046, SEP-0047, and SEP-0048 contract introspection support. New `ContractMeta`, `ContractSpec`, and `ContractInfo` wrappers under `org.stellar.sdk.contract` parse contract Wasm metadata and interface specs locally. `SorobanServer` adds `getContractWasm`, `getContractWasmByHash`, `getContractMeta`, `getContractSpec`, and `getContractInfo` for RPC-backed retrieval. ## 3.0.0 diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 000000000..32aac8f0f --- /dev/null +++ b/skills/README.md @@ -0,0 +1,61 @@ +# Agent Skill for the Java Stellar SDK + +This directory contains an [Agent Skills](https://agentskills.io/) compatible skill for +developers building Stellar applications in Java with the `stellar-sdk` package +(`network.lightsail:stellar-sdk`). + +**Audience: SDK users, not java-stellar-sdk contributors.** The skill is a portable +artifact that can be installed in another project so coding agents have concise, +Stellar-specific guidance while generating application code. + +## Contents + +```text +skills/java-stellar-sdk/ + SKILL.md # entry point: install, conventions, critical rules, examples + references/ # topic-specific reference files loaded on demand +``` + +The skill is self-contained: examples are embedded directly, and external pointers use +absolute URLs such as the SDK Javadoc and GitHub. It should continue to work after being +copied into a project that does not contain this repository. + +## Installation options + +| Environment | How to use this skill | +| --- | --- | +| Claude Code plugin | Add this repository as a plugin marketplace and install the `java-stellar-sdk` plugin. The marketplace manifest lives at `.claude-plugin/marketplace.json`. | +| Claude Code project skill | Copy `skills/java-stellar-sdk/` into your project at `.claude/skills/java-stellar-sdk/`. | +| Claude Code personal skill | Copy `skills/java-stellar-sdk/` into `~/.claude/skills/java-stellar-sdk/`. | +| Other Agent Skills compatible tools | Copy `skills/java-stellar-sdk/SKILL.md` and its `references/` directory into the location your tool uses for skills or reusable instructions. | +| Generic coding agents | Point your agent instructions file at `SKILL.md`, or paste the relevant reference file into the agent context. | + +For Claude Code's skill and plugin behavior, see the Claude Code documentation for +[skills](https://docs.anthropic.com/en/docs/claude-code/skills) and +[plugin marketplaces](https://docs.anthropic.com/en/docs/claude-code/plugin-marketplaces). + +## Discovery and marketplaces + +This repository is set up for two broad distribution paths: + +- **Claude Code marketplaces:** `.claude-plugin/marketplace.json` describes this repository + as a Claude Code plugin source. +- **Agent Skills indexes and registries:** public directories or aggregators may discover + repositories containing `SKILL.md` files. Examples include community indexes such as + [skillsmp.com](https://skillsmp.com/) and any future Agent Skills compatible catalogs. + +Third-party indexes are convenience discovery channels only. They are not required to use +the skill, and their indexing behavior, ranking, and update schedule are outside this +project's control. + +## Not shipped via Maven + +Adding the `network.lightsail:stellar-sdk` dependency installs the SDK package only. It does +**not** install this skill. Install the skill through a plugin marketplace, by copying this +directory, or by cloning this repository. + +## Maintenance + +The skill is maintained alongside the SDK so examples and API references can stay in sync. +Keep embedded examples small, explicit, and aligned with the SDK Javadoc. When SDK APIs +change, review `SKILL.md` and the files under `references/` as part of the same change. diff --git a/skills/java-stellar-sdk/SKILL.md b/skills/java-stellar-sdk/SKILL.md new file mode 100644 index 000000000..56ac5d6b3 --- /dev/null +++ b/skills/java-stellar-sdk/SKILL.md @@ -0,0 +1,251 @@ +--- +name: java-stellar-sdk +description: Build Stellar blockchain applications in Java using stellar-sdk (network.lightsail:stellar-sdk). Use for transaction building, signing, Horizon queries, Soroban RPC, smart contract deployment/invocation, XDR/SCVal conversion, and SEP integrations. +license: Apache-2.0 +compatibility: Published artifacts target Java 8 bytecode (require Java 8+). Building from source requires JDK 21. +metadata: + sdk_package: network.lightsail:stellar-sdk + repository: lightsail-network/java-stellar-sdk + docs: https://javadoc.io/doc/network.lightsail/stellar-sdk +--- + +# Stellar Java SDK (`network.lightsail:stellar-sdk`) + +Write correct Java for the Stellar network using the `stellar-sdk` package. This file is +self-contained; load a file from `references/` only when the task needs that topic. + +## Install + +Maven: + +```xml + + network.lightsail + stellar-sdk + 3.0.0 + +``` + +Gradle: + +```groovy +implementation 'network.lightsail:stellar-sdk:3.0.0' +``` + +## Import style + +Public APIs live under `org.stellar.sdk`; operations under `org.stellar.sdk.operations`; +the contract helpers under `org.stellar.sdk.contract`; SCVal helpers under +`org.stellar.sdk.scval`; exceptions under `org.stellar.sdk.exception`, with the high-level +contract-client exceptions under `org.stellar.sdk.contract.exception`. + +```java +import org.stellar.sdk.Asset; +import org.stellar.sdk.KeyPair; +import org.stellar.sdk.Network; +import org.stellar.sdk.Server; +import org.stellar.sdk.Transaction; +import org.stellar.sdk.TransactionBuilder; +import org.stellar.sdk.operations.PaymentOperation; +import org.stellar.sdk.scval.Scv; +``` + +## Clients + +- `Server` — Horizon client. See `references/horizon.md`. +- `SorobanServer` — Soroban RPC client (`Closeable`). See `references/soroban.md`. +- `ContractClient` — high-level Soroban contract client (`Closeable`) that wraps the + simulate → prepare → sign → submit flow. See `references/soroban.md`. + +The SDK is **synchronous** (built on OkHttp); there is no separate async client. + +## Java conventions (read this first) + +These are the things that most often trip people up with *this* SDK: + +- **Operations use Lombok builders.** Every operation is built with `XxxOperation.builder()... + .build()`, e.g. `PaymentOperation.builder().destination(d).asset(a).amount(amt).build()`. + `InvokeHostFunctionOperation` instead exposes static factories (see `references/operations.md`). +- **There are three asset types — don't mix them up.** `Asset` (payments, offers, paths), + `ChangeTrustAsset` (the `ChangeTrustOperation` line, wraps an `Asset` or `LiquidityPool`), and + `TrustLineAsset` (balances/trustline entries). See `references/assets.md`. +- **Amounts and balances are `BigDecimal`.** `new BigDecimal("10.5")`, never `double`/`float`. +- **`G...` and `M...` both work as a destination.** Address strings accept plain ed25519 + accounts and SEP-23 muxed accounts; use `MuxedAccount` to build/parse `M...` (see + `references/sep.md`). +- **Clients are `Closeable`.** `Server.close()`; wrap `SorobanServer`/`ContractClient` in + try-with-resources. `SSEStream` from `.stream(...)` must also be closed to stop it. +- **SDK exceptions are unchecked.** Everything extends `RuntimeException` + (`org.stellar.sdk.exception.SdkException`), so the compiler will not force you to catch them — + you still should. The common supertype for network/RPC errors is `NetworkException`. +- **`KeyPair.getSecretSeed()` returns `char[]`,** not `String`, so it can be zeroed after use. +- **Android:** works out of the box on API level 28+. For lower levels add the + [Java Stellar SDK Android SPI](https://github.com/lightsail-network/java-stellar-sdk-android-spi). +- **Example syntax.** The SDK runs on **Java 8+**, but the snippets in this skill use some + Java 9–16 conveniences for brevity. On Java 8, substitute equivalents: `List.of(...)` → + `Arrays.asList(...)`, `Map.of(...)` → a populated `LinkedHashMap`, `"x".repeat(n)` → a literal, + pattern-matching `instanceof` → a classic cast, and `java.net.http` → `HttpURLConnection` or + any HTTP client. + +## Critical rules (do not violate) + +1. **`BigDecimal` amounts, never `double`/`float`.** Use `new BigDecimal("350.1234567")`, + not `350.1234567`. Lumens have 7 decimal places; binary floats lose precision. +2. **Prefer loading secret seeds at runtime** (env / a secret manager) over embedding + them in source: `KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY"))`. +3. **Always set a timeout** with `.setTimeout(...)` (or explicit time bounds). A + transaction without one can hang in the pending pool indefinitely. +4. **Use the correct network.** `Network.TESTNET` vs `Network.PUBLIC`. Signatures are + network-specific; a testnet-signed transaction is invalid on mainnet. +5. **`TransactionBuilder.build()` increments the source account's sequence number.** If you + build but do not submit (or submission fails with `tx_bad_seq`), reload the account with + `server.loadAccount(...)` before building again. +6. **For Soroban, prepare before signing.** `sorobanServer.prepareTransaction(tx)` (or + `ContractClient.invoke(...)` which simulates by default) fills in footprint, auth, and + resource fees. Signing before this produces an invalid transaction. +7. **Convert contract args with `Scv.to*`** and read results with `Scv.from*`. Never pass + raw Java values to a contract. +8. **Restore archived state** when simulation reports archived entries (see + `references/soroban.md`). +9. **Close clients.** Call `server.close()` for `Server`; use try-with-resources for + `SorobanServer` and `ContractClient` (both implement `Closeable`). +10. **Catch specific SDK exceptions** (`BadRequestException`, `BadResponseException`, + `PrepareTransactionException`, …), not a broad `Exception`. They all extend + `SdkException`; network/HTTP errors additionally share the `NetworkException` supertype, + but `PrepareTransactionException` and the contract-lifecycle exceptions extend + `SdkException` directly. Network/RPC exceptions live in `org.stellar.sdk.exception`; + `ContractClient`/`AssembledTransaction` errors live in `org.stellar.sdk.contract.exception`. + +## Minimal end-to-end examples + +### 1. Create / load a keypair + +```java +import org.stellar.sdk.KeyPair; + +// New random account (fund it before use — see example 2). +KeyPair kp = KeyPair.random(); +System.out.println(kp.getAccountId()); // G... (safe to share) +System.out.println(kp.getSecretSeed()); // S... (secret — never log or commit in real code) + +// Load an existing account from the environment. +KeyPair signer = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); + +// Verify-only keypair (cannot sign). +KeyPair publicOnly = KeyPair.fromAccountId("GD2JXEFGEO53CNQ22KN2ICOQ2LOASCABQHAIOMLZV265C246PFKKHPYU"); +``` + +### 2. Build, sign, and submit a payment + +```java +import java.math.BigDecimal; +import org.stellar.sdk.*; +import org.stellar.sdk.operations.PaymentOperation; +import org.stellar.sdk.responses.TransactionResponse; + +KeyPair source = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); +String destination = "GD2JXEFGEO53CNQ22KN2ICOQ2LOASCABQHAIOMLZV265C246PFKKHPYU"; + +Server server = new Server("https://horizon-testnet.stellar.org"); + +// Reload right before building so the sequence number is current. +TransactionBuilderAccount account = server.loadAccount(source.getAccountId()); + +PaymentOperation payment = + PaymentOperation.builder() + .destination(destination) + .asset(Asset.createNativeAsset()) + .amount(new BigDecimal("350.1234567")) // BigDecimal amount + .build(); + +Transaction transaction = + new TransactionBuilder(account, Network.TESTNET) + .setBaseFee(Transaction.MIN_BASE_FEE) + .addMemo(Memo.text("Hello, Stellar!")) + .addOperation(payment) + .setTimeout(30) + .build(); + +transaction.sign(source); +TransactionResponse response = server.submitTransaction(transaction); +System.out.println(response.getHash()); +server.close(); +``` + +New testnet accounts can be funded by Friendbot: +`GET https://friendbot.stellar.org?addr=`. + +### 3. Query Horizon + +```java +import org.stellar.sdk.Server; +import org.stellar.sdk.requests.RequestBuilder; +import org.stellar.sdk.responses.Page; +import org.stellar.sdk.responses.operations.OperationResponse; + +Server server = new Server("https://horizon.stellar.org"); + +// Request builders are chainable; .execute() runs the HTTP request. +Page payments = + server.payments() + .forAccount("GB6NVEN5HSUBKMYCE5ZOWSK5K23TBWRUQLZY3KNMXUZ3AQ2ESC4MY4AQ") + .order(RequestBuilder.Order.DESC) + .limit(10) + .execute(); +for (OperationResponse op : payments.getRecords()) { + System.out.println(op.getType()); +} +``` + +### 4. Invoke a Soroban contract (read-only) + +For contracts you call repeatedly, prefer **generated typed bindings** (see +`references/bindings.md`). The hand-written form with `ContractClient` is: + +```java +import java.util.List; +import org.stellar.sdk.Network; +import org.stellar.sdk.contract.ContractClient; +import org.stellar.sdk.scval.Scv; +import org.stellar.sdk.xdr.SCVal; + +// A read-only call needs no signer, but `source` is required and must be an account +// that already exists on the target network — simulate() loads its sequence via +// server.getAccount(), which throws AccountNotFoundException if the account is missing. +String source = "GD2JXEFGEO53CNQ22KN2ICOQ2LOASCABQHAIOMLZV265C246PFKKHPYU"; + +try (ContractClient client = + new ContractClient( + "CACZTW72246RA2MOCNKUBRRRRPT26UZ7LXE5ZHH44OGKIMCTQJ74O4D5", + "https://soroban-testnet.stellar.org:443", + Network.TESTNET)) { + SCVal result = + client + .invoke("hello", List.of(Scv.toSymbol("world")), source, null, null, + (int) org.stellar.sdk.Transaction.MIN_BASE_FEE) + .result(); // read-only: no signer needed + System.out.println(result); +} +``` + +For a state-changing call, pass `source` and `signer`, then `signAndSubmit(...)`. See +`references/soroban.md`. + +## Reference index + +Load on demand: + +- `references/quickstart.md` — install → keypair → fund → payment → query → contract. +- `references/transactions.md` — transaction lifecycle, memos, preconditions, fee bump, + multisig, XDR round-trips, sequence-number pitfalls. +- `references/assets.md` — `Asset` vs `ChangeTrustAsset` vs `TrustLineAsset`, native vs + alphanum4/12, canonical form. +- `references/operations.md` — catalog of every operation and its builder. +- `references/horizon.md` — Horizon client, request builders, pagination, streaming, errors. +- `references/soroban.md` — Soroban RPC client + `ContractClient`/`AssembledTransaction`. +- `references/bindings.md` — generate typed contract clients with `stellar-contract-bindings`. +- `references/xdr_scval.md` — SCVal conversion, `Address`, XDR encode/decode. +- `references/sep.md` — SEP support matrix and common flows (SEP-02/05/10/23/35/45/...). +- `references/troubleshooting.md` — exception hierarchy and common failures. + +Full API docs (Javadoc): https://javadoc.io/doc/network.lightsail/stellar-sdk diff --git a/skills/java-stellar-sdk/references/assets.md b/skills/java-stellar-sdk/references/assets.md new file mode 100644 index 000000000..2e257fe74 --- /dev/null +++ b/skills/java-stellar-sdk/references/assets.md @@ -0,0 +1,68 @@ +# Assets: `Asset` vs `ChangeTrustAsset` vs `TrustLineAsset` + +The Java SDK models assets with **three distinct types**. Using the wrong one is the most +common compile error when writing transactions. They are not interchangeable, but the latter +two wrap an `Asset`. + +| Type | Use it for | Build it with | +| --- | --- | --- | +| `Asset` | Payments, path payments, offers, clawback — anything that moves an asset | `Asset.create(...)`, `Asset.createNativeAsset()` | +| `ChangeTrustAsset` | The `line` of a `ChangeTrustOperation` (a trustline target, which may be a classic asset **or** a liquidity pool) | `new ChangeTrustAsset(asset)` / `new ChangeTrustAsset(liquidityPool)` | +| `TrustLineAsset` | Trustline / balance entries you read back, and `RevokeTrustlineSponsorshipOperation` | `new TrustLineAsset(asset)` / `new TrustLineAsset(liquidityPoolId)` | + +## `Asset` + +`Asset` is abstract with three concrete subtypes, picked automatically by code length: + +- `AssetTypeNative` — native XLM. +- `AssetTypeCreditAlphaNum4` — codes of 1–4 characters. +- `AssetTypeCreditAlphaNum12` — codes of 5–12 characters. + +```java +import org.stellar.sdk.Asset; + +Asset xlm = Asset.createNativeAsset(); +Asset usdc = Asset.create("USDC", "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"); +Asset alt = Asset.create("native"); // canonical form: "native" +Asset alt2 = Asset.create("USDC:GA5Z...KZVN"); // canonical form: "CODE:ISSUER" +Asset alt3 = Asset.create("credit_alphanum4", "USDC", "GA5Z...KZVN"); +``` + +`Asset.create(String canonicalForm)` accepts `"native"` or `"CODE:ISSUER"`. You generally do +**not** instantiate the subtypes directly. + +## `ChangeTrustAsset` (for `ChangeTrustOperation`) + +```java +import org.stellar.sdk.ChangeTrustAsset; +import org.stellar.sdk.operations.ChangeTrustOperation; +import java.math.BigDecimal; + +ChangeTrustOperation trust = + ChangeTrustOperation.builder() + .asset(new ChangeTrustAsset(Asset.create("USDC", "GA5Z...KZVN"))) + .limit(new BigDecimal("1000")) // set limit to "0" to remove the trustline + .build(); +``` + +A `ChangeTrustAsset` can also wrap a `LiquidityPool` to add a trustline to a pool share. + +## `TrustLineAsset` (balances & revoke-trustline-sponsorship) + +```java +import org.stellar.sdk.TrustLineAsset; +import org.stellar.sdk.operations.RevokeTrustlineSponsorshipOperation; + +TrustLineAsset line = new TrustLineAsset(Asset.create("USDC", "GA5Z...KZVN")); +``` + +> Note: `SetTrustlineFlagsOperation` and `AllowTrustOperation` take a plain `Asset`, **not** a +> `TrustLineAsset`. + +Account balances returned by Horizon expose `getTrustLineAsset()` on each `Balance`, so you can +recover a `TrustLineAsset` directly from a queried account. + +## Amounts + +Asset amounts and trustline limits are **`BigDecimal`** with up to 7 decimal places. Never use +`double`/`float` — they lose precision. Construct from a string: `new BigDecimal("350.1234567")`. diff --git a/skills/java-stellar-sdk/references/bindings.md b/skills/java-stellar-sdk/references/bindings.md new file mode 100644 index 000000000..0f39d8448 --- /dev/null +++ b/skills/java-stellar-sdk/references/bindings.md @@ -0,0 +1,68 @@ +# Generated contract bindings + +For any non-trivial Soroban contract work, **generate a typed Java client** instead of +hand-writing `Scv` conversions and `ContractClient.invoke(...)` calls. The generated client is +type-safe, mirrors the contract's functions and custom types, and removes a whole class of +encoding bugs. + +Tool: [`stellar-contract-bindings`](https://github.com/lightsail-network/stellar-contract-bindings) +(supports Java, Python, Flutter/Dart, PHP, Swift). There is also a web generator at +https://stellar-contract-bindings.fly.dev/. + +## Generate + +```bash +pip install stellar-contract-bindings + +stellar-contract-bindings java \ + --contract-id CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF \ + --rpc-url https://mainnet.sorobanrpc.com \ + --output ./bindings \ + --package com.example +``` + +This writes a `Client` class (plus any custom struct/enum/error types the contract defines) +into the given package. The generated `Client extends ContractClient`, so it is `Closeable` and +shares the same lifecycle as the hand-written client. + +## Use + +Each contract function becomes a typed method that returns an +`AssembledTransaction`. The trailing arguments are the same as +`ContractClient.invoke`: `source` account id, `signer` (nullable for read-only), and `baseFee`. + +```java +import java.util.List; +import org.stellar.sdk.KeyPair; +import org.stellar.sdk.Network; +import org.stellar.sdk.contract.AssembledTransaction; +import com.example.Client; // generated + +KeyPair kp = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); + +try (Client client = + new Client( + "CDOAW6D7NXAPOCO7TFAWZNJHK62E3IYRGNRVX3VOXNKNVOXCLLPJXQCF", + "https://mainnet.sorobanrpc.com", + Network.PUBLIC)) { + + // Read-only: signer can be null; read the simulated result. + AssembledTransaction> readTx = + client.hello("World".getBytes(), kp.getAccountId(), null, 100); + List value = readTx.result(); + + // State-changing: pass a signer, then submit. The exact method name, argument + // types, and AssembledTransaction result type come from the contract's spec. + client.increment(kp.getAccountId(), kp, 100).signAndSubmit(kp, false); +} +``` + +The returned `AssembledTransaction` is exactly the one documented in `soroban.md`, so all of +its helpers apply: `result()`, `sign(...)`, `signAuthEntries(...)`, +`needsNonInvokerSigningBy(...)`, `restoreFootprint()`, `signAndSubmit(...)`, `submit()`. + +## When to hand-write instead + +Use `ContractClient.invoke(...)` directly (see `soroban.md`) only when you cannot run the code +generator — e.g. a one-off call, or a contract whose spec you do not have locally. For anything +you maintain, regenerate the bindings when the contract interface changes. diff --git a/skills/java-stellar-sdk/references/horizon.md b/skills/java-stellar-sdk/references/horizon.md new file mode 100644 index 000000000..1c42d7396 --- /dev/null +++ b/skills/java-stellar-sdk/references/horizon.md @@ -0,0 +1,128 @@ +# Horizon API + +`Server` is the Horizon client (synchronous, built on OkHttp). Remember to `close()` it. + +```java +import org.stellar.sdk.Server; + +Server testnet = new Server("https://horizon-testnet.stellar.org"); +Server public_ = new Server("https://horizon.stellar.org"); +``` + +## Top-level methods + +```java +server.loadAccount("G..."); // -> TransactionBuilderAccount (with current sequence) +server.submitTransaction(tx); // submit a built+signed Transaction (or FeeBumpTransaction) +server.submitTransactionAsync(tx); // submit and return immediately (async-pending) +server.close(); // release the HTTP client +``` + +## Request builders + +Each returns a chainable builder; `.execute()` runs the HTTP request. + +```java +server.root(); +server.accounts(); +server.assets(); +server.claimableBalances(); +server.effects(); +server.feeStats(); +server.ledgers(); +server.liquidityPools(); +server.offers(); +server.operations(); +server.orderBook(); +server.strictReceivePaths(); +server.strictSendPaths(); +server.payments(); +server.trades(); +server.tradeAggregations(...); +server.transactions(); +``` + +## Chaining / filters + +```java +import org.stellar.sdk.requests.RequestBuilder; +import org.stellar.sdk.responses.Page; +import org.stellar.sdk.responses.operations.OperationResponse; + +Page records = + server.payments() + .forAccount("G...") // endpoint-specific filter + .order(RequestBuilder.Order.DESC) // ASC (default) or DESC + .limit(50) // max 200 + .cursor("now") // paging token; "now" for live tail (streaming) + .execute(); +``` + +Other filters by endpoint: `forLedger`, `forTransaction`, `forOperation`, `forAsset`, +`forClaimant`, `forSigner`, `forLiquidityPool`, etc. Single-resource lookups return the +response directly: `server.accounts().account("G...")`, +`server.transactions().transaction("")`. + +## Pagination + +Page through results with the paging token of the last record: + +```java +import org.stellar.sdk.requests.PaymentsRequestBuilder; + +PaymentsRequestBuilder builder = server.payments().forAccount("G...").limit(100); +List all = new ArrayList<>(); +String cursor = null; +while (true) { + if (cursor != null) builder.cursor(cursor); + List records = builder.execute().getRecords(); + if (records.isEmpty()) break; + all.addAll(records); + cursor = records.get(records.size() - 1).getPagingToken(); +} +``` + +`Page` also exposes `getNextPage(okHttpClient)` to follow the HAL "next" link directly. + +## Streaming (Server-Sent Events) + +```java +import org.stellar.sdk.requests.EventListener; +import org.stellar.sdk.requests.SSEStream; +import org.stellar.sdk.responses.operations.OperationResponse; + +SSEStream stream = + server.payments() + .forAccount("G...") + .cursor("now") + .stream(new EventListener() { + @Override public void onEvent(OperationResponse payment) { + System.out.println(payment.getId()); + } + @Override public void onFailure(java.util.Optional error, java.util.Optional code) {} + }); +// ... later ... +stream.close(); // stop streaming +``` + +`cursor("now")` starts at the present. On disconnect the SDK reconnects from the last seen +cursor. Always `close()` the stream when done. + +## Errors + +```java +import org.stellar.sdk.exception.*; + +try { + server.accounts().account("G..."); +} catch (BadRequestException e) { // 4xx (e.g. 400/404), includes problem details + System.out.println(e.getMessage()); +} catch (BadResponseException e) { // 5xx + System.out.println(e.getMessage()); +} catch (NetworkException e) { // supertype: transport/other network errors + System.out.println(e.getMessage()); +} +``` + +`NetworkException` is the common supertype; `BadRequestException` and `BadResponseException` +are subclasses. See `troubleshooting.md` for result codes on failed submissions. diff --git a/skills/java-stellar-sdk/references/operations.md b/skills/java-stellar-sdk/references/operations.md new file mode 100644 index 000000000..09332fbad --- /dev/null +++ b/skills/java-stellar-sdk/references/operations.md @@ -0,0 +1,105 @@ +# Operations + +Each operation lives in `org.stellar.sdk.operations` and is built with a Lombok builder, then +added to a transaction via `TransactionBuilder.addOperation(op)`. Every operation builder +optionally accepts `.sourceAccount(accountId)` to set a per-operation source account (defaults +to the transaction source). Amounts and balances are **`BigDecimal`**. + +```java +import org.stellar.sdk.operations.PaymentOperation; +import org.stellar.sdk.Asset; +import java.math.BigDecimal; + +PaymentOperation op = + PaymentOperation.builder() + .destination("GD2J...HPYU") + .asset(Asset.createNativeAsset()) + .amount(new BigDecimal("10")) + .build(); +``` + +## Payments & accounts + +| Operation class | Purpose | +| --- | --- | +| `CreateAccountOperation` | Create & fund a new account (`startingBalance`) | +| `PaymentOperation` | Send an asset | +| `PathPaymentStrictSendOperation` | Send exact amount, receive ≥ min | +| `PathPaymentStrictReceiveOperation` | Receive exact amount, send ≤ max | +| `AccountMergeOperation` | Merge account into destination | +| `BumpSequenceOperation` | Bump the account sequence number | +| `ManageDataOperation` | Set/remove an account data entry | + +## Trustlines & flags + +| Operation class | Purpose | +| --- | --- | +| `ChangeTrustOperation` | Create/update/remove a trustline (`limit` `"0"` removes) | +| `SetTrustlineFlagsOperation` | Authorize/freeze a trustline (`trustor`, `asset`, clear/set flags) | +| `AllowTrustOperation` | Legacy authorize (prefer `SetTrustlineFlagsOperation`) | +| `SetOptionsOperation` | Signers, thresholds, home domain, flags, inflation dest | + +`ChangeTrustOperation` takes a `ChangeTrustAsset` (not a plain `Asset`). Note that +`SetTrustlineFlagsOperation` and `AllowTrustOperation` take a plain `Asset`, while +`TrustLineAsset` is used for balances and `RevokeTrustlineSponsorshipOperation`. See +`assets.md` for the three asset types and when to use each. + +```java +import org.stellar.sdk.ChangeTrustAsset; +import org.stellar.sdk.operations.ChangeTrustOperation; + +ChangeTrustOperation trust = + ChangeTrustOperation.builder() + .asset(new ChangeTrustAsset(Asset.create("USDC", "GA5Z...ZVN"))) + .limit(new BigDecimal("1000")) + .build(); +``` + +## Offers & liquidity pools + +| Operation class | Purpose | +| --- | --- | +| `ManageSellOfferOperation` | Create/update/delete a sell offer | +| `ManageBuyOfferOperation` | Create/update/delete a buy offer | +| `CreatePassiveSellOfferOperation` | Passive sell offer | +| `LiquidityPoolDepositOperation` | Deposit into an AMM pool | +| `LiquidityPoolWithdrawOperation` | Withdraw from an AMM pool | + +## Claimable balances + +| Operation class | Purpose | +| --- | --- | +| `CreateClaimableBalanceOperation` | Create a claimable balance (`asset`, `amount`, `claimants`) | +| `ClaimClaimableBalanceOperation` | Claim one (`balanceId`) | + +## Sponsorship (wrap the sponsored ops between begin/end) + +`BeginSponsoringFutureReservesOperation` … sponsored ops … +`EndSponsoringFutureReservesOperation`. + +Revoke existing sponsorships with the matching class: +`RevokeAccountSponsorshipOperation`, `RevokeTrustlineSponsorshipOperation`, +`RevokeOfferSponsorshipOperation`, `RevokeDataSponsorshipOperation`, +`RevokeClaimableBalanceSponsorshipOperation`, `RevokeSignerSponsorshipOperation`. + +## Clawback + +`ClawbackOperation` (`asset`, `from`, `amount`), `ClawbackClaimableBalanceOperation` +(`balanceId`). Requires the asset's clawback flag. + +## Soroban operations + +See `soroban.md` for the full lifecycle; `InvokeHostFunctionOperation` exposes static builder +factories for every host function: + +| Static builder | Purpose | +| --- | --- | +| `InvokeHostFunctionOperation.invokeContractFunctionOperationBuilder(contractId, functionName, parameters)` | Call a contract function | +| `InvokeHostFunctionOperation.uploadContractWasmOperationBuilder(wasmBytes)` | Upload Wasm bytecode | +| `InvokeHostFunctionOperation.createContractOperationBuilder(wasmId, address, constructorArgs, salt)` | Instantiate a contract | +| `InvokeHostFunctionOperation.createStellarAssetContractOperationBuilder(asset)` | Deploy the SAC for a classic asset | +| `ExtendFootprintTTLOperation` | Extend ledger entry TTL | +| `RestoreFootprintOperation` | Restore archived state | + +Soroban operations must be **simulated/prepared** before signing so the footprint, auth, and +resource fee are populated. diff --git a/skills/java-stellar-sdk/references/quickstart.md b/skills/java-stellar-sdk/references/quickstart.md new file mode 100644 index 000000000..bc93e287b --- /dev/null +++ b/skills/java-stellar-sdk/references/quickstart.md @@ -0,0 +1,112 @@ +# Quickstart + +The fast path from zero to a working Stellar app. All amounts are `BigDecimal`; secrets come +from the environment. See `transactions.md` for the full lifecycle. + +## Install (Gradle) + +```groovy +implementation 'network.lightsail:stellar-sdk:3.0.0' +``` + +## Generate or load a keypair + +```java +import org.stellar.sdk.KeyPair; + +KeyPair kp = KeyPair.random(); // new account +KeyPair signer = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); // existing +KeyPair publicOnly = KeyPair.fromAccountId("GD2J...HPYU"); // verify-only, cannot sign +``` + +## Fund a testnet account (Friendbot) + +Hit `https://friendbot.stellar.org?addr=` with any HTTP client. With `HttpURLConnection` +(Java 8): + +```java +import java.net.HttpURLConnection; +import java.net.URL; + +KeyPair kp = KeyPair.random(); +URL url = new URL("https://friendbot.stellar.org?addr=" + kp.getAccountId()); +HttpURLConnection conn = (HttpURLConnection) url.openConnection(); +conn.getResponseCode(); // triggers the request +``` + +## Native XLM payment + +```java +import java.math.BigDecimal; +import org.stellar.sdk.*; +import org.stellar.sdk.operations.PaymentOperation; + +Server server = new Server("https://horizon-testnet.stellar.org"); +KeyPair source = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); +TransactionBuilderAccount account = server.loadAccount(source.getAccountId()); + +Transaction tx = + new TransactionBuilder(account, Network.TESTNET) + .setBaseFee(Transaction.MIN_BASE_FEE) + .addOperation( + PaymentOperation.builder() + .destination("GD2JXEFGEO53CNQ22KN2ICOQ2LOASCABQHAIOMLZV265C246PFKKHPYU") + .asset(Asset.createNativeAsset()) + .amount(new BigDecimal("100.5")) + .build()) + .setTimeout(30) + .build(); +tx.sign(source); +System.out.println(server.submitTransaction(tx).getHash()); +server.close(); +``` + +## Custom (non-native) asset payment + +The recipient must already hold a trustline to the asset (see `ChangeTrustOperation` in +`operations.md`). + +```java +import org.stellar.sdk.Asset; + +Asset usdc = Asset.create("USDC", "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"); +// PaymentOperation.builder().destination(dest).asset(usdc).amount(new BigDecimal("25.0")).build(); +``` + +## Basic Horizon query + +```java +import org.stellar.sdk.Server; +import org.stellar.sdk.responses.AccountResponse; + +Server server = new Server("https://horizon.stellar.org"); +AccountResponse account = server.accounts().account("GB6N...Y4AQ"); +System.out.println(account.getBalances()); +``` + +## Basic Soroban contract call (read-only) + +```java +import java.util.List; +import org.stellar.sdk.Network; +import org.stellar.sdk.Transaction; +import org.stellar.sdk.contract.ContractClient; +import org.stellar.sdk.scval.Scv; + +try (ContractClient client = + new ContractClient( + "CACZTW72246RA2MOCNKUBRRRRPT26UZ7LXE5ZHH44OGKIMCTQJ74O4D5", + "https://soroban-testnet.stellar.org:443", + Network.TESTNET)) { + // A source account id is required (no signer needed). It must already exist on the + // network — simulate() loads its sequence and 404s with AccountNotFoundException. + String source = "GD2JXEFGEO53CNQ22KN2ICOQ2LOASCABQHAIOMLZV265C246PFKKHPYU"; + Object result = + client + .invoke("hello", List.of(Scv.toSymbol("world")), source, null, null, + (int) Transaction.MIN_BASE_FEE) + .result(); +} +``` + +Learn more: https://javadoc.io/doc/network.lightsail/stellar-sdk diff --git a/skills/java-stellar-sdk/references/sep.md b/skills/java-stellar-sdk/references/sep.md new file mode 100644 index 000000000..595ca77c2 --- /dev/null +++ b/skills/java-stellar-sdk/references/sep.md @@ -0,0 +1,112 @@ +# SEP support + +Stellar Ecosystem Proposals supported by the Java SDK. Unlike some SDKs, support is spread +across top-level classes rather than a single `sep` package. + +| SEP | Topic | Where | +| --- | --- | --- | +| SEP-02 | Federation | `org.stellar.sdk.federation.Federation` | +| SEP-05 | Key derivation (BIP-39 seed) | `KeyPair.fromBip39Seed(byte[], int)` | +| SEP-10 | Web authentication | `org.stellar.sdk.Sep10Challenge` | +| SEP-23 | Muxed accounts | `org.stellar.sdk.MuxedAccount` | +| SEP-29 | Memo-required check | submission helpers / `AccountRequiresMemoException` | +| SEP-35 | Operation IDs (TOID) | `org.stellar.sdk.TOID` | +| SEP-45 | Soroban web auth | `org.stellar.sdk.Sep45Challenge` | +| SEP-46 | Contract meta | `SorobanServer.getContractMeta` | +| SEP-48 | Contract spec | `SorobanServer.getContractSpec` / `getContractInfo` | +| SEP-51 | XDR JSON encoding | XDR base classes (see `xdr_scval.md`) | +| SEP-53 | Message signing | `KeyPair.signMessage` / `verifyMessage` | + +## SEP-02: federation lookup + +```java +import org.stellar.sdk.federation.Federation; +import org.stellar.sdk.federation.FederationResponse; + +Federation federation = new Federation(); +FederationResponse record = federation.resolveAddress("alice*example.com"); +System.out.println(record.getAccountId()); +// federation.resolveAccountId("G...", "example.com"); // reverse lookup +``` + +## SEP-05: key derivation from a BIP-39 seed + +```java +import org.stellar.sdk.KeyPair; + +// bip39Seed is the 64-byte seed derived from a mnemonic (e.g. via a BIP-39 library). +KeyPair kp = KeyPair.fromBip39Seed(bip39Seed, 0); // account index 0 +``` + +## SEP-10: web authentication (server side) + +```java +import org.stellar.sdk.Sep10Challenge; +import org.stellar.sdk.Network; +import org.stellar.sdk.TimeBounds; + +long now = System.currentTimeMillis() / 1000L; +org.stellar.sdk.Transaction challenge = + Sep10Challenge.newChallenge( + serverKeyPair, // KeyPair of the server's signing key + Network.TESTNET, + "G...", // client account id + "example.com", // home domain + "auth.example.com", // web auth domain + new TimeBounds(now, now + 300)); // challenge lifetime + +// The client signs the challenge XDR and returns it; then verify (note the argument +// order: serverAccountId, network, homeDomain, webAuthDomain, ...): +Sep10Challenge.ChallengeTransaction read = + Sep10Challenge.readChallengeTransaction( + signedChallengeXdr, serverAccountId, Network.TESTNET, "example.com", "auth.example.com"); + +java.util.Set signers = + Sep10Challenge.verifyChallengeTransactionThreshold( + signedChallengeXdr, serverAccountId, Network.TESTNET, "example.com", "auth.example.com", + threshold, signerSet); // signerSet is a Set +``` + +(Several overloads exist, including ones that add a client domain and memo; check the Javadoc +for the exact argument list you need.) + +## SEP-23: muxed accounts + +```java +import org.stellar.sdk.MuxedAccount; +import java.math.BigInteger; + +MuxedAccount muxed = new MuxedAccount("G...", BigInteger.valueOf(12345)); +String mAddress = muxed.getAddress(); // M... +MuxedAccount parsed = new MuxedAccount("M..."); // parse an existing M-address +``` + +## SEP-35: TOID (total order ID) + +```java +import org.stellar.sdk.TOID; + +long id = new TOID(ledgerSequence, transactionOrder, operationIndex).toInt64(); +TOID toid = TOID.fromInt64(id); +``` + +## SEP-45: Soroban web authentication + +Same idea as SEP-10 but with contract authorization entries, in `org.stellar.sdk.Sep45Challenge`: +`buildChallengeAuthorizationEntries`, `readChallengeAuthorizationEntries`, +`verifyChallengeAuthorizationEntries`. + +## SEP-46 / SEP-48: contract introspection + +```java +import org.stellar.sdk.SorobanServer; +import org.stellar.sdk.contract.ContractMeta; +import org.stellar.sdk.contract.ContractSpec; +import org.stellar.sdk.contract.ContractInfo; + +try (SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org:443")) { + ContractMeta meta = server.getContractMeta("C..."); // SEP-46 + ContractSpec spec = server.getContractSpec("C..."); // SEP-48 + ContractInfo info = server.getContractInfo("C..."); // spec + meta +} +``` diff --git a/skills/java-stellar-sdk/references/soroban.md b/skills/java-stellar-sdk/references/soroban.md new file mode 100644 index 000000000..45860475c --- /dev/null +++ b/skills/java-stellar-sdk/references/soroban.md @@ -0,0 +1,194 @@ +# Soroban (RPC + contracts) + +Two layers: the low-level `SorobanServer` RPC client, and the high-level `ContractClient` / +`AssembledTransaction` which wrap the simulate → prepare → sign → submit dance. Both implement +`Closeable` — use try-with-resources. + +**Prefer generated bindings.** For most contract work, generate a typed Java client with +[`stellar-contract-bindings`](https://github.com/lightsail-network/stellar-contract-bindings) +instead of hand-writing `Scv` conversions — see `bindings.md`. The hand-written `ContractClient` +flow below is the fallback. + +## RPC client (`SorobanServer`) + +```java +import org.stellar.sdk.SorobanServer; + +try (SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org:443")) { + server.getHealth(); + server.getNetwork(); + server.getLatestLedger(); + server.getLedgerEntries(keys); // Collection + server.getEvents(request); + server.simulateTransaction(tx); + server.prepareTransaction(tx); // simulate + assemble (footprint, auth, resource fee) + server.sendTransaction(tx); // submit; returns immediately with a hash + status + server.getTransaction(hash); // poll for the result + server.pollTransaction(hash); // poll with retries until final + server.getAccount("G..."); // -> TransactionBuilderAccount + server.getContractData(contractId, key, durability); + server.getContractInfo(contractId); // SEP-48 (interface spec + meta) + server.getContractMeta(contractId); // SEP-46 + server.getContractSpec(contractId); // SEP-48 +} +``` + +### Manual submit loop + +```java +import org.stellar.sdk.*; +import org.stellar.sdk.exception.*; +import org.stellar.sdk.operations.InvokeHostFunctionOperation; +import org.stellar.sdk.responses.sorobanrpc.GetTransactionResponse; +import org.stellar.sdk.responses.sorobanrpc.SendTransactionResponse; +import org.stellar.sdk.scval.Scv; +import org.stellar.sdk.xdr.SCVal; +import java.util.List; + +KeyPair kp = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); +try (SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org:443")) { + TransactionBuilderAccount source = server.getAccount(kp.getAccountId()); + + Transaction tx = + new TransactionBuilder(source, Network.TESTNET) + .setBaseFee(Transaction.MIN_BASE_FEE) + .addOperation( + InvokeHostFunctionOperation.invokeContractFunctionOperationBuilder( + "C...", "hello", List.of(Scv.toSymbol("world"))) + .build()) + .setTimeout(300) + .build(); + + // MUST happen before signing. + Transaction prepared; + try { + prepared = server.prepareTransaction(tx); + } catch (PrepareTransactionException e) { + System.out.println(e.getSimulateTransactionResponse()); + throw e; + } + + prepared.sign(kp); + SendTransactionResponse sent = server.sendTransaction(prepared); + if (sent.getStatus() != SendTransactionResponse.SendTransactionStatus.PENDING) { + throw new RuntimeException("send failed: " + sent); + } + + GetTransactionResponse got; + while (true) { + got = server.getTransaction(sent.getHash()); + if (got.getStatus() != GetTransactionResponse.GetTransactionStatus.NOT_FOUND) break; + Thread.sleep(3000); + } +} +``` + +Read the return value from the transaction meta. Soroban meta lives under `V3` or `V4` +depending on the protocol version, so check both (this is exactly what +`AssembledTransaction.submit()` does internally): + +```java +import org.stellar.sdk.xdr.TransactionMeta; +import org.stellar.sdk.xdr.SCVal; + +TransactionMeta meta = got.parseResultMetaXdr(); +SCVal returnValue = + meta.getV3() != null + ? meta.getV3().getSorobanMeta().getReturnValue() + : meta.getV4().getSorobanMeta().getReturnValue(); +``` + +## High-level client (`ContractClient` / `AssembledTransaction`) + +```java +import org.stellar.sdk.contract.ContractClient; + +ContractClient client = + new ContractClient("C...", "https://soroban-testnet.stellar.org:443", Network.TESTNET); +``` + +`invoke(functionName, parameters, source, signer, parseResultXdrFn, baseFee)` returns an +`AssembledTransaction` (already simulated by default). `parseResultXdrFn` is a +`Function`; pass `null` to get the raw `SCVal`. + +### Read-only call + +No signer needed, but you must still pass a `source` account id that already exists on +the target network — `simulate()` loads its sequence via `server.getAccount()`, which +throws `AccountNotFoundException` if the account does not exist. Read the simulated result: + +```java +import java.util.List; +import org.stellar.sdk.scval.Scv; +import org.stellar.sdk.xdr.SCVal; + +String source = "GD2JXEFGEO53CNQ22KN2ICOQ2LOASCABQHAIOMLZV265C246PFKKHPYU"; +try (ContractClient client = + new ContractClient("C...", "https://soroban-testnet.stellar.org:443", Network.TESTNET)) { + SCVal value = + client + .invoke("hello", List.of(Scv.toSymbol("world")), source, null, null, + (int) Transaction.MIN_BASE_FEE) + .result(); +} +``` + +### State-changing call + +Pass `source` and `signer`, then submit. Use a `parseResultXdrFn` to decode the result: + +```java +import java.util.Collections; +import java.util.function.Function; +import org.stellar.sdk.contract.AssembledTransaction; + +KeyPair kp = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); +Function parse = scVal -> Scv.fromUint32(scVal); + +try (ContractClient client = + new ContractClient("C...", "https://soroban-testnet.stellar.org:443", Network.TESTNET)) { + AssembledTransaction assembled = + client.invoke("increment", Collections.emptyList(), kp.getAccountId(), kp, parse, + (int) Transaction.MIN_BASE_FEE); + Long result = assembled.signAndSubmit(kp, false); +} +``` + +### `AssembledTransaction` lifecycle + +1. construct (via `client.invoke(...)`) — simulated by default +2. `simulate(restore)` — re-run if you changed the transaction +3. `signAuthEntries(kp)` — for each non-invoker that must authorize +4. `sign(signer, force)` / `signAndSubmit(signer, force)` +5. `result()` (read) or `submit()` then parse + +Helpers: `isReadCall()`, `needsNonInvokerSigningBy(includeAlreadySigned)`, +`restoreFootprint()`, `toEnvelopeXdrBase64()`. Pass `force = true` to `signAndSubmit` to submit +a call that simulated as read-only. + +### Multi-party authorization + +```java +AssembledTransaction assembled = + client.invoke("swap", args, submitter.getAccountId(), submitter, null, (int) Transaction.MIN_BASE_FEE); +assembled.signAuthEntries(alice); +assembled.signAuthEntries(bob); +assembled.signAndSubmit(submitter, false); +``` + +## Uploading / creating contracts + +`ContractClient` only invokes functions. To upload Wasm or create a contract, build an +`InvokeHostFunctionOperation` with `SorobanServer` directly (see `operations.md`): +`uploadContractWasmOperationBuilder(wasmBytes)`, `createContractOperationBuilder(...)`, +`createStellarAssetContractOperationBuilder(asset)`. Prepare, sign, send, then read the Wasm ID +/ contract ID from the transaction meta. + +## Archived state + +If simulation reports archived (expired) ledger entries, restore them. `ContractClient.invoke` +uses `restore = true` by default, which restores automatically when a `signer` is present. +For a no-signer/read-only call it cannot auto-restore: retry the invoke with a `signer`, or +build a `RestoreFootprintOperation` transaction yourself. (`assembled.restoreFootprint()` +requires the signer supplied at construction and throws if it is null.) Extend TTL ahead of +expiry with `ExtendFootprintTTLOperation`. diff --git a/skills/java-stellar-sdk/references/transactions.md b/skills/java-stellar-sdk/references/transactions.md new file mode 100644 index 000000000..60efec70d --- /dev/null +++ b/skills/java-stellar-sdk/references/transactions.md @@ -0,0 +1,138 @@ +# Transactions + +## Lifecycle + +```java +import org.stellar.sdk.*; +import org.stellar.sdk.operations.PaymentOperation; +import org.stellar.sdk.responses.TransactionResponse; +import java.math.BigDecimal; + +Server server = new Server("https://horizon-testnet.stellar.org"); +KeyPair kp = KeyPair.fromSecretSeed(System.getenv("STELLAR_SECRET_KEY")); + +// 1. Load the source account — this fetches its current sequence number. +TransactionBuilderAccount source = server.loadAccount(kp.getAccountId()); + +// 2. Build. Each addOperation adds one operation; set a timeout. +Transaction tx = + new TransactionBuilder(source, Network.TESTNET) + .setBaseFee(Transaction.MIN_BASE_FEE) + .addOperation( + PaymentOperation.builder() + .destination("GD2J...HPYU") + .asset(Asset.createNativeAsset()) + .amount(new BigDecimal("10")) + .build()) + .setTimeout(180) + .build(); // <-- increments source sequence + +// 3. Sign with every required key. +tx.sign(kp); + +// 4. Submit. +TransactionResponse response = server.submitTransaction(tx); +``` + +`setBaseFee` is the fee **per operation**, in stroops (1 XLM = 10,000,000 stroops). +`Transaction.MIN_BASE_FEE` is 100 stroops. The total fee is `baseFee * number_of_operations`. +Soroban transactions add a separate resource fee computed during simulation/preparation (see +`soroban.md`). + +### Sequence-number pitfall + +`build()` increments the in-memory sequence number of the `source` account object. If you +build a transaction but do not submit it (or submission fails), do **not** reuse the same +account object to build another transaction — reload it: + +```java +source = server.loadAccount(kp.getAccountId()); // reload before re-building +``` + +Reusing a stale sequence causes a `tx_bad_seq` error on submission. + +## Memos + +```java +import org.stellar.sdk.Memo; + +builder.addMemo(Memo.text("note")); // <= 28 bytes UTF-8 +builder.addMemo(Memo.id(123456789L)); // unsigned 64-bit int +// 32-byte hash as a 64-char hex string (or pass a byte[32]): +builder.addMemo(Memo.hash("0000000000000000000000000000000000000000000000000000000000000000")); +builder.addMemo(Memo.returnHash("0000000000000000000000000000000000000000000000000000000000000000")); +``` + +Only one memo per transaction. Some exchanges require a memo — see SEP-29 in `sep.md`. + +## Preconditions + +`setTimeout` is the convenience path. For finer control build a `TransactionPreconditions` +and pass it with `addPreconditions(...)`: + +```java +import org.stellar.sdk.TimeBounds; +import org.stellar.sdk.LedgerBounds; +import org.stellar.sdk.TransactionPreconditions; + +TransactionPreconditions preconditions = + TransactionPreconditions.builder() + .timeBounds(new TimeBounds(0, 1700000000L)) // unix seconds; 0 = no bound + .ledgerBounds(new LedgerBounds(0, 0)) + .minSeqAge(java.math.BigInteger.ZERO) + .minSeqLedgerGap(0L) + .build(); +builder.addPreconditions(preconditions); +``` + +`setTimeout` and an explicit `timeBounds` are alternative ways to bound validity time; do not +set both on the same builder. + +## XDR round-trips + +```java +import org.stellar.sdk.Transaction; +import org.stellar.sdk.Network; + +String xdr = tx.toEnvelopeXdrBase64(); // base64 string, safe to store/transmit +Transaction restored = (Transaction) Transaction.fromEnvelopeXdr(xdr, Network.TESTNET); +restored.sign(kp); // e.g. a second signer adds their signature +``` + +## Fee-bump transaction + +Wrap an existing (inner) transaction to pay a higher fee on someone else's behalf: + +```java +import org.stellar.sdk.FeeBumpTransaction; + +FeeBumpTransaction feeBump = + FeeBumpTransaction.createWithBaseFee( + feePayer.getAccountId(), // fee source + 200, // base fee per op, in stroops + innerSignedTx); // the signed inner Transaction +feeBump.sign(feePayer); +server.submitTransaction(feeBump); +``` + +## Multisig + +Add multiple signers to one transaction; collect signatures by passing XDR between parties: + +```java +tx.sign(signerA); +String xdr = tx.toEnvelopeXdrBase64(); +// ... send xdr to the second holder ... +Transaction tx2 = (Transaction) Transaction.fromEnvelopeXdr(xdr, Network.TESTNET); +tx2.sign(signerB); +server.submitTransaction(tx2); +``` + +Configure thresholds and signer weights with `SetOptionsOperation`; see `operations.md`. + +## Don't + +- Don't use `double`/`float` for amounts — use `new BigDecimal("10.5")`. +- Don't omit a timeout / time bound. +- Don't mix `Network.TESTNET` and `Network.PUBLIC`. +- Don't reuse a stale source account after `build()`. diff --git a/skills/java-stellar-sdk/references/troubleshooting.md b/skills/java-stellar-sdk/references/troubleshooting.md new file mode 100644 index 000000000..184094db4 --- /dev/null +++ b/skills/java-stellar-sdk/references/troubleshooting.md @@ -0,0 +1,83 @@ +# Troubleshooting + +## Exception hierarchy + +All SDK exceptions derive from `org.stellar.sdk.exception.SdkException` (a +`RuntimeException`). Network/RPC exceptions live in `org.stellar.sdk.exception`; the +high-level contract-client exceptions thrown by `ContractClient`/`AssembledTransaction` +live in `org.stellar.sdk.contract.exception` (and also extend `SdkException`). + +Network / Horizon / RPC (subclasses of `NetworkException`): +- `ConnectionErrorException` — could not reach the server. +- `BadRequestException` — 4xx (except 429); e.g. account/transaction not found (404) or a + rejected transaction (400). Inspect the problem details for `result_codes`. +- `BadResponseException` — 5xx / unexpected response. +- `TooManyRequestsException` — 429 rate limited. +- `RequestTimeoutException` — the request timed out. + +Soroban / contract (RPC-level, in `org.stellar.sdk.exception`): +- `SorobanRpcException` — the RPC returned an error. +- `PrepareTransactionException` — simulation/preparation failed; inspect + `e.getSimulateTransactionResponse()`. (Extends `SdkException` directly, not + `NetworkException`.) +- `AccountNotFoundException` — account does not exist on the network. + +Contract client lifecycle (in `org.stellar.sdk.contract.exception`, all extend +`AssembledTransactionException` → `SdkException`): +- `SimulationFailedException` — the simulation step failed. +- `ExpiredStateException` — ledger entries are archived/expired and need restoring. +- `NotYetSimulatedException`, `RestorationFailureException`, + `SendTransactionFailedException`, `TransactionStillPendingException`, + `TransactionFailedException`, `NeedsMoreSignaturesException`, `NoSignatureNeededException`. + +Other: +- `AccountRequiresMemoException` — destination requires a memo (SEP-29). +- `InvalidSep10ChallengeException`, `InvalidSep45ChallengeException` — challenge validation. +- `UnexpectedException`, `UnknownResponseException`. + +```java +import org.stellar.sdk.exception.*; + +try { + server.submitTransaction(tx); +} catch (BadRequestException e) { + System.out.println(e.getMessage()); // includes result codes for rejected submissions +} catch (NetworkException e) { + System.out.println(e.getMessage()); +} +``` + +## Reading submission failures + +A failed Horizon submission throws `BadRequestException`; the useful detail is the +transaction/operation result codes in the error's problem extras. Common transaction-level +codes: + +| Code | Meaning | Fix | +| --- | --- | --- | +| `tx_bad_seq` | stale sequence number | reload the source account before building (see `transactions.md`) | +| `tx_bad_auth` | missing/insufficient signatures | sign with all required keys; check thresholds | +| `tx_insufficient_fee` | fee too low | raise `setBaseFee` | +| `tx_too_late` / `tx_too_early` | outside time bounds | rebuild with a valid timeout | +| `tx_failed` | an operation failed | inspect per-operation codes | + +Operation-level codes you'll hit often: +- `op_no_trust` / `op_no_issuer` — recipient lacks a trustline for the asset (add a + `ChangeTrustOperation`). +- `op_underfunded` — source lacks the balance. +- `op_no_destination` — destination account does not exist (create it first). +- `op_low_reserve` — would drop below the minimum XLM reserve. + +## Soroban issues + +- **Simulation failed** → `PrepareTransactionException`; read + `e.getSimulateTransactionResponse().getError()` for the diagnostic. +- **State expired/archived** → restore it: `ContractClient.invoke` restores automatically when + a `signer` is present. For a no-signer/read-only call it cannot auto-restore — retry the + invoke with a `signer`, or build a `RestoreFootprintOperation` transaction yourself. + (`assembled.restoreFootprint()` requires the signer supplied at construction and throws if it + is null.) See `soroban.md`. +- **Authorization missing** → a non-invoker must sign auth entries; use + `assembled.signAuthEntries(theirKeyPair)` before `signAndSubmit(...)`. +- **Transaction still pending** → `getTransaction` returns status `NOT_FOUND` until it is in a + ledger; poll with `pollTransaction` or a loop. See `soroban.md`. diff --git a/skills/java-stellar-sdk/references/xdr_scval.md b/skills/java-stellar-sdk/references/xdr_scval.md new file mode 100644 index 000000000..3d81b6c8b --- /dev/null +++ b/skills/java-stellar-sdk/references/xdr_scval.md @@ -0,0 +1,79 @@ +# XDR, SCVal & Addresses + +## SCVal conversion (`org.stellar.sdk.scval.Scv`) + +Soroban contract arguments and return values are `SCVal` XDR objects. Build them with +`Scv.to*` and read them with `Scv.from*`. + +```java +import org.stellar.sdk.scval.Scv; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +// To SCVal (for contract arguments) +Scv.toBoolean(true); +Scv.toVoid(); +Scv.toUint32(7L); Scv.toInt32(-7); +Scv.toUint64(BigInteger.valueOf(7)); Scv.toInt64(-7L); +Scv.toUint128(BigInteger.valueOf(7)); Scv.toInt128(BigInteger.valueOf(-7)); +Scv.toUint256(BigInteger.valueOf(7)); Scv.toInt256(BigInteger.valueOf(-7)); +Scv.toTimePoint(BigInteger.valueOf(1700000000)); Scv.toDuration(BigInteger.valueOf(3600)); +Scv.toBytes(new byte[] {1, 2}); +Scv.toString("hello"); Scv.toSymbol("increment"); +Scv.toAddress("G..."); // account or contract ("C...") address +Scv.toVec(List.of(Scv.toUint32(1L), Scv.toUint32(2L))); +Scv.toMap(Map.of(Scv.toSymbol("k"), Scv.toUint32(1L))); +``` + +```java +// From SCVal (parsing results) +Scv.fromUint32(v); // -> long +Scv.fromInt128(v); // -> BigInteger +Scv.fromString(v); // -> byte[]; new String(bytes, StandardCharsets.UTF_8) for text +Scv.fromSymbol(v); // -> String +Scv.fromAddress(v); // -> Address +Scv.fromVec(v); // -> Collection +Scv.fromMap(v); // -> LinkedHashMap +``` + +`Scv.toMap` sorts entries by key per Soroban's runtime ordering rules (keys must ascend). + +## Addresses + +```java +import org.stellar.sdk.Address; +import org.stellar.sdk.xdr.SCVal; + +Address addr = new Address("G..."); // account or "C..." contract +SCVal scVal = addr.toSCVal(); // SCVal for contract args +Address back = Address.fromSCVal(scVal); +``` + +## Transaction XDR + +```java +import org.stellar.sdk.Transaction; +import org.stellar.sdk.Network; + +String xdr = tx.toEnvelopeXdrBase64(); +Transaction te = (Transaction) Transaction.fromEnvelopeXdr(xdr, Network.PUBLIC); +``` + +## Lower-level XDR types + +Generated XDR types live in `org.stellar.sdk.xdr` (e.g. `org.stellar.sdk.xdr.TransactionMeta`, +`org.stellar.sdk.xdr.SCVal`). Most carry `fromXdrBase64(...)` / `toXdrBase64()` for base64 and +`fromXdrByteArray(...)` / `toXdrByteArray()` for raw bytes. Use these to debug result/meta XDR, +e.g. decoding a `resultMetaXdr` returned by Soroban RPC: + +```java +import org.stellar.sdk.xdr.TransactionMeta; + +TransactionMeta meta = TransactionMeta.fromXdrBase64(resultMetaXdrBase64); +``` + +## SEP-51 JSON + +XDR base classes support JSON encoding/decoding (SEP-51) in addition to base64/bytes, useful +for inspecting structures in a readable form.