Skip to content

Add V-16 retail spending validation#274

Closed
Emelie-Dev wants to merge 64 commits into
ericmt-98:mainfrom
Emelie-Dev:feat/v16-retail-spending-validation
Closed

Add V-16 retail spending validation#274
Emelie-Dev wants to merge 64 commits into
ericmt-98:mainfrom
Emelie-Dev:feat/v16-retail-spending-validation

Conversation

@Emelie-Dev

Copy link
Copy Markdown
Contributor

closes #231

@drips-wave

drips-wave Bot commented Jun 29, 2026

Copy link
Copy Markdown

@Emelie-Dev 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

0xElyte and others added 28 commits June 29, 2026 13:45
…-98#253)

Wave 6 batch 2 (V-16…V-25). First-person, privacy-first answer on NFC
contactless tap-to-pay familiarity and trust vs PIN / QR / cash, for the
retail vertical (Claim 4 + 6). No names, amounts, or financial details.

Co-authored-by: PC <pc@PCs-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
First-person response for issue ericmt-98#237 (V-22). Validates Claim 4 —
passkey/biometric recovery architecture earns trust over seed phrases.

- Adds ### V-22 section to VALIDATION_DRIPS.md
- Registers V-22 row in WAVE6_RESEARCH_ISSUES.md

Closes ericmt-98#237
Creates docs/validation-reports/ with one structured .md per contributor
response — V-16 (consumer spending, Mexico), V-17 (abarrotes merchant,
Mexico), V-18 (NFC vs QR, Nigeria), V-20 (split payroll, Mexico), V-22
(key recovery, Latin America). Each file includes the full first-person
response, SDF narrative, and a key-findings table for product use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added comprehensive security audit report for issue ericmt-98#257
- Documented clipboard exposure risks on Android (inter-app access, keyboard logging, device sync)
- Provided reproduction steps tested on Android 12-13
- Suggested fix options: automatic cleanup, QR code, encrypted backup, etc.
- Severity: High (control of wallet funds)
- Per audit scope: report only, no code changes implemented

Co-authored-by: Stellar Intel Dev <dev@stellar-intel.dev>
* Security audit: SEC-26 double identity persistence in micopay_users Closes ericmt-98#258

* Security audit: SEC-28 WebView without CSP and remote resource loading Closes ericmt-98#260
Co-authored-by: peace0git <peace0git@gmail.com>
GET /api/v1/cash/request/:id was registered with no preHandler — no auth
and no x402 verification — so any unauthenticated caller who knew or
guessed a request_id received 200 OK leaking merchant_name, amount_mxn,
amount_usdc, htlc_tx_hash and expires_at. IDs are mcr-{8 hex}, an
enumerable ~4.3e9 space, and the 200 vs 404 distinction made the endpoint
an existence oracle throttled only by the global 100 req/min limit.

Gate the endpoint to match the existing GET /api/v1/swaps/:id/status
pattern: x402 micropayment (0.0001 USDC, service cash_request_status)
plus a strict 20 req/min per-route rate limit as defense in depth.

Add a regression test and the SEC-03 security report.
Previously, if fetchReputationRoot() threw (e.g. Soroban RPC unreachable),
the catch block silently swallowed the error and forwarded the proof to the
contract, opening a window where an attacker could submit a fabricated Merkle
root during an outage. The Soroban contract has no duplicate root guard.

- Remove the try/catch from fetchReputationRoot() so network errors propagate
- Change the route handler catch to return 503 instead of a non-fatal warning
- Add three new tests covering the SEC-08 fatal guard for reputation_v1,
  access_credential_v1, and verifying poseidon_preimage is unaffected
- Fix the mock structure (vi.hoisted shared fns) so per-test overrides work
- Update circuit count assertion to include access_credential_v1 (3 total)
- Add security report at docs/security-reports/SEC-08-zk-merkle-root-no-fatal.md

Fixes ericmt-98#215
- Add verifyWebhookSignature() with HMAC-SHA256 and timingSafeEqual
- Integrate verification into POST /defi/ramp/webhook
- Add WEBHOOK_SECRET to config.ts and .env.example
- Include replay protection via x-webhook-timestamp (±5 min)
- Create security report at docs/security-reports/SEC-20-ramp-webhook-sin-verificar.md

