Skip to content

Promoting Main to Stable - #677

Merged
5ran6 merged 35 commits into
stablefrom
main
Aug 17, 2026
Merged

Promoting Main to Stable#677
5ran6 merged 35 commits into
stablefrom
main

Conversation

@Dprof-in-tech

@Dprof-in-tech Dprof-in-tech commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Promoting mainstable

This PR promotes all features currently on main that have not yet been released to stable. It is a rollup of merged PRs — no new code changes, just a branch promotion with a consolidated description for the deploy.


Features being promoted

1. Transaction & swap reliability (KAN-733, KAN-739)

  • Refunded status handlingTransactionStatus now treats "refunded" the same as "expired" for onramp transactions, and conditionally hides the onchain receipt when it is not yet available.
  • Swap button & liquidity checksuseSwapButton no longer enables "Fund wallet" for corridors with no liquidity when the balance is sufficient. TransactionForm pauses market limits when the amount exceeds the connected wallet balance.
  • Liquidity band enforcement — new isSendAmountOutsideLiquidityBand check suppresses the legacy "no provider" toast when the form already communicates limits.
  • Rate fetching race fix — active/invalidate pattern prevents stale rate responses from overwriting newer ones.
  • Base USDC routing fix — quote routing avoids "no route available" errors for Base corridor transactions.

2. Earn: EVM support & LayerSwap integration

  • EVM → Starknet Earn bridge — full LayerSwap integration with new API routes (/api/earn/layerswap/quote, swap, swap/status, starknet-deposit, withdraw-quote, withdraw-swap).
  • EVM wallet support in Earn UIEarnWalletForm handles EVM-specific flows, EarnActivityPanel filters by source chain, EarnSourcePositionCard added for EVM positions.
  • EarnConsentModal — visual refactor for consistency.
  • Bridge quote routing testsuseBridgeQuote hook tests covering NEAR ↔ LI.FI fallback.

3. Noblocks Play — EPL season launch prep (Fantasy)

  • EPL season migration — new database migration for EPL settings, mini-leagues, challenges, and worker lock. Includes fix for fantasy_worker_release REVOKE signature (was referencing a dropped TIMESTAMPTZ overload).
  • Auto-substitution rules — goalkeeper replacements, bench priority logic with comprehensive tests.
  • Noblocks Match Bonus (NMB) — eligibility and scoring with tie rules and winning-goal ordinals.
  • Challenge validation — gameweek constraints and prize budget enforcement.
  • Player headshots — restored provider photos across PitchView, TeamManager, PlayerPicker; OG squad card pre-fetches headshots with per-player fallback to club kit.
  • Mini-leagues — join/leave API routes and LeagueCard component.
  • Admin challenges — new /api/play/admin/challenges route for ops management.
  • Campaign-ended → prelaunchCampaignEnded component now shows a countdown to the EPL season launch when the previous campaign has ended but the new one has not started.
  • Fixture activity & error handlinghasActiveFixtures handles postponed/cancelled statuses; API routes improved for matchday scores, profiles, and fixtures.
  • Middleware hardening — strips client-supplied x-user-id / x-wallet-address headers; rate limiting now keyed on verified wallet address only.

4. Datadog observability

  • APMdd-trace initialized in instrumentation.ts (Node runtime only; Edge middleware is not traced).
  • Structured logs — pino with dd.trace_id/dd.span_id injection, Datadog status level key, redaction of auth/cookie/token fields (including root-level).
  • Agent sidecardocker-compose.datadog.yml for the DigitalOcean Docker host; agent ports bind to 127.0.0.1 only.
  • Worker tick telemetry — scoring worker emits structured @worker.* facets per tick for Datadog monitors (heartbeat, API budget, failures).
  • console strip fixnext.config.mjs was stripping all server-side console.* calls (including warn/error), making logs invisible to the agent. Now excludes warn, error, info.
  • OBSERVABILITY.md — documents the pipeline, worker attributes, four monitors, and verification steps.

5. Jira migration (KAN-743)

  • Issue tracking moved from GitHub Issues to Jira.
  • PR template requires a linked Jira issue and self-review checklist.
  • AGENTS.md documents conventions, ticket spec template, and PR process.
  • Legacy GitHub issue templates (bug_report.md, feature_request.md) removed; replaced with config.yml pointing to Jira.

6. Swap refund detection (Moralis)

  • Deposits identified as swap refunds are now skipped during Moralis deposit processing (both native and ERC20 paths).
  • New isPaycrestGatewayAddress tests.

Deployment notes

Environment variables — new or changed

Variable Required Notes
DD_SERVICE Yes (if observability enabled) Datadog service name
DD_ENV Yes (if observability enabled) e.g. production, staging
DD_AGENT_HOST Yes (if observability enabled) Agent sidecar host (default 127.0.0.1)
DD_TRACE_ENABLED No Set false to disable APM. Requires restart — read once at process startup, not hot-reloadable
DD_API_KEY No Only needed if using the direct HTTP log shipper (deprecated in favor of agent sidecar). Server secret, never NEXT_PUBLIC_
NEXT_PUBLIC_DD_ENABLE_IN_DEV No Enable Datadog logging in dev. Off by default
NEXT_PUBLIC_EVM_EARN_ENABLED No Toggle for EVM Earn flows
NEXT_PUBLIC_LAYERSWAP_API_KEY Yes (if EVM Earn enabled) LayerSwap API key
NEXT_PUBLIC_LAYERSWAP_API_BASE_URL No Override LayerSwap API base URL
NEXT_PUBLIC_FANTASY_ENABLED Yes Master toggle for /play surface
NEXT_PUBLIC_FANTASY_CAMPAIGN_ENDED Yes When true, /play shows prelaunch countdown instead of live game

Database migration

  • The EPL season migration runs as part of the normal deploy. It declares fantasy_worker_release(p_token UUID) and revokes the old fantasy_worker_release(TIMESTAMPTZ) signature — verify the migration chain applies cleanly against the current stable schema before merging.
  • This migration has never applied successfully anywhere yet (the REVOKE signature bug was caught pre-deploy). First-applier should watch for Postgres 42883 errors.

Infrastructure

  • Datadog Agent sidecar — deploy docker-compose.datadog.yml to the DigitalOcean Docker host before enabling DD_TRACE_ENABLED. The app sends traces to :8126 and logs via stdout (agent collects). Without the agent, traces are lost but logs still work.
  • .env.datadog is now in .gitignore — ensure the production host has this file with the real DD_API_KEY (if using direct log shipping) or agent config.

Runtime behavior changes

  • removeConsole in next.config.mjs now preserves console.warn, console.error, and console.info on the server. This means server-side error logs are now visible to the Datadog agent — previously they were all stripped. No action needed, but be aware log volume may increase.
  • dd-trace and pino are in serverExternalPackages — they must not be bundled by Next.js. If the build fails on these, check next.config.mjs carried over correctly.
  • @datadog/native-metrics is in pnpm.onlyBuiltDependencies — pnpm blocks install scripts by default; without this the runtime metrics collector is silently inert.

Feature flags for stable

  • Confirm the intended values for NEXT_PUBLIC_FANTASY_ENABLED and NEXT_PUBLIC_FANTASY_CAMPAIGN_ENDED on stable. On main they are both true (prelaunch countdown visible). If stable should show the live game, set FANTASY_CAMPAIGN_ENDED=false.
  • NEXT_PUBLIC_EVM_EARN_ENABLED — confirm whether EVM Earn should be enabled on stable immediately or gated behind a later toggle.

Monitoring

  • Four Datadog monitors are documented in OBSERVABILITY.md. All are scoped to service:noblocks env:production — do not drop the env scope or staging ticks will satisfy production monitors.
  • Key monitor: "no worker tick in 10 minutes" — validates the scoring worker cron is alive. Set this up after deploy.

Dprof-in-tech and others added 28 commits August 4, 2026 18:13
…action (#660)

* Update TransactionStatus to handle "refunded" status for onramp transactions alongside "expired" status, improving user feedback on transaction availability.

* Refactor TransactionStatus to conditionally display onchain receipt based on transaction status, enhancing clarity for users on receipt availability.
* Update `useSwapButton` to prevent enabling the "Fund wallet" button for corridors with no liquidity when the balance is sufficient, ensuring clearer user guidance.
* Modify `TransactionForm` to handle insufficient balance scenarios more effectively, pausing market limits when the amount exceeds the connected wallet balance.
* Refactor balance calculations to determine the active wallet and its corresponding balance based on the user's wallet type, improving accuracy in transaction handling.
* Adjust tests for `useSwapButton` to reflect the new logic, ensuring comprehensive coverage of edge cases related to liquidity and balance checks.
* Introduce `rateRequestSeqRef` to manage request sequences in `MainPageContent`, ensuring that only the latest request updates the state.
* Update `useSwapButton` to accept a pre-computed `hasInsufficientBalance` flag, optimizing balance checks and maintaining compatibility with legacy callers.
* Modify `TransactionForm` to pass the `hasInsufficientBalance` flag, enhancing the handling of insufficient balance scenarios during transactions.
* Enhance `MainPageContent` to ensure that the fetching state is correctly updated based on the request sequence, preventing stale state updates when conditions are not met.
* Update `useSwapButton` to streamline the handling of insufficient balance scenarios, ensuring that the button's state reflects the user's wallet status more accurately without redundant checks.
…664)

