A TEE-powered options market maker running as a Flare Compute Extension (FCE) on Coston2. The enclave receives RFQ parameters on-chain, prices European call/put options using a GBM Monte Carlo simulation seeded from Flare's on-chain SecureRandom, and returns an EIP-712 signed Quote attested by the TEE's hardware key.
Pricing pipeline (per RFQ):
- Fetch XRP/USD spot from FTSO (
getFeedByIdInWeivia ContractRegistry) - Compute 30-day realized vol from 31 FTSO archive-block snapshots
- Derive per-RFQ MC seed:
sha256(Relay.getRandomNumber() || rfq_id) % 2³² - Run 10k-path vectorised GBM Monte Carlo + Black-Scholes convergence check
- Sign EIP-712 Quote struct via TEE
/signendpoint — hardware-attested key - Publish attestation token + pricing inputs on-chain via Relay contract
Cloned from the fce-sign starter template by Flare Foundation and extended for our
EthGlobal Cannes project. Python implementation only (python/app/).
Note:
.envandconfig/proxy/extension_proxy.tomlare gitignored — never committed.
python/
app/ ← our code: config, abi, handlers, eip712_signer, get_ftso_spot, get_realized_vol, mc_pricer, bs_pricer
base/ ← framework infrastructure (DO NOT MODIFY)
integration/ ← end-to-end test harness (see Step 7)
e2e_rfq_pipeline.py ← 11-step full pipeline runner
steps/ ← individual step scripts for isolated testing
tests/ ← unit tests (16 passing, 2 xfail)
main.py
contract/
InstructionSender.sol ← modified: PRICING/QUOTE OPType, sendInstruction(), getExtensionId()
go/tools/ ← deployment and registration CLI tools (Go — not our code)
| Variable | Value |
|---|---|
INSTRUCTION_SENDER |
0xe578e001cb877849672f24A065060702F3eb1DB5 |
EXTENSION_ID |
0x00...0ea (234 decimal) |
TUNNEL_URL |
https://noisy-noncuratively-dalene.ngrok-free.dev (fixed domain) |
TEE_ID (PRODUCTION) |
0xbc7c58e4D6EA000603273f7F8f3687B03414430d |
code hash |
0x194844cf417dde867073e5ab7199fa4d21fd82b5dbe2bdea8b3d7fc18d10fdc2 |
Steps 0–6 below are already completed for this deployment. Only re-run them if starting fresh from a clean environment.
contract/InstructionSender.sol is modified from the original template:
sendInstruction(bytes payload)replacesupdateKey/sign— sendsPRICING/QUOTEto the TEEgetExtensionId()replaces the auto-getter_extensionId()(private state variable)onlyOwneronsendInstruction— owner is the deployer EOA
All deployment and registration tools are in go/tools/. They interact with
smart contracts and the TEE proxy. Go >= 1.23 required.
- Docker
- ngrok with a fixed domain (or cloudflared, no account required)
- Foundry (
forge,cast) - A funded Coston2 wallet (C2FLR for gas + TEE registration fees)
- Go >= 1.23
cp .env.example .env
# Fill in: PRIVATE_KEY, INITIAL_OWNER, LANGUAGE=python
cp config/proxy/extension_proxy.toml.example config/proxy/extension_proxy.toml
# Fill in the DB credentials for the Coston2 C-chain indexer ([db] section).
# Obtain credentials from the Flare team at the venue.Already done — deployed at
0xe578e001cb877849672f24A065060702F3eb1DB5. Skip unless starting fresh.
Our InstructionSender.sol differs from the template: sendInstruction(bytes)
replaces updateKey/sign, and getExtensionId() replaces the auto-getter.
The deploy-contract tool handles compilation via Foundry.
cd go/tools
go run ./cmd/deploy-contract
# Save printed address to .env as INSTRUCTION_SENDERAlready done — EXTENSION_ID = 234 (0x00...0ea). Skip unless starting fresh.
cd go/tools
go run ./cmd/register-extension
# Save printed ID to .env as EXTENSION_ID
⚠️ Critical:docker compose buildgenerates a new teeID which invalidates PRODUCTION registration (circular dependency — see Troubleshooting). Only rebuild when you genuinely need to deploy new container code. Usedocker compose restartfor all other restarts.
First start (or after a code change that requires a rebuild):
docker compose build
docker compose up -dAll other restarts (safe — preserves teeID):
docker compose restartWait for the proxy to become healthy:
until curl -sf http://localhost:6676/info >/dev/null 2>&1; do sleep 2; done
echo "Extension proxy is ready"Already running — fixed ngrok domain:
https://noisy-noncuratively-dalene.ngrok-free.dev
In a separate terminal:
ngrok http --domain=noisy-noncuratively-dalene.ngrok-free.dev 6676Or with cloudflared (generates a fresh URL each time — must update .env):
cloudflared tunnel --url http://localhost:6676
# Update TUNNEL_URL in .env with the new URLNote: The tunnel must stay running for the entire session. The ngrok fixed domain survives restarts without needing to update
.env.
cd go/tools
& "C:\Program Files\Go\bin\go.exe" run ./cmd/allow-tee-version -p http://localhost:6676Make sure TUNNEL_URL is set correctly in .env.
cd go/tools
& "C:\Program Files\Go\bin\go.exe" run ./cmd/register-tee -p http://localhost:6676 -lThe -l flag enables local/test mode (required on Coston2 — TEE returns a
test attestation token, not a real GCP JWT).
Wait for the TEE to reach PRODUCTION status before running the tests. You can monitor with:
curl http://localhost:6676/info
# "lastSigningPolicyId" must be non-zero for the TEE to process instructionsPrerequisites: Steps 0–6 complete, TEE status = PRODUCTION,
.envhasINSTRUCTION_SENDER,TUNNEL_URL,PRIVATE_KEY.
cd python
python integration/e2e_rfq_pipeline.pySteps and what they confirm:
| Step | What | Expected |
|---|---|---|
| 1 | RPC connect (Coston2, chain ID 114) | eth_chainId = 114 |
| 2 | Wallet balance check | balance > 0 C2FLR |
| 3 | GET /info on ext-proxy |
teeId, codeHash, signingPolicyId non-zero |
| 4 | getExtensionId() on-chain |
matches EXTENSION_ID in .env |
| 5 | ABI-encode RFQ payload | no revert |
| 6 | eth_call simulate sendInstruction |
no revert |
| 7 | sendInstruction real tx → parse TeeInstructionsSent log |
status=1, instructionId emitted |
| 8 | Poll GET /action/result/{id} until status=1 |
TEE processed pricing + signed quote |
| 9 | Inspect raw TEE result JSON | result fields present |
| 10 | ABI-decode result: price_mc, price_bs, delta, vega, seed |
valid 18-decimal values |
| 11 | Assertions: prices positive, MC/BS gap < 15%, delta ∈ (0,1) | all pass |
Steps 1–7 confirmed passing on Coston2 (real tx, gas=172665, status=1, block 29000826). Steps 8–11 require TEE status = PRODUCTION.
cd python
python integration/steps/step_01_connect_rpc.py
python integration/steps/step_07_submit_tx.py
# etc.Each step script is self-contained and imports only what it needs.
cd python
python -m unittest discover -s tests -p 'test_*.py'
# 16 passing, 2 xfail| Service | Container port | Host port |
|---|---|---|
| ext-proxy internal | 6663 | 6675 |
| ext-proxy external | 6664 | 6676 |
| redis | 6379 | 6383 |
The tunnel exposes host port 6676 (ext-proxy external) to the internet.
docker compose build generates a new container image → new signing key → new teeID.
The ext-proxy only forwards results from PRODUCTION teeIDs. Completing registration
requires the ext-proxy to forward the attestation result — so the new teeID cannot
self-register through the proxy it hasn't been admitted into yet.
Symptom: step 8 poll returns HTTP 404; docker compose logs ext-proxy shows
invalid teeID; curl http://localhost:6676/info shows lastSigningPolicyId: 0.
Avoid it: use docker compose restart for all restarts. Only run
docker compose build when you genuinely need to deploy new container code.
Recovery (after an unavoidable rebuild):
cd go/tools
& "C:\Program Files\Go\bin\go.exe" run ./cmd/allow-tee-version -p http://localhost:6676
& "C:\Program Files\Go\bin\go.exe" run ./cmd/register-tee -p http://localhost:6676 -l -command Rap-command Rap skips pre-registration and sends a fresh attestation + availability check.
Wait for the TEE to reach PRODUCTION before retrying the integration tests.
The proxy needs a synced C-chain indexer DB. Check the proxy logs and verify
the DB credentials in config/proxy/extension_proxy.toml:
docker compose logs ext-proxyEnsure your wallet has enough C2FLR for gas + fees.
Try restarting the proxy — it may have missed a signing policy round:
docker compose restartIf that doesn't help, the FDC attestation flow requires active relay providers on Coston2. If no relay infrastructure is running, the availability check won't complete.
If using cloudflared (ephemeral URL), update TUNNEL_URL in .env and restart
the Docker stack, then re-run steps 5–6. With ngrok fixed domain this doesn't apply.
To shut down all local services and prepare for a fresh start:
docker compose downThis stops and removes all containers (redis, ext-proxy, extension-tee).
If you want to completely reset and follow the README from the beginning:
# Remove built images (forces rebuild)
docker compose down --rmi local
# Clear environment state
rm -f .env config/proxy/extension_proxy.tomlAfter a full reset, start again from Step 0.
Note: On-chain state (deployed contracts, registered extensions, registered TEEs) cannot be reset. Each fresh start will deploy a new InstructionSender contract and register a new extension. This is fine for testing — Coston2 is a testnet.