Skip to main content

Cloudflare Workers Deployment

Complete guide for deploying Loyalteez Cloudflare Workers to production with mainnet configurations.


Overview

Loyalteez infrastructure runs on 4 Cloudflare Workers that handle API requests, wallet creation, reward processing, and gasless transactions. This guide covers deployment, configuration, and troubleshooting.

Workers Overview:

WorkerDomainPurpose
Event Handlerapi.loyalteez.appManual events, Stripe minting, SDK serving
Pregenerationregister.loyalteez.appEmail/OAuth wallet creation via Privy
Reward ProcessorScheduled (Cron)Background reward distribution
Gas Relayerrelayer.loyalteez.appGasless transaction execution

Prerequisites

# Install Wrangler CLI
npm install -g wrangler

# Authenticate with Cloudflare
wrangler login

# Verify authentication
wrangler whoami

Mainnet Configuration

All workers are configured for Soneium Mainnet (Chain ID: 1868).

Network Settings

CHAIN_ID=1868
NETWORK_NAME=soneium
RPC_URL=https://rpc.soneium.org
BLOCK_EXPLORER=https://soneium.blockscout.com/

Contract Addresses (Mainnet - Deployed 2024)

# LTZ Token (EIP-2612 Permit enabled)
LOYALTEEZ_ADDRESS=0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9

# Perk NFT (v6 - no salt requirement)
PERK_NFT_ADDRESS=0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0

# Points Sale (V3 with Permit)
POINTS_SALE_ADDRESS=0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72

# USDC (Mainnet)
USDC_ADDRESS=0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369

Worker 1: Event Handler

Handles main API endpoints and SDK serving.

Routes

  • api.loyalteez.app/*
  • reward.loyalteez.app/*

Endpoints

# Health Check
GET https://api.loyalteez.app/health

# Manual Event Submission
POST https://api.loyalteez.app/loyalteez-api/manual-event

# Stripe Minting
POST https://api.loyalteez.app/loyalteez-api/stripe-mint

# SDK Serving
GET https://api.loyalteez.app/sdk.js

Configuration (wrangler.event-handler.toml)

name = "loyalteez-event-handler"
main = "src/event-handler/index.js"
compatibility_date = "2024-10-01"
compatibility_flags = ["nodejs_compat"]

[vars]
ENVIRONMENT = "production"
CHAIN_ID = "1868"
NETWORK_NAME = "soneium"
RPC_URL = "https://rpc.soneium.org"
BLOCK_EXPLORER = "https://soneium.blockscout.com/"
LOYALTEEZ_ADDRESS = "0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9"
PERK_NFT_ADDRESS = "0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0"
POINTS_SALE_ADDRESS = "0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72"
PRIVY_APP_ID = "your_privy_app_id"
SUPABASE_URL = "https://your-project.supabase.co"

[[routes]]
pattern = "api.loyalteez.app/*"
zone_name = "loyalteez.app"

[[routes]]
pattern = "reward.loyalteez.app/*"
zone_name = "loyalteez.app"

Secrets (Encrypted)

# Set via wrangler secret put
wrangler secret put PRIVY_SECRET --config wrangler.event-handler.toml
wrangler secret put SUPABASE_SECRET_KEY --config wrangler.event-handler.toml
wrangler secret put WALLET_PRIVATE_KEY --config wrangler.event-handler.toml
wrangler secret put STRIPE_SECRET_KEY --config wrangler.event-handler.toml
wrangler secret put RESEND_API_KEY --config wrangler.event-handler.toml

Deploy

cd cloudflare-automation
npx wrangler deploy --config wrangler.event-handler.toml

Health Check Fix (Jan 2025)

Issue: Worker only responded to /loyalteez-api/health, not root /health.

Solution: Add root-level health check before API routing:

// src/event-handler/index.js
if (path === '/health' && request.method === 'GET') {
return successResponse({
status: 'healthy',
service: 'event-handler',
timestamp: new Date().toISOString(),
hostname: url.hostname,
routes: ['api.loyalteez.app/*', 'reward.loyalteez.app/*']
}, request);
}

Worker 2: Pregeneration

Creates Privy wallets for email addresses and OAuth users.

Route

  • register.loyalteez.app/*

Endpoints

# Health Check
GET https://register.loyalteez.app/health

# Wallet Pregeneration
POST https://register.loyalteez.app/loyalteez-api/pregenerate-user

Configuration (wrangler.pregeneration.toml)

name = "loyalteez-pregeneration"
main = "src/pregeneration/index.js"
compatibility_date = "2024-10-01"
compatibility_flags = ["nodejs_compat"]

[vars]
ENVIRONMENT = "production"
PRIVY_APP_ID = "your_privy_app_id"
SUPABASE_PUBLISH_KEY = "sb_publishable_..."

[[routes]]
pattern = "register.loyalteez.app/*"
zone_name = "loyalteez.app"

Secrets

wrangler secret put PRIVY_APP_SECRET --config wrangler.pregeneration.toml
wrangler secret put SUPABASE_URL --config wrangler.pregeneration.toml
wrangler secret put SUPABASE_SECRET_KEY --config wrangler.pregeneration.toml

Deploy

npx wrangler deploy --config wrangler.pregeneration.toml

Worker 3: Reward Processor

Background worker that processes pending rewards every minute.

Type

Scheduled Worker (Cron: */1 * * * *)

Endpoints

# Health Check (via workers.dev URL)
GET https://loyalteez-reward-processor.taylor-cox1199.workers.dev/health

# Manual Processing
POST https://loyalteez-reward-processor.taylor-cox1199.workers.dev/process-pending

# Queue Status
GET https://loyalteez-reward-processor.taylor-cox1199.workers.dev/queue

Configuration (wrangler.reward-processor.toml)

name = "loyalteez-reward-processor"
main = "src/reward-processor/index.js"
compatibility_date = "2023-11-01"

[vars]
ENVIRONMENT = "production"
CHAIN_ID = "1868"
RPC_URL = "https://rpc.soneium.org"
LOYALTEEZ_ADDRESS = "0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9"
PRIVY_APP_ID = "your_privy_app_id"

# Scheduled trigger - every 1 minute
[triggers]
crons = ["*/1 * * * *"]

# Note: No custom route - handled by Event Handler at reward.loyalteez.app

Secrets

wrangler secret put PRIVY_SECRET --config wrangler.reward-processor.toml
wrangler secret put SUPABASE_SECRET_KEY --config wrangler.reward-processor.toml
wrangler secret put WALLET_PRIVATE_KEY --config wrangler.reward-processor.toml
wrangler secret put RESEND_API_KEY --config wrangler.reward-processor.toml

Deploy

npx wrangler deploy --config wrangler.reward-processor.toml

Worker 4: Gas Relayer

Executes gasless transactions on behalf of users.

Route

  • relayer.loyalteez.app/*

Endpoints

# Health Check
GET https://relayer.loyalteez.app/health

# Relay Transaction (requires Privy auth)
POST https://relayer.loyalteez.app/relay

Configuration (wrangler-gas-relayer.toml)

name = "loyalteez-gas-relayer"
main = "src/gas-relayer/worker.js"
compatibility_date = "2024-10-01"

[vars]
VITE_RPC_URL = "https://rpc.soneium.org"
VITE_PRIVY_APP_ID = "your_privy_app_id"
DEV_WALLET_ADDRESS = "0x92627010DF82F6aA16eeCb45C4bD2207140C52A7"
VITE_LOYALTEEZ_ADDRESS = "0x5242b6DB88A72752ac5a54cFe6A7DB8244d743c9"
VITE_PERK_NFT_ADDRESS = "0x6ae30d6Dcf3e75456B6582b057f1Bf98A90F2CA0"
VITE_POINTS_SALE_ADDRESS = "0x5269B83F6A4E31bEdFDf5329DC052FBb661e3c72"

[[kv_namespaces]]
binding = "RELAYER_KV"
id = "your_kv_namespace_id"

[env.production]
name = "loyalteez-gas-relayer"
routes = [
{ pattern = "relayer.loyalteez.app/*", zone_name = "loyalteez.app" }
]

Secrets

wrangler secret put DEV_WALLET_PRIVATE_KEY --config wrangler-gas-relayer.toml
wrangler secret put PRIVY_SECRET --config wrangler-gas-relayer.toml
wrangler secret put VITE_SUPABASE_SECRET_KEY --config wrangler-gas-relayer.toml
wrangler secret put VITE_SUPABASE_PUBLISH_KEY --config wrangler-gas-relayer.toml

Deploy

npx wrangler deploy --config wrangler-gas-relayer.toml --env production

Automated Deployment

Use the provided deployment script to deploy all workers at once.

deploy-workers.sh

#!/bin/bash
# Deploy all workers to production

echo "🚀 Deploying all Cloudflare Workers..."

# 1. Event Handler
echo "📦 [1/4] Deploying Event Handler..."
npx wrangler deploy --config wrangler.event-handler.toml

# 2. Pregeneration
echo "📦 [2/4] Deploying Pregeneration..."
npx wrangler deploy --config wrangler.pregeneration.toml

# 3. Reward Processor
echo "📦 [3/4] Deploying Reward Processor..."
npx wrangler deploy --config wrangler.reward-processor.toml

# 4. Gas Relayer
echo "📦 [4/4] Deploying Gas Relayer..."
npx wrangler deploy --config wrangler-gas-relayer.toml --env production

echo "✅ All workers deployed successfully!"

Usage

cd cloudflare-automation
chmod +x deploy-workers.sh
./deploy-workers.sh

Health Check Verification

After deployment, verify all workers are healthy:

# Event Handler
curl https://api.loyalteez.app/health
# Expected: {"status":"healthy","service":"event-handler",...}

# Pregeneration
curl https://register.loyalteez.app/health
# Expected: {"status":"healthy","service":"oauth-pregeneration",...}

# Gas Relayer
curl https://relayer.loyalteez.app/health
# Expected: {"status":"healthy","service":"gas-relayer",...}

# Reward Processor (workers.dev URL)
curl https://loyalteez-reward-processor.taylor-cox1199.workers.dev/health
# Expected: {"status":"healthy","worker":"reward-processor",...}

Monitoring & Logs

Tail Logs in Real-Time

# Event Handler
wrangler tail loyalteez-event-handler

# Pregeneration
wrangler tail loyalteez-pregeneration

# Reward Processor
wrangler tail loyalteez-reward-processor

# Gas Relayer
wrangler tail loyalteez-gas-relayer

View Deployment History

# List deployments
wrangler deployments list --name loyalteez-event-handler

# View specific deployment
wrangler deployments view <deployment-id>

Check Worker Status

# Worker details
wrangler status --name loyalteez-event-handler

# KV namespace list
wrangler kv:namespace list

# KV namespace keys
wrangler kv:key list --namespace-id <namespace-id>

Troubleshooting

404 on Health Endpoints

Symptoms:

  • curl https://api.loyalteez.app/health returns 404
  • Worker shows as deployed in dashboard

Causes:

  1. Worker only has /loyalteez-api/health, not root /health
  2. DNS propagation delay (5-30 minutes)
  3. Route not configured

Solutions:

  1. Add root-level health check (Fixed in Jan 2025):
// Before API routing
if (path === '/health' && request.method === 'GET') {
return successResponse({
status: 'healthy',
service: 'event-handler',
timestamp: new Date().toISOString()
}, request);
}
  1. Wait for DNS propagation:
# Check DNS
nslookup api.loyalteez.app

# Test via workers.dev URL while waiting
curl https://loyalteez-event-handler.taylor-cox1199.workers.dev/health
  1. Verify routes in dashboard:
  • Cloudflare Dashboard → Workers & Pages → loyalteez-event-handler
  • Check "Domains & Routes" tab

Route Conflicts

Error: "A route with the same pattern already exists"

Solution: Remove duplicate route from wrangler.toml:

# BAD: Both workers define reward.loyalteez.app/*
# In event-handler:
[[routes]]
pattern = "reward.loyalteez.app/*"
# In reward-processor:
[[routes]]
pattern = "reward.loyalteez.app/*" # ❌ CONFLICT!

# GOOD: Only Event Handler serves reward.loyalteez.app
# reward-processor has no custom route (runs on schedule)

Secrets Not Working

Symptoms:

  • Worker deploys but fails at runtime
  • Authentication errors in logs

Solution:

# List existing secrets
wrangler secret list --config wrangler.event-handler.toml

# Set missing secrets
wrangler secret put PRIVY_SECRET --config wrangler.event-handler.toml

# Redeploy after setting secrets
npx wrangler deploy --config wrangler.event-handler.toml

Cron Not Firing

Symptoms:

  • Reward Processor not running
  • No scheduled executions in logs

Solution:

  1. Verify cron trigger in wrangler.toml:
[triggers]
crons = ["*/1 * * * *"] # Every minute
  1. Check scheduled logs:
wrangler tail loyalteez-reward-processor --format pretty
  1. Manually trigger:
curl -X POST https://loyalteez-reward-processor.taylor-cox1199.workers.dev/process-pending

Security Best Practices

1. Never Commit Secrets

# ✅ Good - stored encrypted
wrangler secret put PRIVY_SECRET

# ❌ Bad - visible in code
[vars]
PRIVY_SECRET = "actual_secret_value"

2. Use Environment-Specific Configs

# Production
[env.production]
name = "loyalteez-gas-relayer"
routes = [
{ pattern = "relayer.loyalteez.app/*", zone_name = "loyalteez.app" }
]

# Development
[env.development]
name = "loyalteez-gas-relayer-dev"
# No custom routes - use workers.dev URL

3. Restrict CORS Origins

// Development
const corsHeaders = {
'Access-Control-Allow-Origin': '*'
};

// Production
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://partners.loyalteez.app'
};

4. Rate Limiting

Use KV namespaces for rate limiting:

const rateLimit = async (userAddress, limit = 35) => {
const key = `rate_limit_${userAddress}_${Math.floor(Date.now() / 3600000)}`;
const count = await env.RELAYER_KV.get(key) || 0;

if (count >= limit) {
throw new Error('Rate limit exceeded');
}

await env.RELAYER_KV.put(key, count + 1, { expirationTtl: 3600 });
};

Performance Optimization

1. Use KV for Caching

// Cache DNS lookups
const cachedDNS = await env.DNS_CACHE.get(`dns_${domain}`);
if (cachedDNS) {
return JSON.parse(cachedDNS);
}

const dnsRecord = await fetchDNSTxtRecord(domain);
await env.DNS_CACHE.put(`dns_${domain}`, JSON.stringify(dnsRecord), {
expirationTtl: 3600 // 1 hour
});

2. Batch Database Queries

// BAD: N+1 queries
for (const event of events) {
await database.getEvent(event.id);
}

// GOOD: Single batch query
const eventIds = events.map(e => e.id);
const eventData = await database.batchGetEvents(eventIds);

3. Use Durable Objects Sparingly

Durable Objects are powerful but expensive. Use KV for:

  • Simple key-value storage
  • Rate limiting
  • Caching

Use Durable Objects for:

  • Real-time collaboration
  • Strong consistency requirements
  • Coordination between workers

Rollback Strategy

Rollback to Previous Version

# List deployments
wrangler deployments list --name loyalteez-event-handler

# Rollback to specific version
wrangler rollback --name loyalteez-event-handler --version <version-id>

# Verify rollback
curl https://api.loyalteez.app/health

Gradual Rollout

# Deploy to 10% of traffic first
[env.canary]
name = "loyalteez-event-handler-canary"
routes = [
{ pattern = "api.loyalteez.app/*", zone_name = "loyalteez.app", percentage = 10 }
]


Changelog

January 2025

  • ✅ Added root-level /health endpoint to Event Handler
  • ✅ Verified mainnet configurations (Chain ID: 1868)
  • ✅ Fixed route conflicts (reward.loyalteez.app only on Event Handler)
  • ✅ Updated contract addresses for 2024 mainnet deployment
  • ✅ Created automated deployment script
  • ✅ Documented health check verification

December 2024

  • Initial Cloudflare Workers documentation

Need Help? Join our Discord or email [email protected]