Payments 4/8: Real transaction status tracking and confirmation lifecycle - #149
Merged
wumibals merged 1 commit intoAug 22, 2026
Conversation
…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
|
@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
approved these changes
Aug 22, 2026
wumibals
left a comment
Contributor
There was a problem hiding this comment.
Reviewed the real transaction status tracking and confirmation lifecycle PR (closes #141).
- Replacing the
setTimeoutmock with a realgetTransaction-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-propagatingNOT_FOUNDhas to be distinguished from a genuinely terminalFAILEDrather 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
SUCCESSas 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. usePositionno 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 singleVaultRoutertaking tier as an argument — that's a real bug that would have silently queried the wrong thing. - The
useLastHarvest/useHarvestHistoryaudit is handled honestly: rather than forcing a superficial alignment withusePositionwhere 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 runningpnpm installto 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
paymentIntentlock/retry semantics for the newpending/expiredstates.
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.
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #141.
Builds a real getTransaction-polling confirmation state machine (
submitted -> pending -> confirmed | failed | expired) driven by actual Soroban RPC responses, replacing thesetTimeoutmock the deposit flow used before.sdks/typescript:TransactionPipeline.waitForTransaction/YieldLadder.waitForConfirmationpollgetTransactionwith exponential backoff, distinguishing a still-propagatingNOT_FOUNDfrom a terminalFAILED(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") fromSUCCESS, with a hard timeout (TransactionTimedOutError). Adds the canonicalPaymentStatusenum. Confirmation-depth policy: Stellar/Soroban's SCP consensus gives a ledger deterministic finality the instant it closes, soSUCCESSis treated as final immediately — no extra confirmation-depth wait is needed.app: mirrors the same polling logic inservices/rpc.ts, extendspaymentIntent'sIntentStatuswithpending/expired(plusmarkPending/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 singleVaultRoutertaking tier as a call argument (persdks/typescript'sYieldLadder, 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), unlikeusePositionwhich had a realVaultRouter.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 fromsdks/typescript, with its own lockfile — no shared workspace). Adding either as a real dependency needs apnpm installto regenerateapp/pnpm-lock.yamlcorrectly, which wasn't safe to run in the environment this PR was prepared in. Two things fall out of that, both called out inline withTODO(#141 follow-up):usePositioncan'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 (callVaultRouter.position()viasimulateTransaction, exactly likeYieldLadder.position()already does) documented as the next step once the SDK is wired in.Test plan
sdks/typescript/src/__tests__/waitForTransaction.test.ts(resolves on SUCCESS, retries through NOT_FOUND, rejects withTransactionFailedErroron FAILED, rejects withTransactionTimedOutErrorpast the deadline).app/src/services/rpc.test.ts(same scenarios against the app's mirrored poller, mockingfetch).app/src/lib/paymentIntent.test.tsfor the newpending/expiredstates and their lock/retry semantics.sdkandappjobs: typecheck, build, test) — pending on this PR.