Skip to content

feat(account): add GET /account/balances with CHEQ/USD conversion - #814

Merged
fraseragain merged 6 commits into
developfrom
feat/account-balances-usd
Sep 22, 2026
Merged

fraseragain merged 6 commits into
developfrom
feat/account-balances-usd

Conversation

@fraseragain

@fraseragain fraseragain commented Sep 8, 2026 •

Copy link
Copy Markdown

What

Adds GET /account/balances — the on-chain balance of the authenticated customer's mainnet and testnet payment accounts, in ncheq, CHEQ and USD.

GET /account/balances            // auth: read:account (same as /account, /account/analytics)
{
  "mainnet": {
    "address": "cheqd1...",
    "denom": "ncheq",
    "balance": { "ncheq": "1500000000000", "cheq": 1500 },
    "usd": 3.03
  },
  "testnet": { "address": "cheqd1...", "denom": "ncheq", "balance": { "ncheq": "0", "cheq": 0 }, "usd": 0 },
  "rate": { "cheqUsd": 0.00202, "source": "coingecko", "asOf": "2026-09-08T13:00:00.000Z" }
}

Why

GET /account returns only the payment-account addresses. checkBalance from @cheqd/sdk was used internally (faucet top-up) but never surfaced, and there was no CHEQ→USD lookup anywhere. The Studio portal needs both to show customers their account funding status. Studio owns the price-feed dependency and caching (the same way it wraps Stripe); the portal just displays.

How

  • src/helpers/price.ts — new PriceHelper.getCheqUsdRate(). Fetches the CHEQ/USD spot rate from CoinGecko (coin id cheqd-network, matching cheqd/market-monitoring), caches it in-process for CHEQ_USD_RATE_CACHE_TTL seconds (default 300), 5s request timeout. Never throws — any failure logs and returns null.
  • src/helpers/denom.ts — new. Extracts ncheqToCheq / cheqToNcheq / toSafeFaucetAmount out of account.ts so the pure conversion logic has one home and is unit-testable in isolation.
  • account.ts — getBalances() controller method (inline @openapi, [Account] tag). getTestnetBalanceNcheq generalised to getBalanceNcheq(address, rpcUrl) with an 8s timeout guard (StargateClient exposes no cancellation).
  • Route in app.ts, read:account rule in account-auth.ts, AccountBalancesResponse / AccountNetworkBalance schemas in swagger-api-types.ts (+ regenerated swagger-api.json).
  • Config: COINGECKO_API_URL / COINGECKO_TOKEN_ID / COINGECKO_API_KEY / CHEQ_USD_RATE_CACHE_TTL (constants use the existing parseNumberEnv; documented in README.md + example.env).

Edge cases handled

  • Customer has no payment account on a network → that key is null.
  • A network's RPC query fails → that network's balance is null (the address is still returned); the other network is unaffected.
  • CoinGecko unavailable → rate is null and every usd is null; balances still returned; response is still 200.
  • Balances beyond Number.MAX_SAFE_INTEGER ncheq → conversion via Number(bigint) / 1e9, no throw (covered by a unit test).

Deliberate deviations from existing conventions

  1. Price cache lives inside PriceHelper (its own node-cache), not LocalStore — LocalStore is coupled to PaymentAccountEntity and sits under database/; a market-rate cache doesn't belong there.
  2. Per-network failure isolation + checkBalance timeout — the existing getTestnetBalanceNcheq lets RPC errors propagate. /account/balances isolates them so one RPC outage doesn't blank the other network or the addresses.
  3. Named swagger schema (AccountBalancesResponse) rather than fully inline like the newest /account/faucet block — the response is structured enough that a named schema (like AccountAnalyticsResponse) reads better.

Testing

  • npm run build (swagger + tsc) — clean.
  • npm run test:unit — 137/137 pass (6 new in tests/unit/account/balances.test.ts).
  • prettier --check 'src/**/*.ts' 'tests/**/*.ts' — clean.
  • New Playwright specs under tests/e2e/parallel/account/ (authenticated shape check + testnet address parity with /account, and an unauthenticated 401) — these need a deployed environment, so they run in CI, not locally.
  • Manually exercised end-to-end against a local Postgres-backed instance: returned the testnet address (matching /account), a live balance, and a live CoinGecko rate; with CoinGecko pointed at a dead URL, rate/usd were null, balances still returned, 200, no crash.

Follow-up

Per-account transaction history is tracked in the payment table (PaymentEntity) but has no read endpoint — raised as #815.

