Skip to content

Update Verify Trustlines guide to use RPC instead of Horizon - #2760

Open
wmendes wants to merge 2 commits into
mainfrom
update-verify-trustlines-rpc
Open

Update Verify Trustlines guide to use RPC instead of Horizon#2760
wmendes wants to merge 2 commits into
mainfrom
update-verify-trustlines-rpc

Conversation

@wmendes

@wmendes wmendes commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1743 (child of #1529).

Removes the Horizon section and rebuilds the RPC section around the modern JS SDK helpers.

What changed

Bugs fixed along the way

The RPC section already existed, but it wasn't in shape to be promoted as the primary path:

  • Private API. It called rpc._getLedgerEntries(key) and therefore hand-decoded xdr.LedgerEntryData.fromXDR(entry.xdr, "base64"). The public getLedgerEntries returns entries[].val already decoded.
  • Flags bitmask bug. if (trustlineData.flags() !== 1) is wrong — trustline flags are a bitfield. An authorized and clawback-enabled trustline has flags() === 5 and was falsely reported "not authorized". Now !(flags() & 1), which is what the SDK itself does.
  • Stale receiver. GCLNZP3W… returns 404 on Testnet — wiped by a reset, so the snippet threw at step 1 for anyone copy-pasting. Replaced with a "G..." placeholder plus a comment on creating one, so it can't go stale again.
  • SAC section: authorized doesn't return false for a missing trustline. The simulation fails with Error(Contract, #13) (trustline entry is missing for account) instead, so the prose now says which branch handles which case.
  • SAC bindings package name. The command wrote to --output-dir=./bindings (package name bindings) while the snippet did import { Client } from "sac" — that import cannot resolve. Command now points at ./sac.
  • Removed a commented-out xlmClient.transfer({...}) block referencing undefined variables, and fixed "conver" / "addresss" / "cient" / "Now we defined".

Testing

Every snippet was run against Testnet with @stellar/stellar-sdk@16.2.0:

Case Result
getAssetBalance, trustline present authorized: true, balance 1252.7872975
getAssetBalance, no trustline Throws Trustline for USDC:… not found — hence try/catch, not if (!balanceEntry)
getLedgerEntriesentries[0].val.trustLine() flags() === 1, limit and balance read correctly
getLedgerEntries, no trustline entries: [], no throw
Insufficient limit Created an account with changeTrust limit 0.5; the limit - balance < sendingAmount branch fires
SAC authorized, trustline present Returns true
SAC authorized, no trustline Simulation fails with Error(Contract, #13)
Bindings command Regenerated; package name resolves as sac and builds

pnpm build and pnpm check:mdx both pass.

🤖 Generated with Claude Code

Removes the Horizon section and rebuilds the RPC section around the
modern JS SDK helpers.

- Delete "Checking a Trustline through the Horizon API" entirely, matching
  the sibling migrations (#1769, #1779, #1784, #1786, #1793).
- Split the RPC section in two: `getAssetBalance` for existence and
  authorization, `getLedgerEntries` for the trustline limit (which
  `getAssetBalance` does not surface).
- Use the public `getLedgerEntries` instead of the private
  `_getLedgerEntries`, which also drops the manual
  `xdr.LedgerEntryData.fromXDR` step since `entries[].val` is already
  decoded.
- Fix a correctness bug: trustline flags are a bitfield, so
  `flags() !== 1` wrongly reported an authorized, clawback-enabled
  trustline (flags = 5) as unauthorized. Now checks `flags() & 1`.
- Replace the hardcoded receiver, which 404s on Testnet after a reset,
  with a placeholder plus guidance on creating one.
- SAC section: `authorized` does not return false for a missing
  trustline — the simulation fails with Error(Contract, #13) — so
  document which branch handles which case. Also point the bindings
  command at ./sac so the generated package name matches the
  `import { Client } from "sac"` below it, and drop a leftover
  commented-out transfer block.
- Link to the official Migrate from Horizon to RPC page, which documents
  that RPC does not return trustlines alongside accounts.

All snippets were run against Testnet with @stellar/stellar-sdk 16.2.0,
covering the missing-trustline, authorized, and insufficient-limit paths.

Fixes #1743

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings August 18, 2026 14:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates the “Verify Trustlines” guide to use Stellar RPC-first patterns (including getAssetBalance and getLedgerEntries) and clarifies how to check existence, authorization, and limits for trustlines and SAC authorization.

Changes:

  • Replaces older trustline-check examples with RPC-focused approaches (getAssetBalance, getLedgerEntries) and adds guidance on when to use each.
  • Adds a dedicated section on checking trustline limits and improves the trustline-flags authorization logic.
  • Clarifies SAC authorized behavior, especially the “missing trustline” simulation failure case.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/build/guides/basics/verify-trustlines.mdx Outdated
Comment thread docs/build/guides/basics/verify-trustlines.mdx Outdated
Comment thread docs/build/guides/basics/verify-trustlines.mdx Outdated
Comment thread docs/build/guides/basics/verify-trustlines.mdx Outdated
Comment thread docs/build/guides/basics/verify-trustlines.mdx
@stellar-jenkins-ci

Copy link
Copy Markdown

- Keep trustline limit arithmetic in BigInt stroops. Converting int64
  amounts through Number loses precision above 2^53-1, and trustline
  limits routinely sit at the int64 maximum (922,337,203,685.4775807).
  The old Number path falsely rejected valid payments: with a limit at
  the maximum and 1000 stroops of headroom, sending 0.0001 was reported
  as exceeding the limit. Adds a `toStroops` helper so the amount to send
  is converted without going through a float, and links the Amount
  precision reference.

- Drop the same Number conversion from the getAssetBalance snippet and
  report `amount` as the stroops string the SDK returns.

- Stop reporting every getAssetBalance failure as "trustline not found".
  The SDK's getTrustline swallows the underlying error and always throws
  the same "not found" message, so an unreachable RPC endpoint is
  indistinguishable from a genuinely missing trustline. The example now
  rethrows the original error and says "could not confirm", and a warning
  documents the caveat and points at getLedgerEntries, which returns an
  empty entries array for a missing trustline and only rejects on a
  transport error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wmendes

wmendes commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Worked through the Copilot review. Two comments were correct and are now fixed, one was correct in spirit but not fixable as suggested, one was wrong, and one was a false premise. Everything was checked against Testnet rather than reasoned about.

Fixed — int64 precision (limit / balance lines). Real bug. Trustline limits routinely sit at the int64 maximum (922,337,203,685.4775807), roughly 1024x above 2^53 - 1, so Number() rounding was ~2048 stroops. With the limit at maximum and 1000 stroops of headroom, sending 0.0001 was reported as exceeding the limit — a false rejection of a valid payment. The snippet now stays in BigInt stroops end to end, with a string-wise toStroops helper (Math.round(parseFloat(x) * 1e7) would reintroduce the same error). Links Amount precision.

Fixed differently — error conflation on getAssetBalance. The concern is real, but inspecting the error shape can't work: the SDK's getTrustline has a bare catch that discards the original error and always throws the same "not found" message. Against a dead endpoint, getAssetBalance reports "trustline not found" while getLedgerEntries correctly raises an AxiosError. So the example now rethrows the original error, says "could not confirm a trustline", and a warning documents the caveat and points at getLedgerEntries for callers that must distinguish. I'll raise the swallowed error upstream on js-stellar-sdk.

Not a bug — amount units. balanceEntry.amount is stroops, built from tl.balance().toString(). Same trustline: Horizon reports 1252.7872975, getAssetBalance returns "12527872975". The / 10 ** 7 was right. That said, the comment pointed at a real adjacent problem — Number(amount) is unsafe for large int64 balances — so I removed that arithmetic and report the stroops string directly.

Not applicable — "docs/build/ is a generated artifact". It's the Build section of the docs site, hand-authored and git-tracked. Docusaurus output goes to build/ at the repo root, and .gitignore has no docs/ entry.

Re-verified after the changes: toStroops round-trips the int64 maximum exactly; all four previously-misjudged precision cases now resolve correctly; the trustline-present, missing-trustline, and insufficient-limit paths all behave as documented against Testnet (including a 0.4-under-a-0.5-limit case that must pass). pnpm build and pnpm check:mdx pass.

@stellar-jenkins-ci

Copy link
Copy Markdown

@kaankacar
kaankacar self-requested a review August 18, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update Verify Trustlines Guide to use RPC instead of Horizon

2 participants