Skip to content

Payments 4/8: Real transaction status tracking and confirmation lifecycle - #149

Merged
wumibals merged 1 commit into
LadderMine:mainfrom
AbdulmujibOladayo:payments-4-8-transaction-status-tracking
Aug 22, 2026
Merged

Payments 4/8: Real transaction status tracking and confirmation lifecycle#149
wumibals merged 1 commit into
LadderMine:mainfrom
AbdulmujibOladayo:payments-4-8-transaction-status-tracking

Conversation

@AbdulmujibOladayo

Copy link
Copy Markdown
Contributor

Summary

Closes #141.

Builds a real getTransaction-polling confirmation state machine (submitted -> pending -> confirmed | failed | expired) driven by actual Soroban RPC responses, replacing the setTimeout mock the deposit flow used before.

  • sdks/typescript: TransactionPipeline.waitForTransaction / YieldLadder.waitForConfirmation poll getTransaction with exponential backoff, distinguishing a still-propagating NOT_FOUND from a terminal FAILED (which also covers a post-inclusion contract revert — Soroban surfaces both as non-SUCCESS, unlike chains where a reverted call can still be "successfully included") from SUCCESS, with a hard timeout (TransactionTimedOutError). Adds the canonical PaymentStatus enum. Confirmation-depth policy: Stellar/Soroban's SCP consensus gives a ledger deterministic finality the instant it closes, so SUCCESS is treated as final immediately — no extra confirmation-depth wait is needed.
  • app: mirrors the same polling logic in services/rpc.ts, extends paymentIntent's IntentStatus with pending/expired (plus markPending/markExpired), and wires the deposit page's step-4 view to it for real.
  • usePosition: no longer fabricates a zero position dressed up as decoded on-chain data (a silently-wrong zero balance is worse than an explicit "unavailable" state). Also fixes its contract config, which read one vault address per tier — the actual deployed architecture is a single VaultRouter taking tier as a call argument (per sdks/typescript's YieldLadder, Payments 2/8: Implement real Soroban transaction pipeline in the TypeScript SDK #139).
  • useLastHarvest/useHarvestHistory: audited per the issue's "align them if they share the pattern" ask. They don't — there's no deployed Harvester contract or event indexer yet to wire either to (tracked separately under GF-12), unlike usePosition which had a real VaultRouter.position() to call. Left as explicit, tracked stubs with a comment recording the audit conclusion.

A scoped, documented limitation

The app doesn't yet depend on @yieldladder/sdk / @stellar/stellar-sdk (it's a separate pnpm project from sdks/typescript, with its own lockfile — no shared workspace). Adding either as a real dependency needs a pnpm install to regenerate app/pnpm-lock.yaml correctly, which wasn't safe to run in the environment this PR was prepared in. Two things fall out of that, both called out inline with TODO(#141 follow-up):

  1. The deposit page's actual submission (building + signing + broadcasting the transaction) is still a placeholder — it produces a format-valid but not-really-submitted hash. Everything after that (the polling built in this PR) is real and already wired for the day a genuine hash is available.
  2. usePosition can't decode real ledger-entry/contract-data without an XDR library, so it now honestly reports "unavailable" rather than faking zeros, with the real fix (call VaultRouter.position() via simulateTransaction, exactly like YieldLadder.position() already does) documented as the next step once the SDK is wired in.