Co-authored-by: michael philip <philipmichaelonyekachi>
- getAuthToken() now sends stellar_address (from getPublicKey()) instead
  of username in both /auth/challenge and /auth/token requests, matching
  the backend schema (additionalProperties: false, stellar_address required)
- Add online?: boolean to Offer interface in ExploreMap.tsx — the field was
  already referenced in OfferConfirmData but missing from the type, causing
  tsc build failures
- Add docs/security-reports/SEC-11-auth-mismatch-cliente-servidor.md

Closes ericmt-98#218
Replace direct window.localStorage reads of auth_token, micopay_users,
and token with readJSON from secureStorage.ts so that on native builds
(Capacitor) the Keychain/Keystore path is used, closing the bypass
identified in the SEC-22 audit.

- TradeDetail.tsx: isCurrentUserBuyer → async + readJSON; isBuyer lifted
  to useState/useEffect
- useChatMessages.ts: getAuthToken() helper wraps readJSON; all three
  fetch calls await it
- reportError.ts: readJSON inside fire-and-forget .then().catch() chain
- docs/security-reports/SEC-22-localstorage-secure-storage.md: audit report

Closes ericmt-98#254
Reject malformed secret and htlc query params, gate legacy MICOPAY
scheme to demo builds, and add security report for issue ericmt-98#262.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds completionRate, tradesCompleted, tier, isBusiness fields to Offer
interface and displays them on ExploreMap and DepositMap cards.

Co-Authored-By: SaviourBosan <saviourmichael444@gmail.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds effectiveFeePercent() helper and MAX_EFFECTIVE_FEE_PERCENT=5
constant to api.ts; ExploreMap and DepositMap now display the combined
commission + platform fee and warn when it exceeds the 5% threshold
identified by market validation (V-1/V-3/V-7/V-8).

Co-Authored-By: adepoju2006 <adepoju2006@github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds useWalletBalance hook that queries Horizon directly for the
user's USDC/XLM balances, replacing the previous hardcoded/mock values
in the Home screen.

Co-Authored-By: Olamidepy <olamidepy@github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds KYCScreen.tsx with pending/approved/rejected states, deep-link
callback via Capacitor App, and status persistence via secureStorage.
Wires KYC into App.tsx routing and CETESScreen navigation.
KYC types (startKYC, getKYCStatus) added to api.ts.

Co-Authored-By: dhareymu <dhareymu@github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…polling

Adds 'spei' tab to CETESScreen with 3-step flow: quote request,
CLABE instructions with QR code, and status polling. Integrates
KYC gate, bank account registration, and ramp order management
via new API functions (getRampQuote, createRampOrder, getRampOrderStatus,
registerBankAccount).

Co-Authored-By: Abdulrasaq1515 <abdulrasaq1515@github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ericmt-98

Copy link
Copy Markdown
Owner

Gracias por la validación V-16 🙏 El contenido está muy bien. Solo un detalle de mecánica: el PR quedó en conflicto con main (probablemente docs/VALIDATION_DRIPS.md cambió desde que abriste la rama). ¿Podrías hacer rebase sobre main y resolver el conflicto? Una vez verde lo mergeamos. 🙌

