Skip to content

Add settlement volume totals FIX - #211

Merged
Jagadeeshftw merged 2 commits into
AnchorNet-Org:mainfrom
Mitch5000:Add-settlement-volume-totals-FIXED
Jul 27, 2026
Merged

Add settlement volume totals FIX#211
Jagadeeshftw merged 2 commits into
AnchorNet-Org:mainfrom
Mitch5000:Add-settlement-volume-totals-FIXED

Conversation

@Mitch5000

Copy link
Copy Markdown
Contributor

================================================================================
PULL REQUEST

Repository : Mitch5000/AnchorNet-Backend
Base branch : main
Head branch : fix/metrics-settled-totals
Commit : 5cc1583
Type : Feature / enhancement (additive, backward compatible)
Files changed: 6 modified, 0 created, 0 deleted
Diff : +374 / -6

================================================================================
TITLE

Add totalSettledAmount and totalFeesCollected to the metrics snapshot

================================================================================
DESCRIPTION


Summary

GET /api/v1/metrics reported settlements and pendingSettlements as counts and
totalLiquidity as a pool-wide sum, but never summed settlement amount or fee.
An operator could see HOW MANY settlements existed, but not HOW MUCH value had
been settled or how much protocol fee revenue had been earned. Getting those
numbers meant paging through GET /api/v1/settlements and summing client-side.

This PR adds two fields to the metrics snapshot, both computed from executed
settlements only:

totalSettledAmount - sum of amount over executed settlements
totalFeesCollected - sum of fee over executed settlements

The change is purely additive. Every pre-existing field keeps its exact name,
type and meaning, so existing consumers are unaffected.


Problem

In src/routes/metrics.ts, snapshot() pulled from three sources but only ever
produced counts of settlements:

settlements: settlements.length,
pendingSettlements: settlements.filter((s) => s.status === "pending").length,

The amount and fee fields carried by every settlement were never aggregated.

totalLiquidity does sum value, but it sums POOL BALANCES: supply currently
parked in the network. That is a different question from how much value has
actually flowed THROUGH the network, and it does not move when a settlement
executes against already-counted liquidity. There was no field answering
"what has this network settled, and what did we earn on it?"


Why executed-only

The settlement lifecycle in src/models/settlement.ts mirrors the on-chain
contract, and only one of the three states represents value that actually moved:

STATUS      MEANING                                    VALUE MOVED?
---------   ----------------------------------------   ------------
pending     Liquidity reserved, still cancellable      No
executed    Liquidity permanently consumed             Yes
cancelled   Reservation released                       No

Counting pending settlements would inflate the totals with value that may never
settle (a pending settlement can still be cancelled). Counting cancelled ones
would report fees on revenue that was never earned. "Collected" has to mean
collected, so both are excluded.

This matches the accounting already inside SettlementService, where execute()
moves an amount out of reserved and into consumed, while cancel() releases
the reservation without ever touching consumed.


Changes

src/routes/metrics.ts (core fix)

Filter once to executed settlements, then reduce over amount and fee:

// Value settled and fees earned count only executed settlements: a
// `pending` settlement has reserved liquidity but may still be cancelled,
// and a `cancelled` one released it without ever moving value.
const executed = settlements.filter((s) => s.status === "executed");

return {
  ...                                                  // existing fields untouched
  totalSettledAmount: executed.reduce((sum, s) => sum + s.amount, 0),
  totalFeesCollected: executed.reduce((sum, s) => sum + s.fee, 0),
};

The MetricsSnapshot interface gained the two typed fields, was given TSDoc on
every member, and is now exported so consumers can import the response type.

Because recordSnapshot() spreads snapshot() into the bounded history buffer,
GET /api/v1/metrics/history inherits both fields with zero additional changes.

src/openapi.ts
Added descriptions to /api/v1/metrics and /api/v1/metrics/history naming both
new fields and stating the executed-only rule.

src/openapi.test.ts
New test asserting the spec documents both fields and the executed-only rule.

src/routes/metrics.test.ts
12 new tests in a new "metrics settled-value totals" block. The 5 original
tests are untouched.

