Building with Chainlink CRE — Lessons from Developing a Decentralized x402 Payment Facilitator
What I learned building three complex workflows at ETHGlobal Buenos Aires
At ETHGlobal Buenos Aires during Devconnect, our team built Wolfy Wallet — a fork of Rabby Wallet with EIP-7702 support, hardware multisig, and most notably, a fully decentralized x402 payment facilitator built using Chainlink CRE.
We walked away with three awards:
🏆 Chainlink — Best Workflow with Chainlink CRE
🏆 Octav — Best App Built Using Octav API
🏆 Zircuit — Best Zircuit Integration
But beyond prizes, the real outcome was learning how CRE behaves when pushed to its limits. We built three sophisticated workflows — verification, settlement, and execution monitoring — and along the way discovered many quirks, pain points, and breakthroughs.
This article is a breakdown of what I learned building a decentralized x402 facilitator using CRE in production conditions.
What is Chainlink CRE?
Chainlink Runtime Environment (CRE) is Chainlink’s all-in-one orchestration layer unlocking institutional-grade smart contracts — data-connected, compliance-ready, privacy-preserving, and interoperable across blockchains and existing systems.
Think of it as a way to write code that can:
- Read from multiple blockchains
- Call external APIs securely
- Do computations off-chain
- Write results back on-chain
- All while being decentralized and trustless
The magic here is that everything runs across multiple independent nodes through something called a Decentralized Oracle Network (DON). When your code makes an API call or reads blockchain data, multiple nodes do it independently, then use Byzantine Fault Tolerant consensus to agree on the result. This means you get the same security guarantees as blockchain transactions, but for everything your code does — not just the on-chain parts.
The SDK currently supports TypeScript and Go, so you can develop your workflow in TypeScript or Go, and CRE compiles it to WebAssembly (WASM) that runs in a sandboxed environment across the DON.
Why I Used CRE: The x402 Problem
The x402 is basically HTTP 402 (Payment Required) but for crypto — it lets you gate access to services behind on-chain payments.
Here’s the problem: x402 requires a “facilitator” if the server doesn’t want to do this job— a trusted service that handles the payment flow between clients and servers. The facilitator has three key responsibilities:
- Verify payments: Confirm that the client’s payment payload meets the server’s declared payment requirements
- Settle payments: Submit validated payments to the blockchain and monitor for confirmation
- Provide responses: Return verification and settlement results to the server, allowing the server to decide whether to fulfill the client’s request
Currently, facilitators are either self-hosted (you run your own server) or hosted facilitator service (trust someone else’s server) e.g. Coinbase’s Facilitator. Both are centralized services.
We wanted a fully decentralized facilitator. That’s where CRE came in.
Our goal was simple:
No servers. No trust assumptions.
A fully decentralized facilitator.
What We Built
I built two CRE workflow files with three total workflow handlers that together form a complete decentralized facilitator “ source code “:
The architecture: Two workflow files (d-verify and d-settlement) with three handlers total - one for verification, one for settlement submission, and one for monitoring execution events.
1. Payment Verification Workflow (d-verify)
Trigger: HTTP trigger — receives payment verification requests from servers
Capabilities Used:
EVMCleintread- Multiple calls to verify EIP-712 signatures, check token balances, and validate nonceshttp- UsessendRequestto POST verification results back to the server
What it does: Validates all x402 payment requirements (protocol version, permit signatures, token balances, deadlines, nonces, amounts, recipient addresses)
Outputs: HTTP POST callback (via sendRequest) to the server with verification result (pass/fail + details)
2. Payment Settlement (d-settlement)
This is actually two workflows in one file that work together:
Workflow 2a: Settlement Submission (HTTP-triggered)
Trigger: HTTP trigger — receives settlement requests after verification succeeds
Capabilities Used:
EVMCleintread: Re-verify payment before settlement (security measure)EVMCleintwrite: - Submit signed report to SettlementReceiver contract on-chain
What it does: Re-verifies the payment, prepares execution data, generates a signed report, and submits it on-chain to be executed
Outputs: Returns JSON (but remember: HTTP triggers don’t actually return data to the caller — you just get an “ACCEPTED” status)
Workflow 2b: Execution Monitoring (EVM log-triggered)
Trigger: EVM log trigger — monitors ExecutionSucceeded and ExecutionFailed events from ExecutionProxy contract
Capabilities Used:
http- UsessendRequestto POST settlement results to the server's callback URL
What it does: Watches the blockchain for settlement execution results from the ExecutionProxy contract and decodes the event data
Outputs: HTTP POST callback (via sendRequest) to the server with settlement result (pass/fail + details: success, transaction, network, errorReason, payer)
The Hard Lessons
Here’s what I learned the hard way building these workflows:
1. HTTP Triggers Don’t Return Data (Big Limitation!)
When you trigger a workflow via HTTP, you cannot return data like a normal API.
You get back a response like this:
json
{
"success": true,
"response": {
"workflow_id": "0x...",
"workflow_execution_id": "0x...",
"status": "ACCEPTED"
}
}That’s it. Just “we got your request.” Your actual workflow output lives in the CRE logs.
The Problem: For x402, the facilitator needs to immediately tell services whether a payment is valid. But we can’t return that data in the HTTP response!
My Workaround: I used Chainlink’s http capability to make HTTP sendRequest callbacks to the service with the verification result. Not perfect because:
- It’s not 100% compliant with x402 architecture
- You can hit quota limits on HTTP calls
- But it was the only solution available
I hope they provide a better solution for this soon.
2. External Calls Must Use Capabilities
You cannot just import a library and make API calls or EVM calls directly (in our case, we were using the x402 library). If you try, you get this unhelpful error:
panic: runtime error: invalid memory address or nil pointer dereferenceWhy: All external interactions (API calls, blockchain reads/writes) must go through CRE’s “capabilities” system. This is how the DON nodes coordinate to execute calls and reach consensus.
Get Eman Herawy’s stories in your inbox
Join Medium for free to get updates from this writer.
The Learning Curve: You have to learn to think in capabilities:
- Want to call an API? → Use
httpcapability - Want to read/write blockchain data? → Use EVMClient Read & Write capabilities
It’s a different mental model than normal backend development.
3. Secrets Configuration Is Confusing
When simulating locally, you need to set up secrets in a specific way:
The Gotcha: The key name and value name in secrets.yaml cannot be the same.
Wrong:
yaml
secretsNames:
EVM_ADDRESS:
- EVM_ADDRESS # ❌ This breaks!Right:
yaml
secretsNames:
EVM_ADDRESS:
- EVM_ADDRESS_ALL # ✅ Different nameThen in your .env:
bash
EVM_ADDRESS_ALL="0x1234..."This tripped me up for hours. The error messages don’t help you figure this out.
4. Always Use Cache Settings
Critical lesson: Always include cacheSettings in HTTP requests for non-idempotent operations (POST, PUT, PATCH, DELETE) to prevent duplicate executions.
The Problem: All nodes in the DON execute HTTP requests independently. For payment settlements or callbacks, this can cause duplicates.
The Solution: cacheSettings creates a shared cache—first node executes the request, others reuse the cached response.
typescript
cacheSettings: {
readFromCache: true, // Enable cache reading
maxAgeMs: 60000, // Accept cached responses up to 60 seconds old
}Key points:
- Set
maxAgeMsslightly longer than your workflow's execution time (e.g., 60000ms) - Caching is best-effort, not guaranteed to prevent all duplicates
- Only works if all nodes construct identical requests (same URL, headers, body)
In our x402 facilitator, we used this for all callback requests to minimize duplicate payment notifications.
5. Simulation Requires — broadcast Flag
When simulating locally, if you want to do any EVM calls (reads or writes), make sure to pass the --broadcast flag:
bash
cre workflow simulate my-workflow --target staging-settings --broadcastWithout it, CRE can’t execute real evm calls in simulation.
6. You Need Whitelisting to Deploy
Currently only whitelisted accounts can deploy to production CRE. You can simulate locally all you want, but to actually run on the DON, you need Early Access approval.
During the hackathon, the Chainlink team deployed our workflow for us, but before that we could only simulate, which was enough to validate the architecture.
7. Concurrency Limits Are Real
You can only run 5 workflows concurrently across your entire account. This means if 10 payment verification requests come in at once, 5 will process and 5 will queue (and wait up to 10 minutes before being dropped). For a payment facilitator serving multiple users, this is tight. We should rethink about our desgin and plan accordingly.
The Service Quotas You Need to Know
CRE has strict limits. Here are the key ones that affected my workflows:
Per-account limits:
- Deploy 1 workflow per minute (burst: 1)
- Maximum 5 concurrent workflow executions across all workflows
- Workflow triggers: 5 per second (burst: 5)
- Max binary size: 100 MB (20 MB compressed)
Per-workflow limits:
- Triggers fire at 1 per 30 seconds (burst: 3)
- Max 10 triggers per workflow
- 5-minute timeout with 100 MB memory
- Maximum 5 concurrent instances per workflow
- 100 KB response size limit
Capability limits:
- 3 concurrent capability calls max
- HTTP: 5 calls per execution, 10 KB response, 100 KB request
- EVM read: 10 calls per execution, 100 blocks for log queries
- EVM write: 5M gas quota, 1 KB report size, 10 target chains
Other limits:
- Logging: 1 KB per line, 1,000 events per execution
- EVM log triggers: max 5 per workflow, monitor up to 5 contract addresses
These limits are tight when you’re building something production-grade. The 5-concurrent-execution limit especially means you need to think about queuing and retry logic in your application layer.
What CRE is Great For
IMO, despite the limitations, CRE is genuinely powerful for certain use cases:
✅ Cross-chain orchestration: Our workflows supported 7+ EVM chains (Ethereum, Base, Arbitrum, Optimism, Polygon, Avalanche, BSC) with one codebase “ more to come”
✅ Moving computational logic to a trustless decentralized off-chain network: Reduce on-chain costs by processing complex logic off-chain with the same security guarantees
✅ Complex workflows: Combining HTTP triggers, EVM reads, EVM writes, and log monitoring in one coherent flow
✅ Institutional-grade security: Every operation goes through BFT consensus — no single point of failure
What CRE Currently Can’t Do (or Does Awkwardly)
❌ Synchronous HTTP responses: Can’t return data directly from HTTP-triggered workflows
❌ High-frequency operations: IMO concurrency and rate limits make it unsuitable for high-throughput scenarios
❌ Easy debugging: Error messages need improvement; hard to debug WASM execution issues
⚠️ Cost at scale: Currently there is no cost estimation for your workflow to help you decide. It will definitely be way less than on-chain, but there’s no clarity yet on how much since you’ll be monthly billed
Final Thoughts
Building a fully decentralized x402 facilitator on CRE wasn’t easy.
But it proved a point:
You can run real payment infrastructure
without servers, trusted hosts, or custodial intermediaries.
CRE is still early. It has sharp edges.
But the architecture is groundbreaking — and worth betting on.
For everyone keep an eye on it. As the platform matures and adds features like confidential computing, the use cases will expand significantly.