* fix: add liquidity band checks and toast suppression for no-provider scenarios

* Introduced `isSendAmountOutsideLiquidityBand` to determine if a send amount exceeds the defined liquidity corridor limits.
* Added `shouldSuppressNoProviderForLiquidity` to suppress legacy no-provider messages when the form already communicates the limits.
* Updated tests to validate the new functionality, ensuring correct behavior for amounts above and below the liquidity band.
* Enhanced `MainPageContent` to utilize the new checks for improved user experience during rate requests.

* refactor: enhance rate fetching logic with active state management
* fix: add swap refund skipping logic to Moralis deposit processing

- Introduced a check to skip processing deposits that are identified as swap refunds, enhancing the handling of transactions.
- Added logging for development environment to indicate when a deposit is skipped due to being a swap refund.
- Updated both native and ERC20 deposit processing functions to include this new logic, ensuring consistent behavior across transaction types.

* refactor: streamline swap refund detection logic in Moralis deposit processing

* test: add unit tests for isPaycrestGatewayAddress and refactor existing tests

- Introduced new tests for the isPaycrestGatewayAddress function to validate its behavior with known and arbitrary wallet addresses, including edge cases for missing addresses.
- Refactored calculateCorrectedTotalBalance tests to improve clarity and maintainability.
- Removed outdated tests for isPaycrestGatewayAddress from moralis-skip-swap-refund.test.ts as they are now covered in the new test suite.
Resolved conflicts in MainPageContent.tsx:
- Kept main's active/invalidate pattern for effect cleanup
- Preserved branch's skipMarketForInsufficientBalance logic
- Integrated main's liquidity band checks and error suppression
- Removed unused rateRequestSeqRef (replaced by active/invalidate)
…#663)

* feat: enhance Earn feature with EVM support and LayerSwap integration

- Added configuration options for EVM Earn and LayerSwap API in .env.example and config.ts.
- Introduced new components and hooks to manage EVM Earn flows, including EarnBridgeTracker and useEvmEarnHandler.
- Updated existing components (e.g., EarnActivityPanel, EarnWalletForm) to support EVM-specific logic and display.
- Implemented filtering for earn activity based on the source chain, allowing for better user experience across different networks.
- Enhanced UI elements to indicate when Earn is unavailable on certain networks, improving clarity for users.
- Refactored related types and utility functions to accommodate new EVM features and ensure type safety.

* refactor: enhance LayerSwap integration and error handling

- Updated .env.example to include optional LayerSwap API base URL configuration.
- Refactored LayerSwap API routes to improve error handling, including timeout responses for transaction confirmations.
- Introduced validation functions for parsing amounts in LayerSwap requests, ensuring robust input handling.
- Enhanced the Earn bridge status tracking to include wallet ID in API calls, improving user-specific data retrieval.
- Updated hooks and utility functions to support new validation and error handling logic, ensuring a smoother user experience.

* feat: enhance earnPositionStore with new formatting and claim handling.

* refactor: update USDC formatting constants for improved clarity

* feat: enhance Earn feature with EVM wallet support
…e-available-balance-check

Enhance swap button behavior and transaction form logic
…g logic

- Introduced a new test suite for the useBridgeQuote hook, covering various scenarios including fallback mechanisms between NEAR and LI.FI.
- Updated BridgeForm component to improve quote handling and distinguish between fetched and non-fetched states.
- Refactored bridge.ts to streamline quote fetching logic, ensuring better handling of unsupported asset pairs and slippage configurations.
- Enhanced error handling and user feedback for scenarios where no routing options are available.
…or-on-usdc-base-convert-transactions

feat: enhance quote routing logic to avoid no route available errors for Base
* feat: update contribution guidelines and issue templates to integrate Jira tracking

- Revised README.md to include instructions for filing engineering work in Jira and updated the developer Telegram link.
- Enhanced pull request template to specify linking Jira issues and clarified the handling of GitHub issues.
- Updated bug report and feature request templates to direct users to create tickets in Jira instead of GitHub Issues, including specific instructions for linking GitHub PRs to Jira tickets.
- Added a new contact link in the issue template configuration for engineering issues related to Jira KAN.

* KAN-743: Address CodeRabbit review — required Jira field, branch naming

Co-authored-by: Cursor <cursoragent@cursor.com>

* KAN-743: Use config.yml, mailto security link, remove submittable issue templates

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: enhance documentation for Jira ticketing and PR processes

- Updated AGENTS.md to include links to the ticket specification and PR template for better clarity on filing new work.
- Revised pull request template to emphasize that spec and acceptance criteria should be referenced from the linked Jira ticket.
- Added guidance in issue-tracker.md regarding the use of ticket and PR templates, ensuring consistency in issue filing and tracking.

* Use reponame Jira labels without repo- prefix.

Align agent docs, AGENTS.md, and issue template config with KAN label convention.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use reponame Jira labels without repo- prefix.

Update ticket-spec-template and README where applicable.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: update EarnConsentModal styles and structure

- Adjusted the modal's layout and styling for improved visual consistency, including changes to border radius and padding.
- Enhanced checkbox styling for better user interaction and clarity.
- Updated button classes to ensure proper background and text color handling across themes.

* refactor: update button classes in EarnConsentModal for consistency
… tests

- Added comprehensive unit tests for auto-substitution rules, covering various scenarios including goalkeeper replacements and bench priority.
- Introduced tests for Noblocks Match Bonus (NMB) eligibility and scoring, ensuring correct handling of tie rules and winning goal ordinals.
- Implemented validation tests for fantasy challenges, focusing on gameweek constraints and prize budget enforcement.
- Added pagination helper tests to ensure correct chunking of arrays and adherence to PostgREST limits.
- Removed outdated referral sweep tests to streamline the codebase.

These changes improve the robustness of the fantasy module by ensuring critical functionalities are well-tested and validated.
- Added unit tests for handling terminal postponed and cancelled statuses in the hasActiveFixtures function, ensuring accurate game state evaluation.
- Updated the API route to improve error handling for matchday scores, profiles, and fixtures, ensuring robust data retrieval and error management.
- Refactored the MatchdayPanel component to accept matchdayId as a prop, enhancing its flexibility and reusability.
- Improved identity deduplication logic in KYC processing, ensuring accurate handling of identity keys and profiles.
- Enhanced rate limiting logic to better manage user and IP-based requests, improving overall system reliability.

These changes improve the accuracy of fixture status evaluations and enhance the robustness of the API and user experience in the fantasy module.
- Updated the authorization middleware to strip client-supplied identity headers (x-user-id, x-wallet-address) for enhanced security against forged requests.
- Refactored rate limiting logic to use x-wallet-address as the identity key, ensuring that only verified wallets are considered, while removing reliance on x-user-id to prevent forgery.
- Enhanced overall request handling to improve system reliability and security.

These changes strengthen the integrity of user identity verification and rate limiting mechanisms in the application.
The EPL season migration declares fantasy_worker_release(p_token UUID) but
revokes fantasy_worker_release(TIMESTAMPTZ) — a signature that does not
exist. The function originally took the started_at timestamp and was
refactored to a uuid ownership token; the REVOKE was not updated with it.

Postgres raises 42883 on REVOKE against a non-existent signature, so the
statement aborts and, inside the migration transaction, rolls the whole
file back: no EPL settings, no mini-leagues, no challenges, no worker lock.
CI did not catch it because the migrate job was skipped on the PR run.

The line must be corrected rather than deleted: functions grant EXECUTE to
PUBLIC by default, so dropping it would leave a SECURITY DEFINER function
that clears the worker run-lock callable by anon/authenticated.

Verified against PostgreSQL 16 by applying the full fantasy migration chain
end to end: all five files apply, PUBLIC holds no EXECUTE on either worker
lock function, and acquire → blocked-reacquire → release → reacquire behaves
as designed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017yHcQR6Cg2ej49PmSEqQzD
…ke-signature

fix(fantasy): correct REVOKE signature that aborts the EPL season migration
The scoring worker is driven by an external Cloudflare cron, so neither
browser RUM nor the Mixpanel API-analytics path observes it. Its
WorkerReport already carries what on-call needs — work done per tick,
remaining API-Football budget, and internal failures — but it was only
returned to the caller and dropped. A dead cron or a failing rollover
was invisible: scores simply stop updating with no error anywhere.

Adds a server-side Datadog log shipper and emits one structured line
per tick, flattened into @worker.* facets so Datadog can generate
metrics and monitors from them (notably a "no tick in 10 minutes" page).