README.md
Metrics section rewritten to describe both totals and the executed-only rule.

CHANGELOG.md
New [Unreleased] -> Added entry.


Design notes

  • A single .filter() is shared by both reducers. One pass over the status check
    instead of two, and the executed-only predicate is stated exactly once, so the
    two totals can never drift apart.

  • The aggregation lives in the route's snapshot(), per the issue's suggested
    execution, rather than being pushed down into SettlementService. This keeps
    the diff minimal and adds no new public service surface. Worth flagging for
    reviewers: if these totals should be reusable by other callers, moving them
    behind a SettlementService method would be the cleaner long-term home. Happy
    to make that change if preferred.

  • Both reducers seed at 0, so a fresh app reports 0 / 0 rather than NaN or
    undefined. Covered by a test.


Example

Given one anchor with 100,000 USDC liquidity and four settlements
(10,000 pending / 20,000 executed / 30,000 cancelled / 40,000 executed),
at the default 10 bps fee:

GET /api/v1/metrics

{
  "anchors": 1,
  "activeAnchors": 1,
  "pools": 1,
  "totalLiquidity": 100000,
  "settlements": 4,
  "pendingSettlements": 1,
  "totalSettledAmount": 60000,     <-- new: 20000 + 40000, executed only
  "totalFeesCollected": 60         <-- new: 20 + 40, executed only
}

Counts still cover all four settlements. The value totals cover only the two
executed ones.


Backward compatibility

Fully backward compatible. Only additive.

  • No existing field renamed, retyped, removed, or recalculated.
  • GET /api/v1/metrics/history snapshots gain the same two fields plus the
    existing timestamp; the shape is otherwise identical.
  • A test pins the EXACT response key set on both the metrics response and the
    history snapshot, so any future accidental field rename or removal fails CI
    rather than silently breaking clients.

Testing

GATE                          RESULT
---------------------------   ----------------------------------------------
npx jest (full suite)         PASS - 451/451 tests, 40 suites
                              (baseline 439, so +12 new, 0 regressions)
npm run build (tsc)           PASS - exit 0, no errors; new fields present
                              in emitted metrics.js and metrics.d.ts
npm run lint (ESLint)         PASS - exit 0, clean
Coverage: metrics.ts          100% stmts / branch / funcs / lines
Coverage: openapi.ts          100% stmts / branch / funcs / lines
Coverage: global              97.11% stmts / 95.67% funcs / 97.55% lines
Live HTTP smoke test          PASS - real requests against compiled dist/
Mutation testing              PASS - 5/5 deliberate breakages caught

The 12 new tests

1.  Fresh app reports 0 / 0
2.  Pending-only settlement excluded from both totals
3.  Cancelled-only settlement excluded from both totals
4.  Single executed settlement contributes its amount and fee
5.  Mixed pending + executed + cancelled + executed (the 4-settlement case)
6.  Multi-anchor, multi-asset executed settlements sum correctly
7.  Value moves into the totals only once the settlement is executed
8.  Totals stay stable when a LATER settlement is cancelled
9.  Exact response key set - backward-compatibility guard
10. History snapshots carry the totals and evolve correctly across reads
11. Totals are finite numbers, never string / null / NaN
12. OpenAPI spec documents both fields (in openapi.test.ts)

Mutation testing

To confirm the new tests actually catch regressions rather than merely executing
lines, the implementation was deliberately broken five ways:

#   MUTATION                                          TESTS FAILED
--  -----------------------------------------------   ----------------
1   Drop the status filter (sum every settlement)     6  caught
2   Use status !== "cancelled" (lets pending leak)    4  caught
3   Sum `fee` into totalSettledAmount                 6  caught
4   totalFeesCollected = executed.length              5  caught
5   Delete the field entirely                         compile error, caught

All five were caught. Source was restored and verified byte-identical (diff)
after each mutation, and the final committed file matches the intended fix.

Live smoke test

