Skip to content

Product analytics and observability ​

Fuel Credit uses two separate Cloudflare analytics products:

  • Cloudflare Web Analytics for privacy-first page views and browser performance. It does not accept custom product events.
  • Workers Analytics Engine through the dedicated fuel-credit-product-telemetry Worker for bounded product events.

Custom telemetry is fail-safe and non-blocking. The broad early-access privacy policy is published with operator, global-service, retention, access-role, and privacy-contact details. Telemetry remains disabled until regional consent behavior and automated retention/deletion controls are implemented and verified.

Privacy contract ​

The browser and Worker must never transmit or store:

  • raw wallet addresses;
  • transaction IDs, signatures, balances, collateral or repayment amounts;
  • typed form values;
  • exception messages, stack traces or arbitrary DOM text.

Wallet filtering uses account_tag, a SHA-256 digest of:

text
fuel-credit-analytics:v1:<network>:<lowercase wallet address>

This is pseudonymous data, not anonymous data. Access should be restricted to protocol operators. The tracker honors Do Not Track: 1, uses no cookies, and creates a random per-tab session ID. Mainnet and testnet tags intentionally differ.

Derive the tag for a known address locally:

bash
bun -e 'const [network,address]=process.argv.slice(2); const bytes=new TextEncoder().encode(`fuel-credit-analytics:v1:${network}:${address.toLowerCase()}`); console.log(Buffer.from(await crypto.subtle.digest("SHA-256",bytes)).toString("hex"))' testnet 0xADDRESS

Event dictionary ​

FunnelEvents
Visitapp_loaded, route_view, network_selected
Walletwallet_connect_started, wallet_connected, wallet_disconnected
Fundingbalance_state_detected, faucet_started, faucet_gas_redirected, faucet_claimed, faucet_failed
Quote/openquote_loaded, quote_failed, loan_open_started, loan_open_confirmed, loan_open_failed
Accountaccount_sync_started, account_sync_completed, account_sync_failed
Repaymentrepayment_started, repayment_confirmed, repayment_failed
Maturitymaturity_finalize_started, maturity_finalize_confirmed, maturity_finalize_failed
Claimcollateral_claim_started, collateral_claim_confirmed, collateral_claim_failed
Frictionvalidation_blocked, global_error, unhandled_rejection, rage_click

Errors are reduced locally to an allowlisted category such as user_rejected, insufficient_funds, oracle_unavailable, network_error, maturity_restriction or unknown. A flow_id connects start/success/failure without exposing an onchain transaction ID. Rage clicks require three pointer interactions on the same normalized control within 1.5 seconds and are suppressed for 30 seconds.

Analytics Engine blob layout:

  1. event
  2. network
  3. route
  4. session ID
  5. account tag
  6. target
  7. outcome
  8. error code
  9. flow ID

Double layout: occurrence timestamp, duration in milliseconds. The indexed dimension is <network>:<account_tag|anonymous>.

Activation ​

  1. Publish a privacy disclosure and choose/document a retention period and access owners.

  2. Enable Cloudflare Web Analytics for fuel.credit in Cloudflare Dashboard → Web Analytics and add its generated beacon token to app/index.html (or enable automatic Pages Web Analytics). Do not send custom events to that beacon.

  3. Review workers/telemetry/wrangler.jsonc. Production permits only https://fuel.credit. Add a preview URL only as an explicit comma-separated origin during a controlled preview deployment; never use *.

  4. Deploy the custom endpoint:

    bash
    bun run telemetry:deploy
  5. Change the Worker TELEMETRY_ENABLED variable to true, deploy it, build the app with both variables, and deploy the app:

    bash
    VITE_ANALYTICS_ENABLED=true \
    VITE_ANALYTICS_ENDPOINT=https://events.fuel.credit/v1/events \
    bunx vite build app

Both flags are emergency stops. Disable the Worker first to stop ingestion immediately; rebuild with VITE_ANALYTICS_ENABLED=false to stop client emission.

Queries ​

Use the Cloudflare Analytics Engine SQL API or dashboard. Example funnel by network:

sql
SELECT blob2 AS network, blob1 AS event, count() AS events
FROM fuel_credit_product_events_v1
WHERE timestamp > NOW() - INTERVAL '7' DAY
  AND blob1 IN ('loan_open_started', 'loan_open_confirmed', 'loan_open_failed')
GROUP BY network, event
ORDER BY network, event

Unresolved errors by category:

sql
SELECT blob2 AS network, blob8 AS error_code, count() AS failures
FROM fuel_credit_product_events_v1
WHERE timestamp > NOW() - INTERVAL '24' HOUR
  AND blob7 = 'failed'
GROUP BY network, error_code
ORDER BY failures DESC

Filter a known account after deriving its tag:

sql
SELECT timestamp, blob1 AS event, blob3 AS route, blob7 AS outcome, blob8 AS error_code
FROM fuel_credit_product_events_v1
WHERE blob5 = '<account_tag>'
ORDER BY timestamp DESC
LIMIT 200

Operations ​

  • Review aggregate funnels; do not use account-level data for marketing or profiling.
  • Never log rejected request bodies. The Worker responds only with fixed error codes.
  • The endpoint accepts at most 20 events and 32 KiB per request. Unknown fields, origins, events, routes and unbounded strings are rejected.
  • Revisit the allowlist and event dictionary before adding any event. Prefer a new fixed category over arbitrary metadata.
  • Cloudflare Worker observability is separate from product telemetry and should retain sampled structured service errors only.

Open-source infrastructure on Fuel.