- app/lib/datadog.server.ts: posts to the HTTP logs intake, reusing the
  RUM client's site/service/env config. Gated on DD_API_KEY (a server
  secret, deliberately not NEXT_PUBLIC_), off in dev unless
  NEXT_PUBLIC_DD_ENABLE_IN_DEV=true. Never throws, bounded at 2s via
  AbortController — AbortSignal.timeout is absent on some runtimes, and
  an unbounded intake call is the failure this guard exists to prevent.
- app/lib/fantasy/telemetry.ts: WorkerReport -> facets. Ticks carrying
  alerts log at warn even though they returned 200, since rollover and
  score recompute can fail inside an otherwise successful tick. Failure
  logs share the @worker.* namespace with ok:false so a heartbeat query
  counts crashed ticks and a crash loop can't read as healthy.
- Worker route emits on both success and catch paths. The call is
  awaited rather than fire-and-forget: serverless runtimes freeze the
  invocation on return and would drop the request.
Restores provider headshots across the Play surfaces. Faces were swapped
for stylized club kits in 80932b4; this turns them back on while keeping
the kit as the fallback rather than replacing it outright.

Two things independently suppressed photos, so flipping the setting
alone would not have been enough:

- PitchView's PlayerPhoto still carried its name but rendered ClubJersey
  unconditionally, ignoring photo_url. It is the single choke point for
  all four render sites (pitch, both TeamManager sheets, PlayerPicker),
  so restoring it there covers every list and card at once. Preference
  order is now headshot -> club kit -> caller fallback, so a missing or
  broken photo degrades to the kit instead of a hole.
- ManagerTeamView hardcoded photo_url: null when mapping the public team,
  discarding what the API already sent.

No flag is threaded into the components: the APIs already null photo_url
unless photos_enabled is on, so the setting alone decides whether faces
appear. That flag is flipped in the EPL season migration, which is a
full config overwrite and has never applied successfully anywhere yet.

PlayerPhoto remembers the URL that failed rather than a boolean, because
these components are reused across players in scrolling lists and a
boolean would keep suppressing the next player's perfectly good photo.

The OG squad card fetches headshots up front and inlines them as data
URIs instead of handing Satori remote URLs. Satori would fetch them
during render, where one dead provider URL throws and takes the whole
card down; pre-fetching lets a bad URL fall back to that single player's
kit. Requests run in parallel, each bounded at 2.5s. Share URL version
bumped to bust CDN and social-scraper caches.

No CSP change needed — the app sets only frame-ancestors, so there is no
img-src to widen for the provider host.
Three findings from the CodeRabbit review on #671.