🤖 Generated with Claude Code

dubiouspip and others added 3 commits September 8, 2026 16:51
Returns the on-chain balance of the authenticated customer's mainnet and
testnet payment accounts in ncheq, CHEQ and USD. The CHEQ/USD rate is
sourced from CoinGecko (coin id `cheqd-network`) via a new PriceHelper and
cached in-process for CHEQ_USD_RATE_CACHE_TTL seconds.

The endpoint degrades gracefully: if the rate lookup or a network's RPC
endpoint is unavailable it still returns 200, with the affected
usd/rate/balance fields set to null. Per-network balance queries are
isolated so one RPC outage doesn't blank the other network or the
addresses, and checkBalance is bounded by an 8s timeout.

Also extracts the ncheq<->CHEQ helpers (cheqToNcheq, ncheqToCheq,
toSafeFaucetAmount) out of account.ts into helpers/denom.ts, and
generalises getTestnetBalanceNcheq to getBalanceNcheq(address, rpcUrl).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- unit: ncheqToCheq edge cases (zero, sub-CHEQ, > Number.MAX_SAFE_INTEGER)
  and the cheq * rate USD calculation
- e2e: authenticated shape check + testnet address parity with GET /account,
  and an unauthenticated 401 check

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dubiouspip and others added 3 commits September 9, 2026 11:54
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
testMatch used `\.no-auth.spec.ts`, requiring a literal dot before
"no-auth", but every spec is named with a hyphen (e.g.
balances-no-auth.spec.ts). These files were silently falling into the
"Parallel Logged In User Tests" project instead, which injects a
valid x-api-key on every request - so no-auth specs never actually
ran unauthenticated. Also exclude them from that project so they
don't now double-run and fail there once the pattern is corrected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/credential/verify requires the verify:credential:testnet/mainnet
scope (unlike /did/search, /credential-status/search and
/resource/search, which are explicitly registered with
allowUnauthorized: true). verify-no-auth.spec.ts asserted 200 OK
without auth, which was only ever passing because the previous
testMatch regex bug routed it into the authenticated project by
accident. Renamed to verify.spec.ts so it runs under the
authenticated project as intended, and dropped the unauthenticated
storageState override.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@fraseragain
fraseragain merged commit 6a2510e into develop Sep 22, 2026
12 checks passed
@fraseragain
fraseragain deleted the feat/account-balances-usd branch September 22, 2026 18:01
cheqd-bot Bot pushed a commit that referenced this pull request Sep 22, 2026
## [3.17.0-develop.6](3.17.0-develop.5...3.17.0-develop.6) (2026-09-22)

### Features

* **account:** add GET /account/balances with CHEQ/USD conversion ([bfc013e](bfc013e))
* **account:** add GET /account/balances with CHEQ/USD conversion ([#814](#814)) ([6a2510e](6a2510e))

### Bug Fixes

* **test:** correct no-auth spec matching in Playwright projects ([348251a](348251a))
* **test:** move credential verify test out of no-auth suite ([36bae65](36bae65))
@cheqd-bot

cheqd-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 3.17.0-develop.6 🎉

The release is available on:

Your semantic-release bot 📦🚀

cheqd-bot Bot pushed a commit that referenced this pull request Sep 29, 2026
## [3.17.0](3.16.1...3.17.0) (2026-09-29)

### Features

* **account:** add GET /account/balances with CHEQ/USD conversion ([bfc013e](bfc013e))
* **account:** add GET /account/balances with CHEQ/USD conversion ([#814](#814)) ([6a2510e](6a2510e))
* Add Faucet API access headers ([#810](#810)) ([59d9f7e](59d9f7e))
* add guarded Stripe plan migration ([#816](#816)) ([e4e495d](e4e495d))
* Align Basic/Build plans and add testnet faucet API ([#811](#811)) ([7b00c1f](7b00c1f))
* Start Basic plan as trial for new Studio accounts ([#812](#812)) ([52c2249](52c2249))

### Bug Fixes

* Add missing body parameters for faucet topup requests ([#813](#813)) ([a61bfd5](a61bfd5))
* Improve error-handling for faucet requests ([89ed790](89ed790))
* revert semantic-release version for node 20 ([0915874](0915874))
* **test:** correct no-auth spec matching in Playwright projects ([348251a](348251a))
* **test:** move credential verify test out of no-auth suite ([36bae65](36bae65))
@cheqd-bot

cheqd-bot Bot commented Sep 29, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 3.17.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@cheqd-bot cheqd-bot Bot added the released label Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants