Skip to content

feat: add typed transaction result reconciliation (Closes #62) - #153

Open
panditdhamdhere wants to merge 1 commit into
Raegis-RWA:mainfrom
panditdhamdhere:feat/transaction-result-reconciliation
Open

feat: add typed transaction result reconciliation (Closes #62)#153
panditdhamdhere wants to merge 1 commit into
Raegis-RWA:mainfrom
panditdhamdhere:feat/transaction-result-reconciliation

Conversation

@panditdhamdhere

Copy link
Copy Markdown
Contributor

Description

Submitting a Soroban transaction and knowing what happened to it are two different
problems. sendTransaction only reports whether the network accepted the transaction
for inclusion, and getTransaction returns NOT_FOUND both for transactions that have
not landed yet and for ones outside the RPC retention window. Consumers currently have
to interpret those raw strings themselves, which is how pending transactions get
mistaken for failures and get resubmitted.

This PR reconciles both signals into one stable status model, exposed as
client.transaction.

Why this approach: the mapping is deliberately conservative. NOT_FOUND stays
pending because absence is not proof of rejection, and any status this SDK version
does not recognise resolves to unknown rather than confirmed. rejected (refused
before inclusion, no fee or sequence consumed) is kept distinct from failed (included
in a ledger, then failed), because merging them would hide whether ledger state changed.
Polling reads state only — no reconciliation path calls sendTransaction, so a long
poll cannot cause duplicate ledger effects.

Files added

  • src/types/transaction-result.ts — discriminated TransactionResult union over the five states
  • src/transactions/reconciliation.ts — pure reconcilers for status, send responses, and get responses
  • src/transactions/module.tsTransactionModule with getResult and bounded waitForResult
  • src/errors/transaction.tsTransactionReconciliationError
  • src/soroban/transaction-result.tsdecodeTransactionResultCode, reads only the result switch name
  • docs/transaction-reconciliation.md
  • tests/transaction-reconciliation.test.ts, tests/transaction-module.test.ts, tests/fixtures/transaction-results.ts

Files modified

  • src/client.ts — wires client.transaction (singular, to avoid colliding with the mock client's existing transactions receipt array)
  • src/index.ts — public exports for the module, helpers, types, and error
  • docs/api-reference.md — full TransactionModule section with signatures, params, returns, and every throw path
  • README.md — quickstart section
  • CONTRIBUTING.md — doc-update rule and review checklist item

Closes: #62


Evidence Checklist

1. Issue Reference

  • This PR references a tracked issue (Closes #62).
  • The linked issue's acceptance criteria are copied into Section 6 below.

2. Implementation Summary

  • A clear description of what changed is provided above.
  • A brief explanation of why this approach was chosen is included.
  • All files added, modified, or removed are listed or summarised.

3. Tests

  • New or updated unit tests cover every added or changed public method.
  • Tests use predictable mocks rather than live RPC.
  • If tests are not applicable, a justification is provided below.

45 new tests across two dedicated suites. Coverage includes every send and get
status, unrecognised future statuses, NOT_FOUNDSUCCESS, NOT_FOUND
FAILED, exhausted observation windows, bounded exponential backoff, RPC
timeouts surfacing as NetworkFailure, validation rejecting bad hashes and
poll options before any RPC call, and an assertion that sendTransaction is
never called during reconciliation or polling.

tests/transaction-module.test.ts mocks rpc.Server and injects a sleep
function, so polling tests are deterministic with no real timers.

4. Commands Run

git fetch upstream
git rebase upstream/main
npm run check
git push --force-with-lease
Command output
Successfully rebased and updated refs/heads/feat/transaction-result-reconciliation.

> @aegis/sdk@0.1.0 check
> npm run build && npm test -- --runInBand && npm run test:compat

> @aegis/sdk@0.1.0 build
> tsc

> @aegis/sdk@0.1.0 test
> jest --runInBand

PASS tests/client-factory.test.ts
PASS tests/mock-client.test.ts
PASS tests/events-decoder.test.ts
PASS tests/transaction-module.test.ts
PASS tests/transaction-reconciliation.test.ts
PASS tests/admin-receipts.test.ts
PASS tests/network-failures.test.ts
PASS tests/config.test.ts
PASS tests/role.test.ts
PASS tests/investor.test.ts
PASS tests/events-module.test.ts
PASS tests/client.test.ts
PASS tests/mock-client-examples.test.ts

Test Suites: 13 passed, 13 total
Tests:       162 passed, 162 total
Snapshots:   0 total
Time:        2.558 s, estimated 3 s
Ran all test suites.

> @aegis/sdk@0.1.0 test:compat
> node scripts/check-compat.mjs

Browser compatibility: bundle resolved without Node-only imports.
Node compatibility: public SDK entrypoint and signer initialized.

To github.com:panditdhamdhere/aegis-sdk.git
 + 5b6a9bb...01f20c7 feat/transaction-result-reconciliation -> feat/transaction-result-reconciliation (forced update)

5. CI Status

  • All GitHub Actions checks pass on this PR.

(Check this after CI finishes on the PR — leave unchecked until then.)

6. Acceptance Criteria Coverage

  • Reconciliation helper is implemented. reconcileTransactionStatus plus
    reconcileSendTransactionResponse and reconcileGetTransactionResponse
    wrappers, all exported from the package root.
  • Confirmed, failed, pending, unknown, and rejected states are typed.
    TransactionResultStatus union with one discriminated interface per state,
    so narrowing on status gives type-safe field access.
  • Polling can use reconciliation safely. waitForResult is bounded by
    maxAttempts, backs off up to maxIntervalMs, and routes every read
    through client.runNetworkOperation so RPC problems arrive as typed
    NetworkFailure.
  • Tests cover major states. All five states plus window expiry,
    unrecognised statuses, backoff bounds, and validation paths.
  • Docs explain retry caution. docs/transaction-reconciliation.md has a
    dedicated section with five numbered rules, including that
    NetworkFailure.retryable refers to retrying the read, not the submission.
  • No blind resubmission is encouraged. No reconciliation or polling path
    calls sendTransaction (asserted by test). safeToResubmit is true only
    for rejected, the one case where the network never accepted the
    transaction — and the docs still require a corrected, re-signed transaction
    rather than resending the same envelope.

Reviewer Notes

Branch is rebased onto current upstream/main (includes role-aware client
factory #141 and the recent contributor docs PRs). Local npm run check passes
after the rebase.

npm run lint and npm run format cannot run on this branch or on main.
Both scripts invoke eslint and prettier, but neither package is in
devDependencies, so npm run verify exits at eslint: command not found. This
is pre-existing and unrelated to this change; CI runs npm run check, which
passes.

Known gap, documented rather than fixed: AssetModule.mint and transfer
still return a bare hash and never inspect sendTransaction's status, so an
ERROR or TRY_AGAIN_LATER submission is currently indistinguishable from an
accepted one. Changing their return type is API-breaking, so I left an explicit
note in docs/api-reference.md pointing users at client.transaction.waitForResult.
Happy to take that on as a follow-up issue if maintainers want it.

Naming: the module is client.transaction (singular) because the mock client
in @aegis/sdk/testing already exposes a transactions array of receipts.

Reconcile submitted Soroban transactions into confirmed, failed, pending,
rejected, and unknown states so dashboards stop inferring outcomes from raw
RPC status strings.

Mapping is conservative: NOT_FOUND stays pending because absence means either
"not included yet" or "outside the retention window", and unrecognised statuses
resolve to unknown rather than confirmed. Polling is bounded and only ever calls
getTransaction, so no reconciliation path can cause duplicate ledger effects.
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.

1 participant