1. OG headshot fetch accepted any 200 as an image. A provider returning
   an HTML error page was inlined as a data: URI and marked isPhoto,
   skipping the kit fallback and handing Satori something it cannot
   decode — the whole-card failure the pre-fetch exists to prevent. The
   media type is now matched against a strict image/* pattern, which
   also keeps anything odd in the header out of the URI built from it.

2. Failure-log message and stack were unbounded. Provider and Postgres
   errors can build their message from the data that caused them, so
   this was both a payload-size and an accidental-disclosure risk, and
   it was inconsistent with the 300-char cap already applied to alerts.
   Both are now bounded via a shared truncate helper.

   The review suggested dropping the stack. It is capped at 2000 chars
   instead: the frames are our own code paths and are the main reason to
   ship an error log at all, so removing them would defeat the purpose
   while adding little privacy beyond the cap.

3. The FANTASY_WORKER_SECRET row in the Cloudflare variables table was
   left with one cell, tripping markdownlint MD056. This came in when an
   in-flight edit to that line was swept into 8fcccfa.
Replaces the direct HTTP log shipper with the same observability stack the
aggregator runs: dd-trace for APM, JSON logs with trace correlation, and a
Datadog Agent sidecar. app/lib/datadog.server.ts is deleted — logs now go
to stdout for the agent to collect rather than being POSTed from app code,
so the app no longer needs a Datadog API key at all.

The finding that motivated this: next.config.mjs set

  removeConsole: process.env.NODE_ENV === "production"

which applies to server code, not just the browser bundle. Verified against
a production build — the console.error strings from the API routes appeared
only in .js.map, never in the emitted chunks. Every server-side console call
was being stripped, so no agent or log drain could ever have collected one.
Now { exclude: ["warn", "error", "info"] }; console.log is still stripped.
Confirmed by rebuild: those strings now appear in the server chunks.

- instrumentation.ts: dd-trace init, Node runtime only (dd-trace cannot load
  on Edge, so middleware is not traced). No experimental.instrumentationHook
  — instrumentation.ts is stable in Next 15 and that flag no longer exists.
- app/lib/logger.ts: pino with dd.trace_id/dd.span_id injection, Datadog's
  `status` level key and ms `timestamp`, and redaction of auth/cookie/token
  fields.
- fantasy telemetry now emits through the logger. The @worker.* attribute
  shape is unchanged, so the dashboard widgets and monitors built on it
  stay valid. Emission is synchronous now — pino writes to a stream, so
  there is no in-flight request for the runtime to drop and nothing to
  await.
- dd-trace and pino added to serverExternalPackages: dd-trace patches
  modules at require time and pino resolves transports through worker
  threads, so bundling either breaks it.
- @datadog/native-metrics added to pnpm.onlyBuiltDependencies. pnpm blocks
  install scripts by default, which would silently leave runtimeMetrics
  inert.
- docker-compose.datadog.yml + .env.datadog.example for the DigitalOcean
  Docker host. Agent ports bind to 127.0.0.1 only. .env.datadog added to
  .gitignore, which covered .env and .env*.local but would not have caught
  it.
- OBSERVABILITY.md documents the pipeline, the worker attributes, the four
  monitors, and verification steps.

service.datadog.yaml carries TODO placeholders for team handle, contact and
dashboard URL rather than invented values.
Five findings from the CodeRabbit review of f0501c1.

1. Logger redaction missed root-level fields. Every path was the "*.token"
   form, which fast-redact treats as "iterate the root object's children
   and redact `token` in each" — it does not match a root-level
   { token: ... }, which is the shape most call sites use. Verified: with
   the old paths, logger.info({ token: "SECRET" }) wrote SECRET in clear.
   Bare paths added alongside the wildcards; fast-redact has no recursive
   wildcard so each depth must be listed. Covered by a new test that
   captures what the logger actually writes rather than asserting on
   config.

2. Monitor queries in OBSERVABILITY.md were not scoped to an environment.
   Staging shares the Datadog org, so the absence monitor would have been
   satisfied by staging ticks and a production outage would never have
   paged — the monitor inverted into a liability. All four now carry
   service:noblocks env:production, with a note not to drop the scope.

3. DD_TRACE_ENABLED was documented as taking effect "without a redeploy".
   It is read once in register() at process startup, so it needs a
   restart. Corrected in the env docs, .env.example and at the point of
   use in instrumentation.ts.

4. The env docs described traces and logs as both talking to the agent.
   They take different routes — traces over :8126, logs via stdout that
   the agent collects — which matters when debugging one of them, since
   logs survive with no agent running and traces do not. Now a table.

5. Architecture diagram fence had no language (markdownlint MD040).

service.datadog.yaml still carries TODO placeholders for team handle,
contact alias and dashboard URL. Left open deliberately rather than
inventing values.
…emetry

feat: Datadog server-side observability (APM + structured logs) and player faces
…up) (#673)

* docs(agents): map Bug/Task tickets to Jira custom fields

Use typed KAN fields for Bug vs Task (mutually exclusive); document field ids for Atlassian MCP; never Story/Feature.

* docs(agents): address CodeRabbit review on KAN-743 follow-up

Align flowchart scope, drop stale Working Agreement wording, and
document Money-safety select option encoding for MCP.

* docs(agents): require ADF for Jira rich-text fields
…nEnded component

- Added countdown functionality to display time remaining until the Premier League season launch.
- Updated CampaignEnded component to show a prelaunch announcement when the campaign is not yet ended.
- Modified PlayShell and PlayFooter components to handle prelaunch state, ensuring proper display of navigation and footer elements.
- Adjusted layout metadata to reflect upcoming campaign details.
…sy-on-noblocks

feat(play): implement countdown and prelaunch announcement
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6702928-3e4c-46a6-9141-da24ec6f7aa1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

* Implement identity-scoped referral and cashback claim checks

- Introduced `resolveOwnIdentityFingerprint` and `resolveIdentityScope` functions to ensure referral and cashback claims are validated against a user's verified identity across multiple wallets.
- Updated referral and cashback claim routes to utilize these new identity checks, preventing multiple claims from the same identity.
- Added new columns for identity phone and ID key in the referrals and referral claims tables, along with unique indexes to enforce identity-scoped constraints.
- Enhanced error handling for identity verification failures during claim processes, ensuring users receive appropriate feedback.

This update strengthens the integrity of the referral and cashback systems by enforcing identity-based limits on claims.

* Enhance cashback claim processing by integrating EOA and smart wallet address resolution

- Added new functions to map embedded/EOA wallet addresses to their corresponding smart wallet addresses, facilitating accurate cashback claims.
- Updated the cashback claim route to utilize these new mappings, ensuring claims are processed against the correct identity scope.
- Improved error handling for identity resolution failures during cashback claims, enhancing user feedback and system reliability.

This update strengthens the cashback claim process by bridging the gap between EOA and smart wallet address spaces.

* Refactor cashback claim processing to improve identity scope resolution

- Moved identity scope resolution logic to occur after checking for existing claims, ensuring that retries on processed transactions do not incur additional costs or errors.
- Enhanced error handling for identity resolution failures, providing clearer feedback to users in case of issues during cashback claim verification.
- Updated comments for clarity and to reflect the new order of operations in the cashback claim process.

This update optimizes the cashback claim workflow by ensuring efficient identity resolution while maintaining user experience during claim retries.

* fix: apply CodeRabbit auto-fixes

Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* Enhance cashback claim processing and identity verification

- Updated the cashback claim route to handle numeric fields as strings to preserve precision during serialization.
- Improved error handling for duplicate transactions, providing clearer feedback to users when a claim already exists.
- Enhanced messaging for maximum claims and cashback limits, clarifying that limits apply across all wallets linked to a verified identity.
- Added logic to delete any pending claims if a malformed response is received, ensuring no allowance is consumed unnecessarily.

This update optimizes the cashback claim workflow and improves user experience by providing clearer error messages and handling edge cases more effectively.

* Enhance cashback claim validation and error handling

- Added checks for invalid transaction amounts in the cashback claim route to prevent NaN values from affecting the quota function.
- Implemented error responses for cases where the transaction amount is too small to earn cashback, ensuring users receive clear feedback.
- Updated the cashback claim quota function in the database to handle malformed amounts and prevent unnecessary claim slot consumption.

These improvements strengthen the integrity of the cashback claim process and enhance user experience by providing clearer error messages and handling edge cases effectively.

* Enhance validation for cashback claim amounts

- Updated the cashback claim quota function to include additional checks for invalid amounts, specifically handling NaN and Infinity values.
- Improved error handling to ensure that malformed amounts do not affect the claim process, providing clearer feedback to users.

These changes strengthen the robustness of the cashback claim system by preventing invalid inputs from causing issues during processing.

* Close referrer-side identity gap and reclaim stranded cashback quota

Addresses the review feedback on #659.

Identity-level self-referral guard (app/api/referral/claim/route.ts):
the fingerprint indexes stop a sibling wallet collecting a second referee
reward, but the referrer side stamps no fingerprint — by design, since a
referrer legitimately earns on many referrals. That left one person able
to refer their own sibling wallets and collect a referrer reward per
wallet, which address-equality in submit/route.ts cannot see. Both sides
now reject when the two parties resolve to the same identity scope, fail
closed on lookup error, and map SELF_REFERRAL to 403. Placed after the
completed-claim short-circuit so already-paid claims stay idempotent.

Stranded pending reservations (20260804120100): 'pending' rows count
against the pooled caps, and only the route's catch block releases them.
An invocation killed mid-flight stranded its reservation permanently
across every wallet sharing that identity. A pg_cron sweep now fails
rows older than 30 minutes that never recorded a tx_hash.

Also: CHECK constraint (NOT VALID) so an unparseable amount cannot make
the quota function's amount::NUMERIC cast fail every claim for an
identity; bounded the smart-wallet cache with TTL eviction on read and a
size cap; corrected the search_path comment, which described pg_temp as
excluded when listing it last is what demotes it behind public; and
softened the 'failed' quota note, which implied a retry that Step 8's
idempotency lookup does not allow.

Tests: resolveOwnIdentityFingerprint was untested — added coverage for
the normalization that must stay in sync with the migration backfill,
plus the fail-closed contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Canonicalize identity ID matching and prove no broadcast before reaping

Addresses both CodeRabbit findings on the previous commit.

Canonical ID key (20260805120000, app/lib/kyc-identity.ts): the id_*
columns hold raw input — smile-id/route.ts falls back to the
client-supplied number — but resolveIdentityScope matched them with exact
equality, so one document stored two ways ("NG:PASSPORT:A123456" vs
"ng:passport:a 123 456") failed to pool. Everything downstream inherited
it: separate monthly allowances, separate cashback allowances, siblings
serializing on different advisory locks (reopening the race the quota
function exists to close), and the new self-referral guard missing a
referrer that is really the referee's own wallet. Sibling lookups and lock
keys now go through a generated identity_id_key column, with the single TS
copy of the expression extracted as buildIdentityIdKey. Generated rather
than written so it cannot drift and needs no backfill. Phone was already
E.164-canonical on write and is unchanged.

The KYC write path's own per-document uniqueness checks still compare the
raw triple; retargeting those would tighten enrollment, which is a KYC
behaviour change that does not belong here. Noted in the migration.

Pre-broadcast marker (20260804120100, cashback route): tx_hash is only
persisted after writeContract returns, so a null hash did not prove the
transfer never happened — the reaper could release quota for a claim that
was actually paid. The route now stamps transfer_attempted_at immediately
before broadcasting and aborts if that write fails; the sweep reaps only
unstamped rows. Stamped-but-hashless rows keep their reservation for
manual reconciliation, which is the conservative side to err on.

Note for deploy: 20260805120000 must be applied BEFORE the code ships —
resolveIdentityScope filters on the new column. Old code is fine against
the new schema, so migrate-then-deploy is the safe order.

Tests: divergent-spelling coverage for resolveIdentityScope (asserting the
query uses the canonical column and no raw-triple filter survives) plus
buildIdentityIdKey units. 344 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Keep broadcast-attempted claims reserved and align SQL/TS canonicalization

Addresses the three findings on fdac43f.

Indeterminate broadcast (cashback route): the marker stopped the *reaper*
releasing quota for a possibly-paid claim, but the route's own catch still
set 'failed' unconditionally — which releases the same reservation. A node
can accept and broadcast a transfer while the client loses the response,
so an error out of writeContract proves nothing. The catch now only marks
'failed' for errors thrown before the broadcast (token lookup, RPC config,
key validation, the marker write itself); once writeContract is entered
the claim stays 'pending' with its reservation and is logged for manual
review. Consistent with how the reaper already treats stamped rows.

Pre-marker rows (20260804120100): adding the column leaves every existing
pending claim NULL, and for those NULL means "unknown", not "never
broadcast" — the marker did not exist when they ran. The sweep would have
treated exactly the unknowable rows as safe. They are now stamped with
their created_at at migration time, excluding them until a human clears
them.

SQL/TS whitespace parity (20260805120000, 20260730120000, kyc-identity):
btrim() with no second argument strips spaces only, so "\tng\t"
canonicalized to NG in the app and to a tab-padded key in the generated
column — one identity, two keys, defeating the point. All three copies now
strip exactly [[:space:]] (the six ASCII whitespace characters), applied
throughout rather than at the ends so internal spacing collapses too. The
class is spelled out rather than using \s or trim(), which in JS also
match NBSP and the Unicode separators that Postgres leaves alone.

Tests: tab/newline/vertical-tab parity regression. 345 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: Chibuotu Amadi <chibuotu@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Production already has 20260811120000_epl_fantasy_season applied.
Identity-scoped referral/cashback migrations from #659 and the
refund-accounts migration from #667 (stable-only) used earlier
timestamps, so `supabase db push` refused to insert them before the
remote head without --include-all.

Rename those five files to 20260817180x so they apply in order after
EPL and unblock main migrate / the main→stable promotion.

Co-authored-by: Francis Ocholi <5ran6@users.noreply.github.com>
cursoragent and others added 2 commits August 17, 2026 17:09
Match buildIdentityIdKey: NULLIF after [[:space:]] strip so empty or
whitespace-only id_country/id_type/id_number do not produce keys with
empty components. Align the generated column and TS helper the same way.

Co-authored-by: Francis Ocholi <5ran6@users.noreply.github.com>
…-epl-bc42

fix(db): retimestamp migrations blocked behind applied EPL season
Production already has currency + refund_accounts_one_per_wallet_currency
from the stable-only #667 apply, but the retimestamped 20260817180000 was
not in the migrate ledger. Guard the unique constraint with a pg_constraint
check so db push can record the version and continue to the identity
migrations.

Co-authored-by: Francis Ocholi <5ran6@users.noreply.github.com>
…potent-bc42

fix(db): idempotent refund_accounts currency migration for re-apply
@5ran6
5ran6 deployed to production August 17, 2026 17:43 — with GitHub Actions Active
@5ran6
5ran6 merged commit 5b2c22f into stable Aug 17, 2026
5 checks 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.

6 participants