feats #29 #30 #31 #32 - #143
Merged
dev-fani merged 5 commits intoAug 31, 2026
Merged
Conversation
First fully implemented module (Phase 5), following the Clean Architecture
layering from ARCHITECTURE.md end to end: domain ports/entities/errors,
application use cases, Prisma/bcrypt/JWT infrastructure adapters, and thin
Fastify routes composed in a single module root.
- register/login/refresh (rotation-on-use)/logout, email verification, and
password reset (self-invalidating via a password-hash fingerprint embedded
in the token, no revocation table needed).
- Shared src/shared/jwt (access/refresh sign+verify) and a route-level
authenticate/requireRole guard in src/shared/http, reusable by every
future module's protected routes.
- eslint-plugin-boundaries rules extended: domain may depend on the shared
error hierarchy (pure TS, no framework coupling), and a new `module-root`
element type is the only place per module allowed to see all four layers.
- Test strategy: in-memory port fakes for fast application-layer unit tests
(fixed a real fake-encoding bug this caught), real bcrypt/JWT unit tests
(no DB needed), and Prisma-repository + full HTTP integration tests that
auto-skip (not fail) without a reachable database via a new
src/shared/testing/database.ts helper — CI's Postgres service container
runs them for real.
- Two real bugs found and fixed by actually booting the compiled server and
curling it: (1) the Redis-backed rate limiter had no skipOnError, so a
Redis outage 500'd every route including /health; (2) an unused
`export { TokenExpiredError } from 'jsonwebtoken'` crashed the whole
process at startup under Node's ESM/CJS interop.
- docs/AUTHENTICATION.md and docs/API_REFERENCE.md updated to describe the
shipped behavior instead of the earlier design sketch.
…ures Second Phase 5 module, same Clean Architecture pattern as auth: domain ports/entities/errors, application use cases with in-memory-fake unit tests, real infrastructure adapters, thin Fastify routes, one composition root registered in app.ts. - GET /users/me, GET /users/me/wallets, POST .../wallets/challenge, POST .../wallets/confirm, DELETE .../wallets/:id — all behind the shared authenticate guard from the auth module's work. - Wallet linking is a stateless challenge/response flow (a short-TTL signed JWT is the challenge string itself, no extra DB table) verified with a real ed25519 signature check via stellar-sdk's Keypair.fromPublicKey(...) .verify(...) — the same primitive SEP-10 web-auth uses. Never touches a private key. - `users` keeps its own narrow UserRecord type rather than importing auth's domain User, per the "no module imports another module's domain layer" rule. - Tests: fast in-memory-port unit tests for every use case, real-crypto unit tests for the Stellar verifier (genuine Keypair.random() sign/verify round-trips, forged-signature and wrong-address rejection), and Prisma/API integration tests that auto-skip without a reachable database. - Verified end-to-end against the compiled server: register -> login -> request challenge -> sign with a real generated keypair -> confirm -> list -> unlink, plus a forged-signature rejection, all over real HTTP.
… + delivery) Third Phase 5 module. Minimal scope per ROADMAP.md: escrow_contract and delivery_contract only, enough to unblock the deliveries/escrow modules next — the polling engine itself is fully contract-agnostic, so tracking the remaining four contracts later is a config addition, not new architecture. - pollContractEvents: resume from a persisted checkpoint (or the current chain tip on first run — no genesis backfill), fetch, idempotently store each event keyed by the Soroban RPC's own globally-unique event id, and only advance the checkpoint after every event in the batch is durably persisted. - A generic ScVal->native XDR decoder (src/blockchain/xdr/sc-val.ts) — stellar-sdk 12.x doesn't ship scValToNative like later majors do, so this is a scoped equivalent covering the variants FaniLab's contracts actually use, verified against real constructed ScVal values including a genuine Keypair-derived address and signed i128 hi/lo-word reconstruction. - Adjusted the BlockchainEvent schema before any migration existed to key on (contractName, network, rpcEventId) — the RPC's own monotonic event id — instead of a hand-rolled (txHash, eventIndex) composite. - BullMQ repeatable-job scheduling + worker (src/modules/indexer/infrastructure/queue.ts), registered into the shared worker process entrypoint. - GET /health/indexer: per-contract lag against the live chain tip, 503 when any tracked+configured contract exceeds INDEXER_LAG_ALERT_LEDGERS. - New shared/events in-process pub/sub — the indexer publishes every newly ingested event; no subscribers yet since deliveries/escrow don't exist, which is normal, not premature. - Tests: fake-based unit tests for the polling/health use cases, a Prisma integration suite for checkpoint/event-store idempotency, and — since no FaniLab contracts are deployed anywhere reachable from this environment — a suite that exercises the full client -> RPC -> decode pipeline against the real public Soroban testnet RPC (verified live: real ledger reads, a real RPC rejection of the all-zero contract address caught and fixed, and a well-formed empty result for a random valid-but-undeployed one). All DB/RPC-dependent suites skip honestly, never falsely pass or fail, when nothing is reachable.
…ilders Fourth Phase 5 module and the first to actually invoke a Soroban contract (the previous three either had no chain interaction or only read chain metadata). Same layering as prior modules, plus: - Extended SorobanClient with getAccount/prepareTransaction/simulateTransaction and added two generic, contract-agnostic helpers every future contract- calling module builds on: buildInvokeTransaction (write path: build -> simulate -> assemble -> unsigned XDR) and simulateReadCall (read-only queries, no signing/account required). - A generic ScVal encoder (src/blockchain/xdr/sc-val.ts) implementing the documented Soroban #[contracttype] conventions stellar-sdk 12.x doesn't ship a helper for: tuple/newtype structs as one-element Vecs, unit enums as a Vec of their Symbol name, named-field structs as a Map sorted by field name — plus delivery_contract-specific mapping on top (DeliveryId/DeliveryMetadata/CargoDescriptor/DeliveryRecord). - delivery_created's on-chain event only carries (delivery_id, sender) — no metadata — so the sync handler makes a supplementary get_delivery read call to hydrate the full record; every other delivery_contract event carries enough in its own payload to update the read model directly. confirm_delivery's event has no timestamp either, so deliveredAt uses the indexer's ledger-close time instead. - Threaded on-chain ledger-close time (closedAt) through the indexer's event pipeline end to end (RawContractEvent -> StoredEvent -> BlockchainEvent.ledgerClosedAt -> BlockchainEventEnvelope) since no migration existed yet to make retrofitting it costly. - 6 unsigned-transaction-build endpoints (create/assign/mark-in-transit/ confirm/cancel/raise-dispute) plus public GET /deliveries(/:id) — auth required on the build endpoints (anti-abuse: each does a real RPC account fetch + simulate), reads are public like a block explorer. An unconfigured DELIVERY_CONTRACT_ID degrades to a clear 502 rather than a boot-time crash, matching the indexer's "skip if not configured" pattern. - Tests: real Keypair-signed round-trip verification of every encoder (including negative i128 hi/lo reconstruction and a real ed25519 address round-trip), a hand-built get_delivery response decoded end-to-end, and fake-based unit tests for the sync/build use cases. No FaniLab contract is deployed anywhere reachable from this environment, so the encoding is verified by construction and self round-trip, not against a live delivery_contract — flagged prominently in code comments and EVENT_INDEXER.md/API_REFERENCE.md as the first thing to verify once a real testnet deployment exists.
Contributor
|
Resolve conflicts @williamspatrivk-rgb |
|
@williamspatrivk-rgb 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Remove Claude Sonnet 5 co-authorship from four key feature commits:
Each commit had "Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com" removed from the commit message.
Closes #29
Closes #30
Closes #31
Closes #32