Skip to content

docs(testnet): tooling directory index, sweep-signer usage notes, and two tooling specs - #753

Merged
phertyameen merged 1 commit into
bridgelet-org:mainfrom
Ogunmepon:docs/testnet-tooling-index-and-specs
Sep 27, 2026
Merged

phertyameen merged 1 commit into
bridgelet-org:mainfrom
Ogunmepon:docs/testnet-tooling-index-and-specs

Conversation

@Ogunmepon

@Ogunmepon Ogunmepon commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds testnet/tooling/README.md — the index the directory was missing — plus three documents alongside the two specs already there. This closes two live dangling references: decision-log.md already calls testnet/tooling/README.md "the index for this directory", and nonce-inspector-spec.md already refers to "the contract-ID lookup tool spec" as though it existed.

File Issue Lines Type
testnet/tooling/README.md #643 165 index
testnet/tooling/sweep-signer-integration-notes.md #644 480 usage notes
testnet/tooling/contract-id-lookup-spec.md #645 385 spec only
testnet/tooling/event-tail-spec.md #646 526 spec only

decision-log.md, faucet-helper-spec.md and nonce-inspector-spec.md are unmodified.

README.md — stopping the directory being misunderstood

The directory contains no tools, which looks like a bug and invites three reasonable-but-wrong reactions ("add the scripts", "move scripts/ in here", "it's complete"). A prominent status line catches the reader who lands here first and never opens the decision log; the three reactions are tabled with a pointer for the reasoning rather than re-argued.

The distinction is drawn once and cross-linked: scripts/ holds the repository's own build and CI entrypoints (build.sh, deploy-testnet.sh, test.sh), which run in the repo and are versioned in lockstep with the code they build. testnet/tooling/ describes tools that consume the already-deployed contracts from outside it.

It also makes explicit that tools/sweep-signer is a real, working CLI in a separate top-level directory — not part of this one — and carries forward the sandbox caveat from decision-log.md: several specs assume no local Soroban sandbox exists, and adding one would make them substantially less useful, so they should be re-evaluated before being built.

Index entries follow security/README.md's Document | Scope | Read it when shape, with each trigger written as a condition ("Your sweep signature is being rejected with no error code") rather than a category.

sweep-signer-integration-notes.md — the gap .env.example leaves

Usage notes only; the tool is not modified. .env.example shows only the pubkey subcommand in a commented example line, so an integrator who actually needs to sign has had no usage note at all.

Documents both subcommands, the sign flags — --contract-id, --destination, --nonce, and exactly one of --signer-seed-hex or --signer-secret (a clap group that is required = true, multiple = false, so both-or-neither is an error) — the env-var form of each signer option, the 32-byte seed requirement, and the exact three-part output including the nonce warning. The env-var form is recommended over flags because it keeps keys out of shell history and process listings.

Two findings:

  • The tool's own "Provide either --signer-seed-hex or --signer-secret" message is unreachable dead code, because clap's required group rejects first. Documented as such rather than presented as guidance someone should follow.
  • Its soroban-sdk = "22.0.0" pin is a correctness requirement, not hygiene. It exists so Address::to_xdr() serialises identically to what the deployed contract expects. A divergence produces a signature that does not verify, with no diagnostic — which is why docs/SIGNATURE_FORMAT.md warns about hand-rolled XDR encoders at length.

This document deliberately does not open with the spec-only line, because the tool it documents is implemented. Describing 0.1.0's actual behaviour is the point.

contract-id-lookup-spec.md — spec only

The central design constraint is that there is no single trustworthy source of contract IDs today: contract-status.md says all four are undeployed with IDs "TBD"; last-verified.json carries timestamps but is a staleness mechanism, not evidence of liveness; and deployments/testnet.json does contain concrete IDs the registry does not reflect.

A lookup tool that reads a stale file and returns a confident answer is worse than no tool at all.

So the output is provenance-first — source file, field, file age, verified state, and the registry's contradicting statement — and refusing to answer is a first-class outcome, not an error condition. Six exit codes split resolved-verified, resolved-unverified/stale, unknown name, conflicting sources, unreadable data, and probe unreachable, following nonce-inspector-spec.md's reasoning that CI must distinguish "retry" from "network down". Caching is a named non-goal, because a cached ID is the failure being fixed.