A real server was started on the compiled dist/ output and driven over HTTP with
a mixed pending + executed + cancelled workload. It returned
totalSettledAmount: 20000 and totalFeesCollected: 20 (the executed leg only),
confirmed the six pre-existing fields were unchanged, and confirmed the served
openapi.json contains the new documentation.


Acceptance criteria

[x] GET /api/v1/metrics includes correct totalSettledAmount and
    totalFeesCollected, computed only from executed settlements
[x] Existing fields (anchors, activeAnchors, pools, totalLiquidity,
    settlements, pendingSettlements) unchanged
[x] GET /api/v1/metrics/history snapshot shape remains backward compatible
    (new fields simply added)
[x] src/openapi.ts updated to document the new fields
[x] Tests cover a mix of pending / executed / cancelled settlements
[x] Minimum 95% test coverage (100% on all changed files)
[x] Clear documentation (TSDoc, inline rationale, OpenAPI, README, CHANGELOG)

Security

No security impact. This is a read-only aggregate computed over settlement data
that GET /api/v1/settlements already exposes. No new input is accepted, no new
data is persisted, and no new field is introduced to the domain model. The route
sits behind the same middleware stack (API key auth, rate limiting, security
headers) as before.


Files changed

FILE                         TYPE       +/-      CHANGE
--------------------------   --------   ------   -----------------------------
src/routes/metrics.ts        Modified   +35 -1   CORE FIX. Two new
                                                 MetricsSnapshot fields plus
                                                 executed-only sums in
                                                 snapshot(); interface
                                                 exported and documented.
src/routes/metrics.test.ts   Modified   +281     12 new tests in a new
                                                 describe block; the 5
                                                 original tests untouched.
src/openapi.ts               Modified   +17 -2   Documented new fields on
                                                 /api/v1/metrics and
                                                 /api/v1/metrics/history.
src/openapi.test.ts          Modified   +15      One test asserting the spec
                                                 documents both fields.
README.md                    Modified   +11 -3   Metrics section describes
                                                 both totals and the
                                                 executed-only rule.
CHANGELOG.md                 Modified   +15      [Unreleased] -> Added entry.

TOTAL                        6 files    +374 -6

No files created. No files deleted.


Reviewer notes

  • Field naming. The issue said "or similarly named". totalSettledAmount and
    totalFeesCollected were chosen to pair readably with the existing
    totalLiquidity. If the team prefers something more explicit about the status
    filter, such as totalExecutedAmount, the rename is mechanical and I can push
    it on request.

  • Placement. As noted in Design notes, the aggregation currently lives in the
    route. Say the word and it moves into SettlementService.

  • Precision. amount and fee are plain numbers in the existing model, and these
    sums inherit that. No change in precision behaviour is introduced by this PR,
    but at very large volumes a shared integer/BigInt strategy across the codebase
    would be the correct follow-up. Out of scope here.

================================================================================
HOW TO PUSH AND OPEN THIS PR

This sandbox has no GitHub credentials and no gh CLI, so the branch could not be
pushed from here. The commit is ready locally at /home/user/AnchorNet-Backend on
branch fix/metrics-settled-totals.

Option A - GitHub CLI

cd AnchorNet-Backend
git push -u origin fix/metrics-settled-totals
gh pr create \
  --base main \
  --head fix/metrics-settled-totals \
  --title "Add totalSettledAmount and totalFeesCollected to the metrics snapshot" \
  --body-file PULL_REQUEST.txt

Option B - Web UI

cd AnchorNet-Backend
git push -u origin fix/metrics-settled-totals

Then open:
https://github.com/Mitch5000/AnchorNet-Backend/compare/main...fix/metrics-settled-totals

Paste the TITLE and DESCRIPTION sections above into the form.

Note: if you push from a fork rather than from Mitch5000/AnchorNet-Backend
directly, change the remote first:

git remote set-url origin https://github.com/<your-user>/AnchorNet-Backend.git

================================================================================

Closes #146

@Jagadeeshftw
Jagadeeshftw merged commit 6c3f5a7 into AnchorNet-Org:main Jul 27, 2026
1 check 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.

Add settlement volume totals (sum of amount/fee) to the routes/metrics.ts snapshot, not just counts

2 participants