ZuLu0890 and others added 27 commits June 29, 2026 20:22
… state machine (ericmt-98#227)

- Add micopay/backend/src/tests/tradeAuth.test.ts with 9 regression tests
  covering lockTrade (seller-only), revealTrade (seller-only),
  getTradeSecret (seller-only), completeTrade (buyer-only), and
  getTradeById (participants-only) guards
- Add test:trade-auth npm script to micopay/backend/package.json
- Add docs/security-reports/SEC-10-state-machine-regresion.md report

All 9 tests pass. tsc build is clean.
Closes ericmt-98#217

Co-authored-by: ericmt-98 <eric.020.1999@gmail.com>
…ntegrated economy (ericmt-98#241)

Designs and indexes 10 new market/user validation issues that extend the
cash-out validation (V-1…V-15) into the next two verticals from the vision —
retail spending (B) and payroll / dollar inflows (C) — plus open product
questions (passkey recovery, canonical peso, supply bootstrap). Adds two new
SDF claims (6: dollars get spent, 7: dollars flow in).

- New: WAVE6_RESEARCH_ISSUES_BATCH2.md — gap analysis, model, index (ericmt-98#231ericmt-98#240), full issue bodies
- VALIDATION_DRIPS.md — V-16…V-25 placeholder headers for contributor PRs

Issues published to milestone ericmt-98#18 (Wave 6: Market & User Validation).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Closes ericmt-98#213

Co-authored-by: ericmt-98 <eric.020.1999@gmail.com>
…x security vulnerabilities in client-errors telemetry (ericmt-98#275)

Co-authored-by: ericmt-98 <eric.020.1999@gmail.com>
…rce (ericmt-98#297)

The SEC-27 fix merged in ericmt-98#275 defined redactSensitiveData/redactString but
never called them, and read the auth token from a non-existent flat `token`
key — so client error reports still leaked unredacted stack/context and were
sent unauthenticated.

- Apply redactString to message/stack and redactSensitiveData to context
  before posting to /client-errors.
- Read the token from `micopay_users` (buyer/seller.token), the key the app
  actually writes on login, matching the SEC-27 report's recommendation.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Route was registered in both merchants.ts and trades.ts, crashing
Fastify on startup with FST_ERR_DUPLICATED_ROUTE. Kept the version
in trades.ts (has schema validation + service layer). Also updates
.env.testnet to point to the correct Render service URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend:
- Fix systemic safe-area overlap: every fixed-header screen now offsets
  content by calc(header + env(safe-area-inset-top)) so text is no longer
  cut off under the notch (Profile, History, CETES, Explore, ExploreMap,
  Blend, Cashout, MerchantInbox, QRReveal, Terms, Privacy, ChatRoom).
- Profile: deterministic gradient avatar + reputation tier badge + stats
  (operaciones, % completadas, miembro desde).
- Map: testnet builds search from a fixed CDMX origin so seeded demo
  merchants always appear regardless of the device GPS.

Backend:
- GET /users/me now returns trades_completed, completion_rate and
  reputation_tier computed from the trades table.
- getAvailableMerchants returns trades_completed/completion_rate/tier.
- Seed demo merchants with CDMX coordinates + reputation on the in-memory
  store so the discovery map has pins on testnet (idempotent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A plugin-level addHook(preHandler, authMiddleware) was applying auth to
the whole merchants plugin, including the public discovery endpoint —
so the map's unauthenticated /merchants/available call always 401'd and
the discovery map never loaded. Switch to per-route auth on the /me
routes and leave /merchants/available open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… to in-memory)

The runtime pool used connectionTimeoutMillis=2000, too short for the
managed Postgres first handshake on cold boot — so every start failed the
initial connect and silently dropped to the ephemeral in-memory store
(no JOINs/haversine/subqueries → empty map, zeroed reputation, no
persistence). migrate.ts already used a 10s timeout and worked, proving
the DB is reachable.

- Bump connect timeout to 15s, retry the initial connect 5x with backoff.
- Add a pool error handler so idle disconnects don't crash the process.
- Gate the demo-merchant seed on SEED_DEMO_DATA (explicit opt-in) instead
  of the in-memory flag, and run it against the real DB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…provisions

Render service has no preDeployCommand guaranteed, and the previous DB was
deleted. Make migrate.ts export runMigrations() and call it on startup when
a real PostgreSQL connection is present, so attaching a new database creates
the full schema automatically on the next deploy — no manual migrate step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… user

- StatusBar.setOverlaysWebView(false): on Android env(safe-area-inset-top)
  is 0, so the overlaying bar sat on top of fixed headers (title colliding
  with the clock). Reserve the bar so headers never overlap it.
- Offer cards multiplied completion_rate (already 0-100) by 100 → 8900%.
  Show it directly; relabel trades → ops (ExploreMap + DepositMap).
- Map now uses the device's real GPS (dropped the fixed-CDMX testnet
  override) and the demo-merchant seed origin is configurable via
  SEED_ORIGIN_LAT/LNG (default northern CDMX metro), repositioning existing
  demo merchants on redeploy so agents appear in the user's actual zone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…c estimate

/rate/xlm-mxn was throwing 503 (CoinGecko blocks shared cloud IPs), which made
the wallet fall back to a fabricated x20 rate → $200,000 for 10k testnet XLM.

- Send Accept + User-Agent headers (CoinGecko often blocks UA-less requests).
- On upstream failure with no cache, serve a conservative configurable
  estimate (XLM_MXN_FALLBACK, default 2.5) marked source:fallback/stale
  instead of 503 — graceful degradation, no fabricated balance.
- Frontend XLM->MXN fallback corrected from 20 to 2.5 (XLM is ~2-3 MXN).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rflow

- Rate: CoinGecko is blocked from Render's egress IP. Compose a real rate
  from Binance XLMUSDT x er-api USD/MXN (both reachable from cloud) ~3.2,
  with CoinGecko as secondary and a marked estimate only as last resort.
- ExploreMap + DepositMap offer cards: badge rows overflowed right (no
  flex-wrap, flex children without min-w-0). Add flex-wrap + min-w-0 +
  truncate on names + flex-shrink-0/whitespace-nowrap on the price column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ources

Binance is geo-blocked from Render's US egress (451) and CoinGecko 429s, so
the rate stuck on the fallback estimate. Add a source chain that tries
Coinbase XLM-USD then Kraken XLMUSD (both allow US/datacenter IPs) x er-api
USD/MXN, with CoinGecko and Binance as later fallbacks. First valid wins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nce fix

- Pagar tab now opens a Send/Receive hub (/pay); cashout removed from the tab
  (still on Home). Send supports MXNe/USDC/CETES/XLM with QR scan, MAX, memo,
  review step and a real on-chain payment signed with the device key. Receive
  shows address QR + copy. New assets registry + payment service.
- Startup validates the stored session against /users/me and auto re-auths an
  orphaned token via the device key. Fixes blank Profile.
- Home: XLM row shows its own MXN value (no more double symbol); card shows total.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bootstrap i18next in main.tsx, language persisted to localStorage.
Locale files es.json and en.json cover nav, home, pay, send, receive, profile.
Language switcher added to Profile page as ES/EN chips.
BottomNav, Home, PayHub, SendPayment, ReceivePayment, Profile use useTranslation().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…loop

Backend: POST /users/register now updates stellar_address when username
exists (device keypair changed), instead of throwing 409. This unblocks
users whose keypair was overwritten by the old Register bug.

Frontend: getAuthToken auto-registers on 404; Register no longer
overwrites existing keypair; after registration, calls onLoginSuccess
directly (no separate login step required).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Login: add "Restaurar con llave secreta" — user imports their Stellar
  secret key to restore the device keypair and authenticate normally
- Register: no 409 fallback, clear error messages
- getAuthToken: remove auto-register on 404 (caused phantom accounts)
- Backend: revert upsert (was insecure — anyone could claim any username)

The correct recovery path is the same as any real wallet: back up your
secret key at registration, import it to restore access on a new device.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- "Depositar efectivo" → "Comprar con efectivo" (ES/EN i18n)
- DepositRequest: remove hardcoded fake balance, clearer description
- DepositMap: "Entregas en efectivo → Recibes en wallet MXNe",
  "Bloqueando fondos" → "Conectando con el agente"
- DepositChat: banner ahora explica que el AGENTE bloqueó los activos
- DepositQR: eliminar PIN falso (no validaba nada), reemplazar con
  botón "Ya entregué el efectivo al agente", "Esperando encuentro" →
  "Ve al agente y entrégale el efectivo"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…/A-2/A-3)

Backend KYC/quote/order/webhook routes called the real Etherfuse sandbox
instead of in-memory stubs. Several assumptions in SPEI_ANCHOR_PLAN.md
didn't match the real API and are corrected: no standalone bank-account
endpoint (CLABE is captured inside the hosted KYC flow), quote/order
request shapes, and the webhook signing scheme (real HMAC-SHA256 over
RFC 8785 JCS-canonicalized body, one secret per event-type subscription,
not the previously invented static-secret scheme).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cetes/blend/kyc/ramp routes are gated behind config.enableInvestments
(index.ts) which was never set in render.yaml — the routes pushed in
ceb2f3b were 404ing in production because of this, not the deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ericmt-98

Copy link
Copy Markdown
Owner

Thanks for this — V-16 content was already integrated into docs/VALIDATION_DRIPS.md (crediting @Emelie-Dev, this PR) as part of a batch integration. Closing since this branch is also far behind main and its diff now includes ~100 unrelated files. No action needed on your side; your contribution is credited in the doc.

@ericmt-98 ericmt-98 closed this Jul 1, 2026
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.

V-16 · Spending digital dollars at a store (consumer side)