Skip to content

Add a `transfer liquidity fixed - #208

Merged
Jagadeeshftw merged 3 commits into
AnchorNet-Org:mainfrom
Mitch5000:Add-a-`transferLiquidity-FIXED
Jul 27, 2026
Merged

Add a `transfer liquidity fixed#208
Jagadeeshftw merged 3 commits into
AnchorNet-Org:mainfrom
Mitch5000:Add-a-`transferLiquidity-FIXED

Conversation

@Mitch5000

Copy link
Copy Markdown
Contributor

===========================================================
PULL REQUEST (PLAIN TEXT — paste into GitHub as-is)

TITLE

feat: add atomic liquidity transfer between anchors
(POST /api/v1/liquidity/transfer)

BODY

SUMMARY

Moving liquidity between two anchors previously required a withdraw
from one anchor followed by a separate add for the other. That two-call
flow was not atomic, was not auditable as a single logical operation,
and briefly reduced the total pool liquidity between the two calls.

This PR adds a dedicated transfer operation that performs both halves
atomically in memory: LiquidityService.transferLiquidity({ from, to,
asset, amount }) and a new POST /api/v1/liquidity/transfer endpoint.

Fixes the issue: "Add a dedicated transfer operation that performs both
halves atomically in memory."

WHAT CHANGED

  • src/services/liquidityService.ts
    New transferLiquidity(input: { from, to, asset, amount }) method.
    It reuses the existing validation helpers (requireString,
    normalizeAsset, requirePositiveNumber), checks that the source
    anchor has sufficient balance, then atomically decrements the
    source and increments the destination for the same asset. Returns
    the resulting entries for both anchors as { from, to }.
  • src/routes/liquidity.ts
    New route POST /api/v1/liquidity/transfer. Responds 200 with
    { from, to }.
  • src/openapi.ts
    New OpenAPI path entry for /api/v1/liquidity/transfer.
  • src/services/liquidityService.test.ts
    8 new service-level tests (see Testing below).
  • src/routes/liquidity.test.ts
    6 new route-level tests (see Testing below).
  • README.md
    Endpoint documentation for the new transfer route.

WHY IT IS ATOMIC

All validation that can throw runs before the first state mutation.

  • Invalid input (blank from/to, non-positive amount, malformed asset)
    throws before any repository access.
  • Source anchor with no balance returns 404 NOT_FOUND.
  • Amount exceeding the source balance returns 400
    INSUFFICIENT_LIQUIDITY, the exact same error code and message format
    as withdrawLiquidity.
  • Self-transfer (from equals to) returns 400 SAME_ANCHOR; this also
    protects accounting, because source and destination share a
    repository key and would otherwise be double-counted.

The two remaining steps (decrement source, increment destination) are
plain in-memory map writes that cannot fail for inputs that passed
validation, so the transfer is never partially applied: never debited
from the source without being credited to the destination, or vice
versa. Because the decrement always equals the increment, the asset's
pool total never dips mid-operation, so the liquidity available for
settlements is unchanged by construction and no reserved-liquidity
check is needed.

Both resulting entries share a single updatedAt timestamp, making the
transfer auditable as one logical operation.

When the full source balance is transferred, the source entry is
removed and returned with amount 0, mirroring withdrawLiquidity.

BEHAVIOR / API CONTRACT

Request:

POST /api/v1/liquidity/transfer
{ "from": "anchorA", "to": "anchorB", "asset": "USDC", "amount": 200 }

Success response (200):

{ "from": { "anchor": "anchorA", "asset": "USDC", "amount": 300, "updatedAt": "..." },
  "to":   { "anchor": "anchorB", "asset": "USDC", "amount": 500, "updatedAt": "..." } }

Error responses:

400 BAD_REQUEST              invalid from/to/asset/amount
400 SAME_ANCHOR              from and to are the same anchor
400 INSUFFICIENT_LIQUIDITY   amount exceeds the source balance;
                             no state changes on either side
404 NOT_FOUND                source anchor holds no balance in the
                             asset; no state changes on either side

TESTING

New tests: 14 (8 service-level, 6 route-level).

Service tests cover:

  • successful partial transfer between two anchors, pool total unchanged
  • transfer accumulating onto an existing destination balance
  • transfer creating a destination entry when the target has none
  • removal of the source entry when its full balance is transferred
  • insufficient balance: 400 INSUFFICIENT_LIQUIDITY with BOTH anchors'
    balances and the pool total verified unchanged (atomicity criterion)
  • unknown source: 404 NOT_FOUND with the destination untouched
  • self-transfer rejection with no state change
  • invalid inputs rejected with no state change
  • balances in other assets untouched

Route tests cover:

  • HTTP 200 success path with { from, to } body and unchanged pool total
  • HTTP 400 INSUFFICIENT_LIQUIDITY with both balances unchanged
  • HTTP 404 NOT_FOUND for an unknown source anchor
  • HTTP 400 for invalid input
  • HTTP 400 for a self-transfer

VALIDATION RESULTS

  • Full test suite: 40 suites, 388 tests, all passing (374 pre-existing
    tests unaffected, 14 new tests all green).
  • npm run build (tsc, strict mode): compiles cleanly, no conflicts.
  • npm run lint (eslint): clean.
  • Coverage on new/changed code: 100 percent of statements, functions,
    and lines in liquidityService.ts and routes/liquidity.ts.
  • Live smoke test against a running server verified every acceptance
    criterion: 200 success with the pool total never changing, 400
    INSUFFICIENT_LIQUIDITY leaving both balances and the pool total
    unchanged, 404 for an unknown source, 400 for self-transfer and
    invalid bodies, and the new path present in GET /api/v1/openapi.json.

ACCEPTANCE CRITERIA

  • [done] POST /api/v1/liquidity/transfer moves liquidity between two
    anchors for the same asset in a single atomic operation.
  • [done] An insufficient-balance transfer leaves both the source and
    destination balances completely unchanged.
  • [done] New tests pass for both the success and failure paths.

SECURITY NOTES

Atomicity prevents a partially-applied transfer (debited from source
but not credited to destination, or vice versa) from corrupting total
pool accounting. The 400 SAME_ANCHOR guard closes the one edge case
where in-place math on a shared repository key could create liquidity
out of thin air.

FILES CHANGED (6 modified, 0 created)

src/services/liquidityService.ts        +80
src/routes/liquidity.ts                 +6
src/openapi.ts                          +10
src/services/liquidityService.test.ts   +161
src/routes/liquidity.test.ts            +127
README.md                               +9

Total: 6 files, 393 insertions, 0 deletions.

BRANCH AND COMMIT

  • Branch: feat/liquidity-transfer
  • Commit: c3509f5 feat: add atomic liquidity transfer between anchors
  • Suggested merge target: main
    (AnchorNet-Org/AnchorNet-Backend, or the Mitch5000 fork's main)

HOW TO OPEN THIS PR

This workspace has no GitHub credentials, so run the following from a
clone that does. The branch and commit are already prepared:

git push -u origin feat/liquidity-transfer

then either:

gh pr create --base main --head feat/liquidity-transfer \
  --title "feat: add atomic liquidity transfer between anchors" \
  --body-file PULL_REQUEST.txt

or open https://github.com/Mitch5000/AnchorNet-Backend/compare,
select the feat/liquidity-transfer branch, and paste the TITLE and
BODY sections from this file into the pull request form.

Closes #155

@Jagadeeshftw
Jagadeeshftw merged commit 9a547ab 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 a transferLiquidity(from, to, asset, amount) operation to LiquidityService mirroring an anchor-to-anchor on-chain transfer

2 participants