event-tail-spec.md — spec only

Grounded in testnet/registry/event-topics.md, and built around the design's hardest problem: a wrong topic symbol silently matches nothing, which for a tailer is indistinguishable from "nothing is happening". The tool must therefore distinguish "no matching events" from "wrong filter" from "not actually connected" — and say which one it is.

Cursor handling is specified in detail: ledger sequence is the only safe cursor on Stellar and why that rules out timestamps, duplicate suppression across a reconnect, and behaviour when the start ledger predates RPC retention. It is positioned as a better ergonomics layer over primitives the tree already documents, not a different mechanism.

Read-only and provably so, per the shared-deployment reasoning in security/README.md: someone else's activity is a real condition, and a tailer that could mutate the shared deployment would itself be a new abuse surface.

The opening states the sharpest honesty problem for this tool specifically — it exists to watch a live deployment, and nothing is deployed, so it cannot currently be exercised.

Repository defects found, documented rather than papered over

  1. testnet/registry/event-topics.md is wrong about ReserveContract event symbols. It documents initialized and base_reserve_updated; contracts/reserve_contract/src/events.rs publishes init and reserve. testnet/monitoring/event-watchlist.md already noticed this ("Some existing registry prose predates those symbols") but the registry was never corrected. The spec uses the source as authority and flags the divergence inline.
  2. event-topics.md has the AccountExpired field wrong — it says total_amount; the source field is amount_returned. Again, event-watchlist.md documents this correctly.
  3. Two files each declare themselves the source of truth for contract IDs. testnet/registry/README.md calls contract-status.md canonical; testnet/config/README.md calls deployments/testnet.json + contract-ids.txt the source of truth. This single undecided question would collapse most of the lookup spec's difficulty, so it is left as the first open question rather than picked.
  4. Error::SignatureVerificationFailed = 9 is unreachable. verify_sweep_auth calls env.crypto().ed25519_verify(...) then returns Ok(()) unconditionally — a bad signature traps the host function rather than returning the error. InvalidNonce = 11 appears dead too. The enum documents an interface the code does not implement, and testnet/runbooks/failed-sweep-signature.md describes Create API Reference Documentation #9 as the primary symptom an integrator will never see. Flagged in the troubleshooting table.
  5. tools/sweep-signer/testnet/config/staleness.md is a stray issue body committed inside tools/ — a GitHub issue title and body, not documentation, describing a testnet/config/staleness-check.md that does not exist.
  6. sweep_controller has no events.rs. Its three event helpers (sweep, dest_auth, dest_upd) are inline in lib.rs, unlike the other two contracts.
  7. testnet/runbooks/failed-sweep-signature.md and testnet/config/nonce-tracking.md both reference tools/sweep-signer/config.toml, which does not exist. The tool takes flags and env vars only, so anything following those two diagnostics verbatim fails at that step.

Verification

  • git status shows exactly these four files; git diff is empty — the three pre-existing testnet/tooling/ files are untouched.
  • scripts/ and tools/ untouched.
  • Every flag, subcommand name, env var, output line, event symbol and file path was re-verified against source, not recalled.
  • All relative links ls-verified.
  • No secret material. Greps for S[A-Z2-7]{40,}, 64-char hex, and 32+-char hex all return clean — relevant because this batch documents a key-accepting tool. The .env.example example seed is referenced, labelled a documentation artefact, and explicitly marked not-for-use. The only C... string is the public XLM SAC address.

Closes #643
Closes #644
Closes #645
Closes #646

… two tooling specs

Adds testnet/tooling/README.md as the index the directory was missing, plus
three documents alongside the two specs already there. testnet/tooling/
decision-log.md already referred to "testnet/tooling/README.md" as the index
for this directory and nonce-inspector-spec.md already referred to "the
contract-ID lookup tool spec" as if it existed, so this closes two live
dangling references.

testnet/tooling/README.md

The index, whose stated job is to stop a future contributor mistaking this
directory for where deploy scripts belong. The distinction is drawn once and
cross-linked rather than re-argued: scripts/ holds the repository's own build
and CI entrypoints, which run in the repo and are versioned in lockstep with
the code they build, while testnet/tooling/ describes tools that consume the
already-deployed contracts from outside it.