Test plan

  • New unit tests: sdks/typescript/src/__tests__/waitForTransaction.test.ts (resolves on SUCCESS, retries through NOT_FOUND, rejects with TransactionFailedError on FAILED, rejects with TransactionTimedOutError past the deadline).
  • New unit tests: app/src/services/rpc.test.ts (same scenarios against the app's mirrored poller, mocking fetch).
  • Extended app/src/lib/paymentIntent.test.ts for the new pending/expired states and their lock/retry semantics.
  • CI (sdk and app jobs: typecheck, build, test) — pending on this PR.

…ycle

Adds a real getTransaction-polling confirmation state machine (submitted ->
pending -> confirmed | failed | expired), driven by actual Soroban RPC
responses instead of a setTimeout mock:

- sdks/typescript: TransactionPipeline.waitForTransaction / YieldLadder
  .waitForConfirmation poll getTransaction with backoff, distinguishing
  still-propagating NOT_FOUND from a terminal FAILED (which also covers a
  post-inclusion contract revert) from SUCCESS, with a hard timeout.
- app: mirrors the same polling in services/rpc.ts (kept dependency-free —
  see the SDK-wiring TODOs left in deposit/page.tsx and usePosition.ts for
  why the app can't yet import the SDK/@stellar/stellar-sdk directly here),
  extends paymentIntent's IntentStatus with pending/expired, and wires the
  deposit page's step-4 view to real polling.
- usePosition no longer fabricates a zero position as if it were decoded
  on-chain data; it now reports honestly that SDK wiring is pending, and
  its contract config is fixed to match the single-VaultRouter deployment
  instead of a stale per-tier-vault scheme.
- useLastHarvest/useHarvestHistory audited per the issue and left as-is —
  no real Harvester contract/indexer exists yet to wire them to (GF-12).

Closes LadderMine#141
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

@AbdulmujibOladayo is attempting to deploy a commit to the wumibals' projects Team on Vercel.

A member of the Team first needs to authorize it.

@wumibals wumibals 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.

Reviewed the real transaction status tracking and confirmation lifecycle PR (closes #141).

  • Replacing the setTimeout mock with a real getTransaction-polling state machine is the actual point of this issue, and the terminal-state handling is Soroban-aware rather than generically copied from another chain: correctly noting that Soroban surfaces both "not yet included" and "included but reverted" as non-SUCCESS, so a still-propagating NOT_FOUND has to be distinguished from a genuinely terminal FAILED rather than treated the same. Getting that distinction wrong would mean either giving up on transactions that just haven't landed yet, or hanging forever on ones that reverted.
  • The confirmation-depth reasoning (treating SUCCESS as final immediately, since SCP gives deterministic finality the moment a ledger closes, unlike probabilistic-finality chains that need N confirmations) is correct and worth having stated explicitly in the code/PR rather than left as tribal knowledge.
  • usePosition no longer fabricating a zero position dressed up as decoded on-chain data is the right call — a silently-wrong zero balance is strictly worse than an explicit "unavailable" state, since a user could read a real zero as "I have no funds" when actually the app just couldn't decode anything. Good instinct to fix this as a drive-by even though it wasn't the primary ask.
  • Also good catch fixing usePosition's contract config, which assumed one vault address per tier when the actual deployed architecture is a single VaultRouter taking tier as an argument — that's a real bug that would have silently queried the wrong thing.
  • The useLastHarvest/useHarvestHistory audit is handled honestly: rather than forcing a superficial alignment with usePosition where no equivalent deployed contract/indexer exists yet, they're left as explicit tracked stubs with the audit conclusion recorded in a comment. That's the right outcome for an issue that asked to "align them if they share the pattern" and found they don't.
  • The scoped limitation (app doesn't yet depend on the SDK packages, so real submission is still a placeholder) is disclosed clearly with inline TODO(#141 follow-up) markers rather than silently shipping a half-real flow, and the reasoning for not running pnpm install to regenerate the lockfile in this environment is sound — that's exactly the kind of change that shouldn't be improvised inside an unrelated PR.
  • Test coverage matches the actual state machine: SUCCESS resolution, NOT_FOUND retry, FAILED rejection, and timeout are all covered on both the SDK and app sides (mirrored pollers, mirrored tests), plus the extended paymentIntent lock/retry semantics for the new pending/expired states.

CI is green across Soroban contracts, Next.js dashboard, and TypeScript SDK (the PR's own checklist listed CI as pending at time of writing, but it has since completed successfully). Vercel's FAILURE is the usual unauthorized deployment integration link, unrelated to the code.

Approving — solid, chain-aware confirmation logic with an honestly-scoped remaining gap.

@wumibals
wumibals merged commit 3e3dc50 into LadderMine:main Aug 22, 2026
3 of 4 checks passed
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.

Payments 4/8: Real transaction status tracking and confirmation lifecycle

2 participants