Skip to content

feat(wallets): add GET /wallets/:id/balance endpoint for XLM balance and low-funds warning#521

Open
petahade wants to merge 1 commit into
ANYTECHS:mainfrom
petahade:feat/wallet-balance-endpoint
Open

feat(wallets): add GET /wallets/:id/balance endpoint for XLM balance and low-funds warning#521
petahade wants to merge 1 commit into
ANYTECHS:mainfrom
petahade:feat/wallet-balance-endpoint

Conversation

@petahade

@petahade petahade commented Jun 25, 2026

Copy link
Copy Markdown

Closes #361


Summary

Adds a GET /wallets/:id/balance endpoint that queries the Horizon API for a wallet's native XLM balance and returns a warning flag when funds are below the 2 XLM threshold that commonly causes NFT mint transaction failures.

  • Balance check before minting — callers can gate prepare-mint on warning: false to avoid wasting a Soroban simulation on an underfunded wallet
  • Strict error propagation — unlike the existing getAccountBalance (which silently returns 0 on any error), the new path surfaces typed HTTP errors so the endpoint response always reflects the actual cause of failure
  • Zero regressions — all 64 existing wallet and Stellar tests pass; existing services are unchanged

Changed files

src/stellar/stellar.service.ts

Adds getWalletBalance(address) alongside the existing getAccountBalance:

Error condition HTTP status thrown
Invalid Stellar address (format check) 400 Bad Request
Account not found on Horizon (HTTP 404) 404 Not Found
Circuit breaker open 503 Service Unavailable (existing behaviour)
Any other Horizon failure 502 Bad Gateway

Also exports LOW_BALANCE_THRESHOLD_XLM = 2.0 and the WalletBalanceResult interface so they can be shared across the codebase.

export interface WalletBalanceResult {
  balance: number;   // native XLM, numeric
  warning: boolean;  // true when balance < 2.0 XLM
}

src/wallets/wallet-balance.service.ts (new)

Single-responsibility service that owns the "look up wallet → fetch balance" flow:

async getBalance(walletId: number, userId: number): Promise<WalletBalanceResult>
  1. Fetches wallet from DB (PrismaService) — 404 if not found, wrong owner, or soft-deleted
  2. Delegates to StellarService.getWalletBalance(wallet.address)

Kept separate from WalletManagementService to avoid introducing a new constructor dependency into a class whose test suite instantiates it directly without a mock for StellarService.

src/wallets/wallets.controller.ts

New route injected from WalletBalanceService:

GET /wallets/:id/balance
Detail Value
Auth @Auth() (JWT, inherited from controller)
Ownership @UseGuards(WalletOwnershipGuard)
Rate limit 30 req / 60 s (default throttle key)
Response { balance: number, warning: boolean }

Full Swagger docs with per-status descriptions (200, 400, 401, 404, 502).

src/wallets/wallets.module.ts

Registers and exports WalletBalanceService.


Request / response

GET /wallets/7/balance
Authorization: Bearer <jwt>

200 OK
{ "balance": 5.2, "warning": false }

200 OK  (low funds)
{ "balance": 1.3, "warning": true }

404 Not Found
{ "message": "Stellar account not found for address: G..." }

502 Bad Gateway
{ "message": "Horizon request failed: ..." }

Error handling matrix

Scenario Status Message
Invalid/malformed JWT 401 Unauthorized
Wallet belongs to another user 404 Wallet N not found (ownership guard)
Wallet soft-deleted 404 Wallet N not found
Stellar address format invalid 400 Invalid Stellar address
Account not funded / doesn't exist 404 Stellar account not found for address: G...
Horizon returns non-404 error 502 Horizon request failed: <reason>
Circuit breaker open 503 Service unavailable

Architecture notes

getAccountBalance (existing) swallows all Horizon errors and returns 0 — appropriate for internal callers that treat 0 as "assume unfunded." getWalletBalance (new) is the strict variant for HTTP-facing callers that need to surface the actual failure reason. Both share the same circuit breaker config so they apply the same fault-tolerance policy.

WalletBalanceService is intentionally separate from WalletManagementService to keep constructor dependencies stable across the existing test suite. The service is thin enough that it doesn't warrant a separate module.

…checks

Introduces a balance endpoint that queries Horizon for a wallet's native XLM
balance and returns a low-funds warning flag to help callers avoid NFT mint
failures caused by insufficient balance.

- StellarService.getWalletBalance(): strict-error variant of getAccountBalance
  that throws BadRequestException for invalid addresses, NotFoundException for
  unfunded/non-existent Stellar accounts, and BadGatewayException for Horizon
  network failures; re-exports LOW_BALANCE_THRESHOLD_XLM (2.0) and
  WalletBalanceResult interface
- WalletBalanceService: dedicated service (PrismaService + StellarService) that
  looks up the wallet by id+userId, guards against soft-deleted wallets, then
  delegates to getWalletBalance
- WalletsController: new GET :id/balance route behind @Auth() +
  WalletOwnershipGuard with full Swagger docs and appropriate rate limit
- WalletsModule: registers WalletBalanceService and exports it
@drips-wave

drips-wave Bot commented Jun 25, 2026

Copy link
Copy Markdown

@petahade Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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 Wallet Balance Check Before Minting

1 participant