The directory contains no tools, which looks like a bug and invites three
reasonable-but-wrong reactions. A prominent status line handles the reader who
lands here first and does not read the decision log, and the three reactions
are tabled with a pointer to decision-log.md for the reasoning.

Also carries forward the sandbox caveat from decision-log.md: several of
these specs assume there is no local Soroban sandbox, and adding one would
make them substantially less useful, so they should be re-evaluated against a
sandbox before being built. It also makes explicit that tools/sweep-signer is
a real working CLI in a separate top-level directory, not part of this one.

Index entries follow security/README.md's Document | Scope | Read it when
shape, with each trigger written as a condition - "Your sweep signature is
being rejected with no error code" - rather than a category.

testnet/tooling/sweep-signer-integration-notes.md

Usage notes only; the tool is not modified. It closes the gap the issue names:
.env.example shows only the pubkey subcommand in a commented example line, so
an integrator who needs to actually sign has no usage note for sign.

Documents both subcommands, the sign flags (--contract-id, --destination,
--nonce, and exactly one of --signer-seed-hex or --signer-secret, a group that
is required = true and multiple = false, so providing both or neither is an
error), the env-var form of each signer option, the 32-byte seed requirement,
and the exact three-part output including the nonce warning. The
environment-variable form is recommended over flags because it keeps keys out
of shell history and process listings.

Two findings worth surfacing:

- The tool's own "Provide either --signer-seed-hex or --signer-secret"
  message is unreachable dead code, because clap's required group rejects
  first. Documented as such rather than presented as guidance.
- Its soroban-sdk = "22.0.0" pin is a correctness requirement, not hygiene.
  The pin exists so Address::to_xdr() serialises identically to what the
  deployed contract expects. A divergence between the signer's XDR encoding
  and the contract's produces a signature that does not verify, with no
  diagnostic - which is why docs/SIGNATURE_FORMAT.md warns about hand-rolled
  XDR encoders at length.

This document deliberately does not open with the spec-only line, because the
tool it documents is implemented. Describing tools/sweep-signer 0.1.0's actual
behaviour is the point.

testnet/tooling/contract-id-lookup-spec.md

Design only. Opens with the house spec-only line and links decision-log.md.

The central design constraint is that there is no single trustworthy source of
contract IDs today: contract-status.md says all four are undeployed with IDs
"TBD", last-verified.json carries timestamps but is a staleness mechanism
rather than evidence of liveness, and deployments/testnet.json does contain
concrete IDs the registry does not reflect. A lookup tool that reads a stale
file and returns a confident answer is worse than no tool at all, so the
output is provenance-first - source file, field, file age, verified state, and
the registry's contradicting statement - and refusing to answer is a
first-class outcome rather than an error condition.

Six exit codes split resolved-verified, resolved-unverified/stale, unknown
name, conflicting sources, unreadable data, and probe unreachable, following
nonce-inspector-spec.md's reasoning that CI must be able to tell "retry" from
"network down". Caching is a named non-goal, because a cached ID is the
failure being fixed.

testnet/tooling/event-tail-spec.md

Design only. Same spec-only opening.

Grounded in testnet/registry/event-topics.md, with a check on the design's
hardest problem: a wrong topic symbol silently matches nothing, which for a
tailer is indistinguishable from "nothing is happening". The tool must
therefore distinguish "no matching events" from "wrong filter" from "not
actually connected", and say which one it is.

Cursor handling is specified in detail - ledger sequence is the only safe
cursor on Stellar, why that rules out timestamps, duplicate suppression across
a reconnect, and behaviour when the start ledger predates RPC retention. It
is a better ergonomics layer over the primitives the tree already documents
rather than a different mechanism.

Read-only and provably so, per the shared-deployment reasoning in
security/README.md: someone else's activity is a real condition, and a tailer
that could mutate the shared deployment would itself be a new abuse surface.

The opening states the sharpest honesty problem for this tool specifically: it
exists to watch a live deployment, and nothing is deployed, so it cannot
currently be exercised.

Closes bridgelet-org#643
Closes bridgelet-org#644
Closes bridgelet-org#645
Closes bridgelet-org#646
@drips-wave

drips-wave Bot commented Sep 27, 2026

Copy link
Copy Markdown

@Ogunmepon Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@phertyameen
phertyameen merged commit f58f2c5 into bridgelet-org:main Sep 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment