DEV Community

Daniel Ioni
Daniel Ioni

Posted on

MyZubster Sepolia ETH E2E: From Cloudflare Routing to On-Chain Payment Verification

MyZubster Sepolia ETH E2E: From Cloudflare Routing to On-Chain Payment Verification
Status: Completed successfully ✅
This article documents the complete end-to-end validation of the MyZubster Marketplace ETH payment flow on Ethereum Sepolia.
The goal was not simply to verify that the Marketplace page loaded. We progressively diagnosed and resolved issues across the entire stack:
React frontend
│
▼
Cloudflare Tunnel
│
├── /api/* ──────────────► Backend :5010
│
└── frontend routes ─────► React :3010
│
▼
Marketplace Operations
│
▼
Payment Intent API
│
▼
MetaMask
│
▼
Ethereum Sepolia
│
▼
Server-side verification
│
▼
PAID

The final result was a successful real Sepolia test transaction with 3 blockchain confirmations and server-side validation of the sender, recipient, amount and transaction status.

  1. Test environment The E2E test used the dedicated Sepolia environment: MyZubster Sepolia — Marketplace Operations The frontend development server was running on: 127.0.0.1:3010

while the Marketplace backend was running on:
127.0.0.1:5010

The Cloudflare Tunnel exposed the public Sepolia hostname:
sepolia.myzubster.com

The test order was:
MyZubster Sepolia ETH E2E Test

with:
Amount: 0.0001 ETH
Network: Ethereum Sepolia
Chain ID: 11155111
Required confirmations: 3

  1. Initial frontend investigation The first problem appeared when the public Marketplace route did not behave as expected. The local React application was running with: react-scripts start

and the relevant process was:
/usr/bin/node
/root/myzubster-sepolia-e2e/frontend/node_modules/react-scripts/scripts/start.js

The process was listening on:
127.0.0.1:3010

We verified:
GET /
→ HTTP 200

GET /marketplace/scambi
→ HTTP 200

The public endpoint also returned:
https://sepolia.myzubster.com/marketplace/scambi
→ HTTP 200

At this point it was necessary to determine whether the problem was the application code, the deployed bundle, or the proxy/cache layer.

  1. Verifying the React route inside the bundle The React application contained the expected route: if (path === '/marketplace/scambi') return ;

The navigation also contained:
window.location.href = '/marketplace/scambi';

We added temporary runtime diagnostics:
console.log('[MYZ-APP-RUNTIME]', {
pathname: window.location.pathname,
path,
marketplaceOps: path === '/marketplace/scambi',
href: window.location.href
});

The browser subsequently reported:
PATH: /marketplace/scambi
marketplaceOps: true

We also checked the public JavaScript bundle and confirmed that it contained the relevant markers:
MYZ-APP-RUNTIME
marketplace/scambi
Paga ETH su Sepolia

This established that the React route itself was present.

  1. Diagnosing the Cloudflare cache The next discovery was the difference between the local and public JavaScript bundles. The public bundle initially returned headers similar to: HTTP/2 200 content-type: application/javascript; charset=utf-8 etag: W/"2c9e23-..." age: 582 cache-control: max-age=14400 cf-cache-status: HIT

The local bundle returned a different ETag:
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8
ETag: W/"2c9ec3-..."

This was a strong indication that Cloudflare was serving a previously cached version of:
/static/js/bundle.js

rather than the newest bundle generated locally.

  1. Cloudflare cache purge We opened the Cloudflare dashboard: Cloudflare Dashboard and navigated to: Caching ↓ Configuration ↓ Purge Cache ↓ Custom Purge ↓ URL

We purged the specific public asset:
https://sepolia.myzubster.com/static/js/bundle.js

Instead of purging the entire website, the purge was targeted at the problematic JavaScript asset.
After the purge, a cache-busting request returned:
cf-cache-status: MISS

and the public ETag became:
W/"2c9ec3-rM6Hsa/kaaF5a08VJlLE6KglTmM"

matching the local bundle.
This confirmed that the public site was now receiving the updated frontend code.

  1. The second and more important issue: API requests were reaching the frontend The next problem appeared when testing: /api/marketplace/orders/mine

Initially, the public response was:
HTTP/2 200
content-type: text/html; charset=utf-8

and the body began with:
<!DOCTYPE html>

This was not an API response.
It was the React application's index.html.
This explained the browser error:
JSON.parse: unexpected character at line 1 column 1

The frontend was attempting to parse an HTML document as JSON.
The request was therefore reaching the wrong service.

  1. Cloudflare Tunnel routing correction The original Sepolia ingress effectively sent the hostname to the React development server:
  2. hostname: sepolia.myzubster.com service: http://127.0.0.1:3010

But the API lived on:
127.0.0.1:5010

The ingress configuration was changed to explicitly separate API traffic from frontend traffic:

The resulting architecture became:
https://sepolia.myzubster.com/api/*
│
▼
Cloudflare Tunnel
│
▼
Backend :5010

and:
https://sepolia.myzubster.com/*
│
▼
Cloudflare Tunnel
│
▼
React frontend :3010

The configuration was validated successfully:
Validating rules from /root/.cloudflared/config.yml
OK

  1. Important operational detail: cloudflared was managed by PM2 An attempt to restart: cloudflared.service

through systemd failed because there was no such systemd unit:
Unit cloudflared.service not found.

We then identified the actual process hierarchy:
systemd
└── PM2
└── cloudflared

PM2 showed:
id name status
4 cloudflared online

with the command:
/usr/local/bin/cloudflared tunnel
--config /root/.cloudflared/config.yml
--protocol http2
run myzubster

Therefore the correct operational restart was:
pm2 restart 4

or, using the process name:
pm2 restart cloudflared

After restarting the PM2-managed process, the public API routing was tested again.

  1. API routing successfully fixed The public request: GET /api/marketplace/orders/mine

then correctly reached the backend.
Without authentication it returned:
HTTP/2 401

with:
{
"success": false,
"message": "Token di autenticazione mancante o non valido"
}

This was actually a positive diagnostic result.
The response was now:
application/json

from the backend rather than:
text/html

from React.
The routing problem was therefore resolved.

  1. Authenticated order retrieval From the authenticated browser session, the same endpoint was called with the MyZubster token: fetch('/api/marketplace/orders/mine', { headers: { Authorization: Bearer ${localStorage.getItem('myzubster-token')} }, cache: 'no-store' })

The backend returned:
{
"success": true,
"orders": [
{
"_id": "6ab870ffc33da1e61579745d",
"quantity": 1,
"status": "ACCEPTED",
"snapshot": {
"title": "MyZubster Sepolia ETH E2E Test",
"price": 0.0001,
"currency": "ETH",
"exchangeMode": "payment"
},
"payment": {
"status": "AWAITING_PAYMENT",
"confirmations": 0
}
}
]
}

The order was therefore correctly available to the authenticated Marketplace Operations page.

  1. Payment intent endpoint A significant discovery was that the frontend was initially attempting to access: /api/marketplace/orders/{ORDER_ID}/payment-intent

That endpoint returned:
404
Cannot GET /api/marketplace/orders/.../payment-intent

The correct endpoint was:
/api/marketplace/orders/{ORDER_ID}/payment/eth-intent

For the test order:
6ab870ffc33da1e61579745d

the request was:
fetch(
'/api/marketplace/orders/6ab870ffc33da1e61579745d/payment/eth-intent',
{
headers: {
Authorization:
Bearer ${localStorage.getItem('myzubster-token')}
},
cache: 'no-store'
}
)

The response was:
HTTP 200
Content-Type: application/json

with:
{
"success": true,
"data": {
"asset": "ETH",
"network": "sepolia",
"chainId": 11155111,
"chainHex": "0xaa36a7",
"expectedSender":
"0xB15707286c4dFC6f642fcDf217419b2665e1847a",
"expectedRecipient":
"0x14d751a54E8617C5A9DF87972dC980827fA91e4d",
"expectedAmountWei": "100000000000000",
"expectedAmountEth": "0.0001",
"minConfirmations": 3,
"paymentStatus": "AWAITING_PAYMENT",
"txId": null,
"testnet": true
}
}

This was the definitive confirmation that the backend was generating a valid Sepolia payment intent.

  1. MetaMask integration The Marketplace UI then presented: Paga ETH su Sepolia · testnet

MetaMask opened on the Sepolia network.
The first attempt exposed another practical testnet issue: insufficient SepoliaETH to cover the transaction and gas fee.
After funding the test wallet with SepoliaETH, the transaction could be prepared successfully.
The expected payment was:
0.0001 SepoliaETH

The transaction had to originate from:
0xB15707286c4dFC6f642fcDf217419b2665e1847a

and be sent to:
0x14d751a54E8617C5A9DF87972dC980827fA91e4d

The MetaMask confirmation screen showed:
Sending
0.0001 SepoliaETH

on:
Sepolia

The destination was represented by the configured MetaMask account.

  1. Transaction submitted After MetaMask confirmation, the Marketplace page changed from: Payment: AWAITING_PAYMENT

to:
Payment: CONFIRMING

The UI displayed the blockchain evidence:
ETH · Ethereum Sepolia testnet

From:
0xB15707...e1847a

To:
0x14d751...A91e4d

Expected amount:
0.0001 ETH

TX:
0xae0b...5af9f94c37

Confirmations:
0

This was the transition from a payment intent to an actual blockchain transaction.

  1. Server-side payment verification The system did not simply mark the order as paid because MetaMask reported a successful transaction. Instead, the backend verified the blockchain evidence server-side. The verification covered the expected: Sender Recipient Amount Transaction status Confirmations

The system required:
3 confirmations

before considering the payment final.
This is an important architectural property of the E2E test:
MetaMask confirmation
≠
automatic PAID status

Instead:
MetaMask
↓
Blockchain transaction
↓
Server-side verification
↓
3 confirmations
↓
PAID

  1. Final successful state After the blockchain reached the required confirmation depth, the Marketplace UI displayed: ✓ Pagamento ETH verificato

with:
Payment: PAID

and:
From:
0xB15707...e1847a

To:
0x14d751...A91e4d

Expected amount:
0.0001 ETH

TX:
0xae0b...5af9f94c37

Confirmations:
3

The final state was therefore:
ORDER
ACCEPTED
│
▼
PAYMENT
PAID
│
▼
ETH
0.0001
│
▼
SEPOLIA
chainId 11155111
│
▼
3 CONFIRMATIONS
│
▼
SERVER-SIDE VERIFIED

  1. What was actually proven The E2E test demonstrated all of the following. Frontend
  2. React application starts correctly.
  3. /marketplace/scambi is recognized by the application.
  4. MarketplaceOpsPage is present in the bundle.
  5. The ETH payment UI is present.
  6. The browser receives the current frontend bundle. CDN / Cloudflare
  7. Cloudflare was initially serving a cached JavaScript bundle.
  8. The specific bundle was purged.
  9. The new bundle became publicly available.
  10. /api/* was separated from frontend traffic.
  11. Cloudflare Tunnel successfully routed API requests to port 5010.
  12. Cloudflare continued routing normal frontend traffic to port 3010. Backend
  13. Authentication works.
  14. /api/marketplace/orders/mine returns JSON for authenticated users.
  15. The test order is retrieved correctly.
  16. The ETH payment intent endpoint exists and responds successfully.
  17. The payment intent contains the correct Sepolia chain information.
  18. The expected sender, recipient and amount are explicitly defined server-side. Blockchain
  19. MetaMask successfully prepared the Sepolia transaction.
  20. The transaction was submitted.
  21. The transaction reached the configured recipient.
  22. The expected amount was transferred.
  23. The transaction received 3 confirmations. Payment state machine The order successfully moved through: ACCEPTED ↓ AWAITING_PAYMENT ↓ CONFIRMING ↓ PAID

This is the complete intended E2E flow.

  1. Technical lessons from the debugging process 17.1 HTTP 200 does not necessarily mean the API works The initial: HTTP 200 Content-Type: text/html

looked superficially successful.
However, the response body was React's index.html.
For API debugging, we therefore need to inspect at least:
HTTP status
Content-Type
final URL
response body

A 200 HTML response is not a successful JSON API response.
17.2 JSON.parse errors can originate upstream
The browser reported:
JSON.parse: unexpected character at line 1 column 1

The actual problem was not necessarily JSON parsing logic itself.
The client was receiving:
<!DOCTYPE html>

instead of:
{
"success": true
}

The error was therefore a symptom of incorrect routing.
17.3 CDN caching must be considered when debugging deployments
The local source code can be correct while the public browser is still receiving an older bundle.
Comparing:
ETag
Age
CF-Cache-Status

was useful for identifying the discrepancy.
A targeted Cloudflare purge resolved the stale asset.
17.4 API and SPA routes need explicit separation
The final routing model is much clearer:
/api/*
→ backend

/*
→ frontend

This prevents an SPA fallback from accidentally converting API failures into HTML responses.

  1. Demonstration links Live Marketplace Operations Open MyZubster Marketplace Operations Sepolia Etherscan Ethereum Sepolia Block Explorer The completed transaction can be searched there using the transaction hash displayed by the MyZubster payment panel: 0xae0b...5af9f94c37

Cloudflare
Cloudflare Dashboard
The relevant configuration is:
myzubster.com
↓
Caching
↓
Configuration
↓
Purge Cache

and the tunnel routing:
sepolia.myzubster.com/api/*
→ 127.0.0.1:5010

sepolia.myzubster.com/*
→ 127.0.0.1:3010

  1. Final E2E checklist Component Result React frontend ✅ /marketplace/scambi route ✅ Marketplace Operations page ✅ Public bundle updated ✅ Cloudflare cache corrected ✅ Cloudflare Tunnel ✅ /api/* routing ✅ Backend authentication ✅ Orders API ✅ ETH payment intent ✅ Sepolia chain ID 11155111 ✅ MetaMask ✅ Sepolia transaction ✅ Correct sender ✅ Correct recipient ✅ Correct amount 0.0001 ETH ✅ Server-side verification ✅ 3 confirmations ✅ Final payment status PAID ✅

Conclusion
The MyZubster Sepolia Marketplace payment flow has now been demonstrated end-to-end on a real Ethereum Sepolia transaction.
The work went beyond a frontend smoke test. We identified and fixed two infrastructure-level problems:

  1. A stale Cloudflare-cached frontend bundle
  2. Incorrect Cloudflare routing of /api/* requests to the React frontend instead of the backend After those issues were resolved, the complete payment lifecycle was exercised: Browser ↓ MyZubster Marketplace ↓ Authenticated order ↓ ETH payment intent ↓ MetaMask ↓ Ethereum Sepolia ↓ On-chain transaction ↓ Server-side verification ↓ 3 confirmations ↓ PAID

The final Marketplace screen explicitly showed:
✓ Pagamento ETH verificato
Payment: PAID
Confirmations: 3

This constitutes a completed Sepolia ETH E2E test of the MyZubster Marketplace payment pipeline.

Top comments (0)