Skip to content

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

Closed

Description

@wumibals

Problem / Context

app/src/app/deposit/page.tsx currently fakes transaction confirmation with a timer:

function handleConfirm() {
  setStep(4);
  setTxStatus('pending');
  // sdk.deposit({ tier: tier.label, amount }) would go here
  setTimeout(() => setTxStatus('confirmed'), 2000);
}

(lines 48-53) — there is no real polling of chain state anywhere. app/src/hooks/usePosition.ts calls callRpc('getLedgerEntries', ...) but then discards the result and always returns hardcoded zeros (lines 51-64: "Parse XDR result once SDK is available; return zeros until then."). app/src/services/rpc.ts implements exactly one method, getLatestLedger — nothing for sendTransaction, getTransaction, or simulateTransaction. There is currently no defined boundary anywhere between "the app believes a payment happened" and "the blockchain has actually confirmed it."

Objective

Build a real transaction-lifecycle state machine (submitted -> pending -> included -> finalized, or failed at any stage) driven by actual Soroban RPC responses, and make the app report true on-chain position state instead of a fixed zero.

Technical approach

  • Extend the RPC layer (app/src/services/rpc.ts, or move into the SDK from Payments 2/8: Implement real Soroban transaction pipeline in the TypeScript SDK #139 so both share it) with sendTransaction and getTransaction; poll getTransaction by hash with backoff until status resolves to SUCCESS, FAILED, or remains NOT_FOUND past a reasonable propagation window.
  • Define one canonical status enum shared between the SDK and the app (e.g. PaymentStatus: 'building' | 'simulating' | 'awaiting_signature' | 'submitted' | 'pending' | 'confirmed' | 'failed' | 'expired'), replacing the app's local ad hoc TxStatus = 'pending' | 'confirmed' | 'failed' (deposit/page.tsx line 16).
  • Rewrite usePosition to decode the real ledger-entry/contract-data response, or (preferably) call VaultRouter.position() via simulateTransaction, so the dashboard reflects true on-chain principal/shares/lockUntil rather than a stub.
  • Wire the deposit page's step-4 view to this real status stream instead of the setTimeout mock.
  • Audit app/src/hooks/useLastHarvest.ts and useHarvestHistory.ts for the same stubbed-data pattern and align them if they share it.
  • Decide and document a confirmation-depth/finality policy: how many ledgers past inclusion should the UI treat as "safe," versus merely "included."

Detailed scope

app/src/services/rpc.ts, app/src/hooks/usePosition.ts, app/src/app/deposit/page.tsx, sdks/typescript (a status-types/polling helper reusable by both SDK consumers and the app directly), and app/src/hooks/useLastHarvest.ts / useHarvestHistory.ts if they need the same treatment.

Important edge cases and failure scenarios

  • A transaction can be included in a ledger while the contract call itself still reverts (Soroban distinguishes tx-level success from host-function/contract-level failure) — this must surface as a failure, not be reported as "confirmed."
  • getTransaction returning NOT_FOUND can mean "still propagating, keep polling" or "genuinely expired/dropped" — the poller must distinguish these rather than treating every NOT_FOUND as terminal.
  • Race condition: a user submits a deposit and immediately navigates to the portfolio page before the vault's balance reflects the new principal — the UI must not render stale zero state as if the deposit failed.
  • Polling/backoff must have a hard timeout that hands control to the failure-handling issue below rather than polling indefinitely.

Dependencies

Depends on #139 (real submission produces a real transaction hash to poll) and #140 (status must be keyed by the same idempotent intent identifier so the UI can recover this state correctly after a reload).

Acceptance criteria

  • The deposit page shows real pending -> confirmed/failed transitions driven by actual testnet transaction results, not a timer.
  • usePosition returns real principal/shares/lockUntil for a funded testnet address, verified against direct contract-state reads.
  • One documented, single-source-of-truth status enum is used by both the SDK and the app — no divergent status types between them.

Definition of done

PR merged; manual verification against Soroban testnet shows a real deposit transitioning through the full status lifecycle in the UI; usePosition is unit-tested with mocked RPC responses for confirmed/failed/pending cases.

Activity

  1. AbdulmujibOladayo commented on Aug 19, 2026

    @AbdulmujibOladayo
    Contributor

    Looked through this project and understood its current implementation, architecture, and overall stage of maturity.

  2. grantfox-oss commented on Aug 19, 2026

    @grantfox-oss

    🦊 GrantFox — @AbdulmujibOladayo has been assigned to this issue as part of the Third Campaign campaign!

    Next steps:

    1. Open a Pull Request referencing this issue (e.g., Closes #141)
    2. Your PR will be reviewed by the LadderMine maintainers

    Good luck! Track your progress on GrantFox.

  3. added a commit that references this issue on Aug 22, 2026
    7ed655a
  4. grantfox-oss commented on Aug 22, 2026

    @grantfox-oss

    🎉 This issue has been marked as completed on GrantFox as part of the Third Campaign campaign!

    @AbdulmujibOladayo's PR #149 was approved and merged by @wumibals.

    🏆 @AbdulmujibOladayo: You earned 40 FoxPoints for this contribution! Your current tier: Builder (1,425 total points). Track your full progress on GrantFox.

    👏 Great work, @AbdulmujibOladayo! Keep contributing to LadderMine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions