Gasless request submission service for the Vela blockchain platform. Allows users to submit requests (transfers, key association) without holding ETH by delegating gas and service fees to a facilitator. Exposes also additional /settle /claim and /supported endpoints compatible to x402 standards.
The facilitator acts as a gas relay: users sign EIP-712 typed messages and the facilitator submits the transactions on-chain, paying all gas. It exposes:
GET /— Landing page with live runtime info and the endpoint directory. HTML by default, JSON withAccept: application/json.POST /submit— Application-agnostic gasless submission. Accepts anyASSOCIATEKEYorPROCESSrequest signed by the user and submits it to theProcessorEndpointcontract.POST /verify— x402 off-chain payment verification.POST /settle— x402 on-chain settlement: callssubmitRequestFor()and then blocks until the TEE confirms the transfer by emitting the matchingAppEvent.POST /claim— Permissionless claim: callsclaim(tokenAddress, payee)onProcessorEndpoint. Anyone can trigger the claim — funds always go topayee.GET /supported— Returns the list of supported x402 schemes and networks.
┌───────────────────────────────────────┐
│ vela-facilitator │
│ ┌─────────────────────────────────┐ │
│ │ Express HTTP server │ │
│ │ POST /submit POST /verify │ │
│ │ POST /settle POST /claim │ │
│ │ GET /supported │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ @horizen/x402-private-vela- │ │
│ │ fixed (x402 scheme package) │ │
│ │ │ │
│ │ verify.ts settle.ts │ │
│ │ sign.ts client.ts │ │
│ │ server.ts scheme.ts │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ Vela ProcessorEndpoint.sol │ │
│ │ (on-chain smart contract) │ │
│ └─────────────────────────────────┘ │
└───────────────────────────────────────┘
vela-facilitator/
├── src/ # Facilitator Express server
│ ├── index.ts # App entry point
│ ├── config.ts # Environment variable config
│ └── routes/
│ ├── x402.ts # GET /supported, POST /verify, POST /settle
│ ├── submit.ts # POST /submit
│ └── claim.ts # POST /claim
├── packages/
│ ├── x402-private-vela-fixed/ # x402 scheme package (publishable)
│ │ └── src/
│ │ ├── types.ts # Shared types and EIP-712 constants
│ │ ├── verify.ts # Off-chain verification logic
│ │ ├── settle.ts # On-chain settlement logic
│ │ ├── sign.ts # Client-side signing + payload encryption
│ │ ├── client.ts # x402Client registration helper
│ │ ├── server.ts # x402ResourceServer registration helper
│ │ └── scheme.ts # SchemeNetworkFacilitator implementation
│ └── contracts/ # Hardhat mock contracts for local dev + testing
│ └── contracts/
│ ├── Structs.sol
│ ├── MockProcessorEndpoint.sol
│ ├── MockEIP2612Token.sol
│ └── MockTeeAuthenticator.sol
├── mock/ # Anvil lifecycle + contract deployment helpers
├── test/ # Vitest integration + E2E tests
└── ARCHITECTURE.md # Architecture decisions and diagrams
pnpm install
pnpm --filter @vela-facilitator/contracts run compile # compile Solidity mock contracts
pnpm --filter @horizen/x402-private-vela-fixed build # compile scheme package
pnpm build # compile facilitator server# Set environment variables (see Configuration below)
CHAIN_RPC_PROTOCOL=http \
CHAIN_RPC_ADDRESS=127.0.0.1 \
CHAIN_RPC_PORT=8545 \
FACILITATOR_PRIVATE_KEY=0x... \
CHAIN_PROCESSOR_ADDRESS=0x... \
pnpm devcd dockerfiles
cp .env.template .env # fill in the required values
docker compose up -dSee dockerfiles/README.md for standalone Docker build instructions and full configuration details.
Tests start a local Anvil node, deploy mock contracts, and run the full facilitator stack.
pnpm testpnpm dev:smoke runs a small HTTP-level smoke test against an already-running facilitator + chain (no bootstrap, no contract deploy). It mirrors the e2e flow:
GET /supportedPOST /submit—ASSOCIATEKEYwith a fresh P-521 key for the buyerPOST /verify— x402 transfer withassetAmount=0POST /settle— x402 transfer withassetAmount=0
Defaults target the vela dev stack (Anvil + vela/dockerfiles/.env.dev); override any env var to point elsewhere (e.g. the remote dev RPC):
| Variable | Default |
|---|---|
FACILITATOR_URL |
http://localhost:3000 |
CHAIN_RPC_PROTOCOL |
http |
CHAIN_RPC_ADDRESS |
localhost |
CHAIN_RPC_PORT |
8545 |
CHAIN_PROCESSOR_ADDRESS |
deterministic Anvil deploy address |
TOKEN_ADDRESS |
ZeroAddress (fine for assetAmount=0) |
TEE_PUBLIC_KEY_HEX |
dev TEE public key (from vela .env.dev) |
BUYER_PRIVATE_KEY |
Anvil account #3 |
SELLER_ADDRESS |
Anvil account #4 |
Prerequisites (not performed by the script):
ProcessorEndpoint+MockEIP2612Tokendeployed at the configured addresses- Facilitator account funded with ETH
- The target
VELA_NOVA_APPLICATION_IDis deployed on-chain (otherwise/settlereverts withInvalidApplicationId())
pnpm dev:smoke
# or with overrides
FACILITATOR_URL=http://localhost:3000 CHAIN_RPC_PROTOCOL=http CHAIN_RPC_ADDRESS=dev-rpc CHAIN_RPC_PORT=8545 pnpm dev:smokeSee scripts/dev-smoke.ts for the full list of overridable env vars.
All configuration is via environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
CHAIN_RPC_PROTOCOL |
yes | — | Ethereum JSON-RPC protocol (e.g. http, https, ws). Combined with address/port at startup. |
CHAIN_RPC_ADDRESS |
yes | — | Ethereum JSON-RPC host. |
CHAIN_RPC_PORT |
yes | — | Ethereum JSON-RPC port. The chain ID is derived from the resulting URL at startup. |
FACILITATOR_PRIVATE_KEY |
yes | — | Hex private key of the facilitator's Ethereum wallet (pays gas) |
CHAIN_PROCESSOR_ADDRESS |
yes | — | Address of the deployed ProcessorEndpoint contract |
MAX_FEE_VALUE |
no | 0 |
ETH in wei sent as msg.value to cover service fees |
VELA_NOVA_APPLICATION_ID |
no | 1 |
vela-nova application ID, used for x402 payments |
PORT |
no | 3000 |
HTTP server port |
APP_EVENT_POLL_INTERVAL_MS |
no | 2000 |
How often /settle polls for the TEE AppEvent after submission |
APP_EVENT_POLL_TIMEOUT_MS |
no | 60000 |
How long /settle waits before returning tee_processing_timeout |
This project implements the private-vela-fixed x402 payment scheme for all three x402 roles:
- Facilitator: verifies and settles payments by calling
submitRequestFor()onProcessorEndpoint - Client (buyer): builds and signs transfer payloads (EIP-712 + EIP-2612), encrypts with TEE P-521 key
- Resource server (seller): configures
PaymentRequirementswithinvoiceIdinextra
See packages/x402-private-vela-fixed/README.md for full scheme documentation.
- Nonce management: clients query
facilitatorNonces[sender]directly from the contract before signing. The facilitator does not maintain nonce state. - TEE-confirmed settlement: a successful
POST /settlemeans vela-nova's TEE has processed the transfer. AftersubmitRequestFor()is mined, the facilitator recomputes theAppEvent.eventSubTypehash —keccak256(uint32_be(len(invoiceId)) || invoiceId || sender || tokenAddress || amount(32B) || recipient)— and polls the chain until the matchingAppEventis emitted. Because that hash binds all five fields, the seller gets a cryptographic proof that the transfer landed as specified. On timeout,/settlereturnssuccess: falsewitherrorReason: "tee_processing_timeout"(the on-chainrequestIdstays inextensionsfor reconciliation). - EIP-2612 permit: uses
permit()(sequential nonces) for gasless ERC-20 approval, unlike Coinbase's reference scheme which uses EIP-3009transferWithAuthorization. - Payload encryption: the transfer payload is encrypted with the TEE's P-521 ECIES public key. The facilitator cannot read the payload contents — but the
AppEventhash lets it verify the decrypted result anyway.
- FACILITATOR.md — Design spec (section 5.3)
- ProcessorEndpoint.sol — On-chain contract
- vela-nova — Private transfer app (x402 scheme target)
- vela-starterkit — Private transfer app documentation