Skip to content

docs: add recovery and protocol exit guides, an error reference and reader routing - #600

Merged
salazarsebas merged 3 commits into
mainfrom
docs/recovery-protocol-exit-routing
Oct 10, 2026
Merged

salazarsebas merged 3 commits into
mainfrom
docs/recovery-protocol-exit-routing

Conversation

@salazarsebas

Copy link
Copy Markdown
Member

What

Three documentation changes, one commit per issue.

  • Recovery guide and error reference ([docs] Publish a recovery guide and an error code reference #517). guides/recovery (by account state: nothing done, partly closed, merged, funds sent to an exchange without credit) and api-reference/errors (all 106 registered codes grouped by part of a close, with status, retry guidance, meaning and what to show the user). troubleshooting-and-faq gains a "What state is my account in" table. introduction links the new reference and fixes its 404 row. Both pages are appended to the navigation and nothing moves.
  • Add-a-protocol-exit guide ([docs] Publish the add-a-protocol-exit contributor guide #519). New "Contributing" navigation group with contributing/add-a-protocol-exit, linked from CONTRIBUTING.md, whose scope list now includes api and web and says xbull is not a scope.
  • Home cards and reader routing ([docs] Add task-based home page cards and a reader-routing table in the README #520). Four task cards on docs/index.mdx, a "Where to read next" table at the top of the README, and a Documentation table that now lists every top-level docs page, the guide sets, the SDK, the API reference and SECURITY.md. No README section or page path is removed or renamed.

Why

A failure halfway through an irreversible close is the worst moment to have only FAQ entries. Integrators need retry and display guidance per code, contributors need the two-place rule with real file paths, and a first-time visitor needs a route by audience.

Drift prevention (#517)

scripts/check-error-reference.mjs fails when a code in apps/api/src/common/error-codes.ts has no row in errors.mdx, when a row names an unknown or duplicated code, when a Retry value is outside Yes | After fix | Check first | No, or when a row's status or meaning differs from the generated registry table in introduction.mdx (itself guarded by the existing OpenAPI drift test). Wiring reuses existing jobs, no new job: a step in the docs job (its change filter now also matches the script and error-codes.ts), and bun test scripts/check-error-reference.test.ts in the audit job next to the other script tests (7 tests).

The two-place rule, stated precisely (#519)

apps/playground/lib/verify.ts deliberately omits invokeHostFunction (its comment, lines 37-40): demo accounts never hold DeFi positions. The guide therefore says a Soroban exit needs the API adapter plus apps/web/lib/stellar/verify.ts (via exit-expectations.ts and the web EXIT_FUNCTIONS), and a new classic operation also needs the playground allowlist.

Stub-protocol check: on a scratch checkout of origin/main I added a stub name to DefiProtocol and ran tsc. It fails in exactly four places, which the guide names: API PROTOCOLS (lib/contract-registry/index.ts), API PROTOCOL_LABEL (lib/defi-exits/plan-exits.ts), web EXIT_FUNCTIONS (lib/contract-registry/index.ts) and web describe-position.ts. The playground and SDK compile unchanged, and check-api reports the SDK API report changing (hence the sdk.api.md and version-bump step). Places the compiler does not catch (catalog.ts, octopos-adapter.ts SUPPORTED_PROTOCOLS, the response DTO's DEFI_PROTOCOLS, exit-payouts.ts whose default silently returns no tokens, the matrix script, tests) are listed explicitly. The scratch checkout was deleted.

Verification

  • bun run format:check, node scripts/check-docs-nav.mjs, node scripts/check-error-reference.mjs ., bun test scripts/check-error-reference.test.ts pass. Each of the three commits passes the nav and error-reference checks on its own.
  • mint broken-links and mint validate (mint 4.2.778 on Node 22) pass. lychee --offline README.md CONTRIBUTING.md: 0 errors.
  • Link audit: all 30 local README links exist on disk, and every docs/ page they target is in docs/docs.json.
  • Rebased on origin/main right before pushing (no new commits at that time) and the error-reference check re-run on the rebased tree.

Testnet reproduction (testnet only)

An API instance run from this branch with a scratch environment only: API_KEYS, the public testnet RPC and Horizon URLs, no mainnet variable, no mediator or sponsor secret, and the repo's .env.local never read. Four throwaway accounts funded by friendbot. The script lived outside the repo and was deleted.

Procedure Observed
Plan an account with 130 data entries 200, 3 steps, source sequence unchanged afterwards
Close into an unfunded destination 422 merge_destination_unusable
Round 1, sign and submit, then stop (closed browser) 200 success, remaining.requiresAnotherCall: true (tx a6b3944365c952742b9fd991bb0adf8b842fbd9ce377e266d33594251bdaec07)
Submit the identical signed XDR again (lost response) 200 success, same hash
Call close/transactions again (resume) 200, one transaction, remaining.steps: 0; submitting it finished the merge
Analyze and build after the merge 404 account_not_found for both
Build, sign, wait 310 s, submit 502 submit_rejected, details.resultCode: tx_too_late
Rebuild and submit a fresh transaction 200 success
Submit an unsigned transaction 400 invalid_signature

Code-derived only (not reproduced): confirmation_timeout (needs a ledger stall), the failed mediator co-sign and fee sponsorship paths (need server-side signing keys, which were not used), and the exchange-credit state (needs a real exchange). Each is described from the code below.

Where each factual claim in the recovery guide comes from

Claim Source
About 90 s wait before confirmation_timeout apps/api/src/config/constants.ts:7-8 (30 polls at 3 s), apps/api/src/lib/stellar/submit.ts:93, apps/api/src/close/close.controller.ts:525
Built transactions valid for 300 s apps/api/src/config/constants.ts:6, apps/api/src/lib/close-api/build-transactions.ts:219,657; tx_too_late message at apps/api/src/lib/utils/errors.ts:53; reproduced above
Already-confirmed transaction reported as success apps/api/src/lib/stellar/submit.ts:53-56; reproduced above
submit_rejected carries details.resultCode apps/api/src/close/close.controller.ts:529-537; reproduced above
submit_failed is the catch-all, including congestion after 3 tries apps/api/src/close/close.controller.ts:542, apps/api/src/lib/stellar/submit.ts:17,73-79
Retry-After default of 30 s on every 503 apps/api/src/common/error-envelope.filter.ts:19,31-33; the 429 header at apps/api/src/auth/rate-limiter.ts:117
SDK never retries submit docs/sdk/errors.mdx:49-51
Session kept in the browser's IndexedDB, "Resume" banner apps/web/lib/session/store.ts:4-27, apps/web/lib/session/recovery.ts:5-12, apps/web/app/[network]/page.tsx:20,65
Co-sign and fee sponsorship happen before submit, so a failure there submits nothing apps/web/hooks/useCloseExecution.ts:251,275,304; the mediator route only returns the signed XDR, apps/api/src/mediator/mediator.controller.ts:135
Analyzing a merged account returns account_not_found apps/api/src/lib/close-api/domain-errors.ts:37-41; reproduced above
source_sequence_too_far cannot be retried apps/api/src/lib/close-api/merge-preflight.ts:84-97
The app shows the error id as "Reference" apps/web/components/execution/ExecutionWizard.tsx:400
Exchange close is one atomic two-operation transaction apps/api/src/mediator/mediator.controller.ts:90-114, docs/architecture.md section 11

Support channels in the guide are only the ones already in the README and the community page (community channels, GitHub issues, SECURITY.md for vulnerabilities). It states no response time.

Retry column: codes not marked Yes or No

Counts: Yes 15, No 16, After fix 71, Check first 4. Rules: Yes means the identical request can succeed later; After fix means the request, account, plan or a stated period must change first; Check first means the outcome may be unknown; No means retrying cannot help. Derived from the throw sites and the ApiErrorResponse declarations in each controller.

Check first

  • confirmation_timeout: the 90 s poll ended (submit.ts:93-111), the transaction may still land.
  • submit_failed: unhandled failure after the send step (close.controller.ts:542), the outcome is unknown.
  • merged_account_not_found: the account being merged is gone, possibly because the close already finished.
  • tx_not_verified: the hash is not a confirmed merge on that network (stats.controller.ts:98-104), check hash and network.

After fix, change the request or credentials: bad_request, invalid_address, invalid_addresses, invalid_body, invalid_decisions, invalid_destination, invalid_memo, invalid_network, invalid_owner, invalid_rate_limit, invalid_request, invalid_signed_xdr, invalid_source, invalid_spender, invalid_token, invalid_tokens, invalid_transaction_xdr, invalid_tx_hash, missing_parameters, missing_transaction, not_found, payload_too_large, request_failed, too_many_addresses, unauthorized, unprocessable_entity, unsupported_media_type, invalid_challenge (request a new challenge), invalid_signature (sign again), api_key_not_found, key_limit_reached.

After fix, plan again or answer a decision: quote_drifted (route changed), defi_positions_stale (detection out of date), soroban_token_route_lost (route disappeared), needs_decisions, conversion_floor_missing, conversion_provider_unrecognized, transfer_destination_missing, transfer_destination_unusable, destination_not_acknowledged, merge_destination_unusable (fund or change the destination), memo_required, unsupported_memo_type, signer_normalization_unsafe, trustline_cannot_be_left.

After fix, act on the account or the protocol first: aquarius_trustline_missing, phoenix_trustline_missing, soroswap_trustline_missing, blend_emissions_trustline_missing, blend_repay_asset_missing, backstop_emissions_unclaimed, backstop_withdrawal_not_queued, backstop_withdrawal_cooling_down (wait out the cooldown), withdraw_before_repay, defi_exit_blocked, defi_exit_unsupported, defi_positions_blocked, soroban_token_conversion_failed, soroban_token_conversion_unavailable, soroban_token_conversion_unsafe, soroban_token_needs_restore, soroban_token_transfer_failed, soroban_token_transfer_unsafe (choose a different disposition), revoke_needs_restore, revoke_simulation_failed.

After fix, rebuild or resign: submit_rejected (depends on details.resultCode, for example tx_too_late needs a fresh transaction), transaction_structure_not_allowed, forward_amount_exceeds_balance, inner_fee_not_zero, fee_bump_exceeds_cap, operation_not_sponsorable.

Your spot-check items: confirmation_timeout Check first; submit_rejected After fix; quote_drifted After fix; source_sequence_too_far No (merge-preflight.ts:84-97, "Retrying does not help"); registry_expired No (close.controller.ts:332-340, the registry must be re-verified by an operator); mediator_not_configured No (mediator.controller.ts:67-72, a server setup issue). All three 503 rows still carry Retry-After, which the page says explicitly does not make them retryable.

Review notes

Security-sensitive documentation: the protocol-exit guide describes verify() and transaction construction and needs closer review. No source under apps/ or packages/ is touched. The PR assignee was left unset.

Closes #517
Closes #519
Closes #520

@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lumenwipe Ready Ready Preview Oct 10, 2026 7:57pm UTC
playground Ready Ready Preview Oct 10, 2026 7:57pm UTC

@salazarsebas salazarsebas self-assigned this Oct 10, 2026
@salazarsebas
salazarsebas merged commit 776854e into main Oct 10, 2026
24 checks passed
@salazarsebas
salazarsebas deleted the docs/recovery-protocol-exit-routing branch October 10, 2026 19:59

This branch was successfully deployed

2 active deployments
Preview – lumenwipe — 0c922a37 Deployed Oct 10, 2026 by vercel[bot]
Preview – playground — 0c922a37 Deployed Oct 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant