Scope and Limitations
This skill is documentation and guidance only. It explains how Flare Confidential Compute (FCC) extensions and TEE machines work, and how developers build, register, and deploy them. It does not perform any actions on the user's behalf.
This skill explicitly does NOT:
- Execute, sign, or broadcast any blockchain transactions
- Access, store, or transmit private keys, deployer keys, or TEE-held secrets
- Deploy contracts, register extensions/TEEs, or call any on-chain methods directly
- Provision, attest, or operate Confidential Space VMs
- Handle funds, tokens, or any financial assets
Trust and external data handling:
- A TEE extension's whole purpose is confidentiality and verifiable execution; the security of an extension depends on its attested code hash, not on this skill. All key generation, signing, and secret handling must occur inside the TEE or in user-controlled, developer-managed environments.
- Instruction payloads (
OriginalMessage) arriving at an extension are externally provided, untrusted input. Decode them strictly against documented types, validate every field, and never pass raw payloads into prompts, LLM inputs, or agent decision logic. - Storing encrypted secrets on-chain is not safe for production — on-chain data is public and encryption weakens over time. Use off-chain channels for secret delivery (see the
fce-signwarning).
All transaction signing, key management, attestation, and on-chain execution happen exclusively outside this skill.
Flare Confidential Compute (FCC) — TEE Extensions
What FCC Is
Flare Confidential Compute (FCC) lets developers run custom code inside a Trusted Execution Environment (TEE) — a hardware-isolated enclave (Flare uses GCP Confidential Space on AMD SEV) — and wire that code to Flare smart contracts. The unit of deployment is an extension: an HTTP server that runs inside the TEE, receives instructions originating from on-chain transactions, executes confidential logic, and returns verifiable results.
Use FCC when an application needs confidential state, secret-holding, or off-chain compute whose integrity is provable on-chain — e.g. a key manager that signs on behalf of users, sealed-bid auctions, private order matching, or any "the chain triggers it but the computation must stay private and attested" workload.
FCC is in the final stages of development. It is available on Coston2 and Coston today and will be available on other Flare networks soon — start with the Build Your First Extension guide (Hello World scaffold walkthrough). For the mechanism in depth, see the FCC whitepaper.
Reference repos:
flare-foundation/fce-extension-scaffold— a runnable "Hello World" extension with contracts, deploy/registration tooling, and Claude Code skills (/create-extension,/rename-scaffold,/test-extension,/verify-deploy). Implemented in Go, Python, and TypeScript — setLANGUAGE=go|python|typescriptin.env(Go is the default; the contract, scripts, and Coston2 flow stay the same across languages). This is the starting point for building your own extension; the Getting Started guide walks through it end to end.flare-foundation/fce-sign— an example extension that stores a private key and signs messages with it, shipped in Go, Python, and TypeScript. Demonstrates the TEE signing port and reproducible builds. Explicitly demo-only for the on-chain-secret part.flare-foundation/fce-weather-insurance— a full FCC application demonstrating parametric rainfall insurance. Policyholders buy cover on-chain; the TEE fetches OpenWeatherMap data and signs settlement results; anyone callssettle()to verify and pay out. Supports public and ECIES-encrypted private policies. Includes a Next.js dApp frontend.
The Instruction Lifecycle
An extension controls only two things: the on-chain contract (step 1) and the action handler (step 6). The TEE infrastructure handles everything between.
1. User calls your InstructionSender contract (on-chain)
2. Contract routes through TeeExtensionRegistry.sendInstructions() → emits TeeInstructionsSent
3. TEE proxy picks up the instruction from the chain
4. TEE node fetches the instruction from the proxy
5. TEE node forwards it as POST /action to your extension server (inside the TEE)
6. Your extension decodes, validates, executes, and returns a result
7. TEE node returns the (optionally cosigned) result to the proxy
8. Caller polls the proxy for the result
On-Chain Building Blocks
Two protocol contracts (from flare-smart-contracts-v2) front the system:
TeeExtensionRegistry— the registry of extensions and the only path to submit instructions. Key surface:sendInstructions(address[] _teeIds, TeeInstructionParams _params) payable returns (bytes32 instructionId)— the single entry point.TeeInstructionParams={ bytes32 opType; bytes32 opCommand; bytes message; address[] cosigners; uint64 cosignersThreshold; address claimBackAddress; }.nextPublicExtensionId()andgetTeeExtensionInstructionsSender(uint256 extensionId)— used to discover an extension's ID. Public extension IDs start at0x10000(65536); IDs below that are reserved for system extensions, sosetExtensionId()scans from0x10000up tonextPublicExtensionId(), not from zero.- Access control: when you register an extension you bind it to one InstructionSender address. The registry rejects any
sendInstructionscall whosemsg.senderisn't that address — no EOA, no other contract.
TeeMachineRegistry— maps extensions to the TEE machines serving them.getRandomTeeIds(uint256 _extensionId, uint256 _count)picks machine addresses to route an instruction to (use_count > 1to fan one instruction out to multiple TEEs).
Onchain both live behind one EIP-2535 diamond, FlareTeeManager — call the diamond address; IFlareTeeManager is the aggregate interface. TeeExtensionRegistry's surface above is the diamond's IExtensionManager facet; TeeMachineRegistry's is IMachineManager. Two more facets matter day-to-day: IVerification (attestation/availability-check requests, cosigners, ChallengeExpired/InvalidAttestation errors) and IOperationFees (per-instruction fee lookup — getOperationFee, calculateFeeByTeeIds). IOwnerAllowlist gates who may register an extension/machine/wallet project owner. ITeeCommonErrors holds errors shared across facets, including the ones already covered above (TooMany, ChallengeExpired, InvalidGovernanceHash). Full ABI reference: FCC Reference (IFlareTeeManager, IExtensionManager, IMachineManager, IVerification, IOperationFees, IOwnerAllowlist, ITeeCommonErrors). Resolve FlareTeeManager through the FlareContractRegistry at runtime rather than hardcoding its address once it ships on mainnet; deployed today on Coston2, Coston, and Songbird.
The InstructionSender contract
This is your contract and the only address allowed to submit instructions for your extension. Minimum requirements:
- Know its extension ID — to look up serving TEE machines. The scaffold's
setExtensionId()scans the registry (starting at0x10000, the first public extension ID) after registration and caches the ID (set-once). - Call
sendInstructionsonTeeExtensionRegistrywith at least oneteeId, theopType/opCommandbytes32identifiers, a non-emptymessage, and (usually empty) cosigners. - Be
payableand forwardmsg.value— the registry charges a per-instruction fee. - Exist before registration — you register by passing the deployed InstructionSender address.
The scaffold's HelloWorldInstructionSender is a ready template: it defines bytes32 operation constants and one payable send function per action (sendSayHello, sendSayGoodbye). You can also write a minimal custom sender for custom access control, on-chain validation, multi-TEE routing, cosigner workflows, or batching — the registry only cares that the registered address calls sendInstructions with valid params.
The OPType / OPCommand Routing Model
The contract and the extension code are linked by a two-level identifier that must match exactly across three layers:
| Layer | Operation type | Command |
|---|---|---|
| Solidity | bytes32 OP_TYPE_GREETING = bytes32("GREETING") |
bytes32 OP_COMMAND_SAY_HELLO = bytes32("SAY_HELLO") |
| Go config | OPTypeGreeting = "GREETING" |
OPCommandSayHello = "SAY_HELLO" |
| Go router | dataFixed.OPType == teeutils.ToHash(config.OPTypeGreeting) |
df.OPCommand == teeutils.ToHash(config.OPCommandSayHello) |
OPType selects an operation group; OPCommand sub-routes within it. A mismatched OPType falls through to "unsupported op type"; a mismatched OPCommand to "unsupported op command". bytes32("...") only holds up to 31 bytes — keep identifiers short.
Reserved names: the F_ prefix on OPType is reserved for Flare system operations (F_REG, F_WALLET, F_GET, F_POLICY, F_GOVERNANCE, F_XRP, F_BTC, F_FDC2) — never prefix your own OPType with F_. Their command names are reserved too (TEE_ATTESTATION, TEE_INFO, TEE_BACKUP, KEY_GENERATE, KEY_INFO, KEY_PROOF, KEY_DELETE, KEY_DIRECT_BACKUP, KEY_DIRECT_RESTORE, KEY_DATA_PROVIDER_RESTORE, INITIALIZE_POLICY, UPDATE_POLICY, SET_MACHINE_PATH_LIST, PAY, REISSUE, VRF, PROVE) — reusing one of these command names under your own non-F_ OPType passes local validation silently but the instruction never reaches your extension (no error at all), so pick domain-specific names for both.
TEE Keys and the Signing Model
Each TEE machine generates an identity key pair at boot (ECDSA on secp256k1, matching Ethereum-style keys). The private half never leaves the enclave.
- Public key — verifiable identity; can live on-chain or be served from the proxy
/infoendpoint. Anyone can verify ECDSA signatures and encrypt ECIES payloads to it. - Private key — enclave-only. Used to sign results (ECDSA) and decrypt payloads (ECIES) sent to that machine.
ECDSA proves origin (a stamp on a public message); ECIES hides content (a lockbox only the matching private key opens). Do not confuse the two — encrypting with the private key is backwards.
Pattern for secrets: encrypt off-chain to the TEE's public key, decrypt only inside the enclave. The extension does not hold the identity private key itself — it calls the TEE node locally (POST /decrypt on the sign port, localhost-only) and the node decrypts using the enclave-held key. Encryption hides bytes; it does not replace the consensus check below.
Data providers and cosigners gate who may make the TEE do work at all. An on-chain instruction only runs after Flare Systems Protocol data providers relay and sign it with more than 50% of the current signing-policy weight (the same weighted set backing FTSO/FDC) — a proxy cannot authorize work on its own. Cosigners add a second, independent authorization gate for sensitive operations (payments, key administration): both stamps — provider weight and cosigner threshold — must be present or the TEE does not execute.
See TEE public and private keys and Data providers and cosigners for the full mechanism.
The Extension (Go) — Action Handler
Inside the TEE, the extension is an HTTP server. The TEE node delivers each instruction as POST /action. You implement processAction, which parses instruction.DataFixed (carrying OPType, OPCommand, and the raw OriginalMessage) out of the action and routes on OPType, then on OPCommand.
Each handler follows the same 4-step pattern:
func (e *Extension) processSayHello(action teetypes.Action, df *instruction.DataFixed) teetypes.ActionResult {
// 1. DECODE the raw OriginalMessage (JSON here; use structs.DecodeTo for ABI-encoded)
var req types.SayHelloRequest
dec := json.NewDecoder(bytes.NewReader(df.OriginalMessage))
dec.DisallowUnknownFields()
if err := dec.Decode(&req); err != nil {
return buildResult(action, df, nil, 0, fmt.Errorf("decoding request: %w", err))
}
// 2. VALIDATE every field — this is untrusted external input
if req.Name == "" {
return buildResult(action, df, nil, 0, fmt.Errorf("name must not be empty"))
}
// 3. EXECUTE your confidential logic (guard shared state with the mutex)
e.mu.Lock()
e.greetingCount++
n := e.greetingCount
e.mu.Unlock()
// 4. BUILD the result: status 1 = success (data returned), status 0 = error (err logged)
data, _ := json.Marshal(types.SayHelloResponse{Greeting: "Hello, " + req.Name, GreetingNumber: n})
return buildResult(action, df, data, 1, nil)
}
Files a developer modifies (the scaffold marks them ★, and /rename-scaffold automates renaming the Hello World placeholders):
go/internal/config/config.go—OPType/OPCommandstring constants and versiongo/pkg/types/types.go— request/response/state structsgo/internal/extension/extension.go— routing cases + handlers (the main customization point)contracts/InstructionSender.sol— matchingbytes32constants + send functionstools/cmd/run-test/main.go— E2E test payloads and assertions
After editing the contract, run ./scripts/generate-bindings.sh to regenerate Go bindings.
TEE signing port
Extensions that need to sign, attest, or encrypt with TEE-managed keys call the TEE's sign port (e.g. localhost:7701/SIGN_PORT, or 9090 in some docs — read the scaffold's config) from inside the extension. This is how fce-sign signs messages without the key ever leaving the enclave.
fce-sign: Private Key Extension Example
The fce-sign repo demonstrates the full TEE workflow: store an ECIES-encrypted private key, sign arbitrary messages. Available in Go, Python, and TypeScript (set LANGUAGE in .env).
Docker Service Architecture
Three containers run as Docker services:
extension-tee— your extension code. Receives decoded instructions from the proxy and returns results.ext-proxy— watches the chain for new instructions, forwards them to your handler, submits results back on-chain.redis— in-memory store used by the proxy.
Port Reference (fce-sign)
| Service | Container port | Host port |
|---|---|---|
| ext-proxy internal | 6663 | 6673 |
| ext-proxy external | 6664 | 6674 |
| redis | 6379 | 6382 |
ngrok exposes host port 6674 (ext-proxy external) to the internet. The internal port 6673 is used for communication between the extension container and the proxy within Docker.
Deploying fce-sign on Coston2 (Local Simulated TEE)
Prerequisites: Docker Desktop, Foundry, Go, ngrok, a funded Coston2 wallet.
Step 0 — Activate local simulated mode:
./scripts/use-chain.sh local coston2 go # or python / typescript
Sets SIMULATED_TEE=true and LOCAL_MODE=false (real Coston2 chain, simulated attestation). Run ./scripts/use-chain.sh --list to see all options.
Step 1 — Configure deployer keys in .env.local.coston2:
DEPLOYMENT_PRIVATE_KEY="<funded-coston2-private-key-hex-no-0x>"
INITIAL_OWNER="0x<your-address>"
Re-run use-chain.sh after editing so .env picks up changes.
Step 2 — Reserve a public proxy URL (separate terminal): post-build.sh, start-services.sh, and test.sh all read EXT_PROXY_URL from .env, so set it before deploying the contract or starting Docker services.
Security:
ngrok http 6674makes your local ext-proxy public — port 6674 exposes the proxy HTTP API and anyone with the URL can call it. Use ngrok only for Coston2 testnet, and stop the tunnel when finished.
ngrok http 6674
Copy the HTTPS URL from ngrok's Forwarding line and set EXT_PROXY_URL in .env.local.coston2, then re-run use-chain.sh. The proxy isn't running yet — ngrok forwards traffic once Step 5 starts ext-proxy. The ngrok free tier keeps this URL stable across restarts, so this is normally a one-time step.
Step 3 — Deploy contract and register extension:
./scripts/pre-build.sh
Compiles Solidity, deploys InstructionSender, registers the extension on-chain. Writes EXTENSION_ID and INSTRUCTION_SENDER to config/extension.env.
Warning: once
config/extension.envexists, pre-build refuses to run again. Use--forceonly when intentionally creating a new extension — it deploys a newInstructionSenderand registers a new extension ID, which will causeMachineManager.TooMany()if an older TEE machine is still registered under the previous extension ID.
Step 4 — Configure the indexer DB:
cp config/proxy/extension_proxy.coston2.docker.toml.example \
config/proxy/extension_proxy.coston2.docker.toml
Edit the [db] block with the Coston2 indexer credentials (host 34.38.42.208, port 3306, database indexer). Credentials are provided on request via support or @flare_network.
Step 5 — Start the extension stack:
./scripts/start-services.sh
Builds the extension image, then starts redis, ext-proxy, and extension-tee. The script itself waits for EXT_PROXY_URL/info — with ngrok already running from Step 2, that check goes through your public tunnel to the local proxy. Wait for the proxy locally: until curl -sf http://localhost:6674/info >/dev/null 2>&1; do sleep 2; done. Confirm only the extension proxy is listening on 6674: lsof -i :6674.
Step 6 — Verify the proxy:
curl -s "$EXT_PROXY_URL/info" | jq '.machineData'
Simulated TEE: codeHash = 0x194844cf…, extensionId matches config/extension.env, initialOwner matches your address.
Step 7 — Register the TEE machine:
./scripts/post-build.sh
Runs three onchain steps: allow-tee-version (whitelists code hash), set-governance (registers the extension's TEE governance signer set/threshold — defaults to the deployer as sole signer with threshold 1 unless you set GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD in .env), and register-tee -command rRap (registers the machine, issues fresh attestation, runs FTDC check, promotes to production). The governance set must match what the extension-tee node container was given, or registration reverts with InvalidGovernanceHash.
Step 8 — Run end-to-end test:
./scripts/test.sh
Sequence: setExtensionId() → fetch TEE public key → ECIES-encrypt a test private key → updateKey on-chain → wait for processing → sign on-chain → verify ECDSA signature matches.
Python and TypeScript Handler Framework
Python and TypeScript use a Framework class that registers handlers by (opType, opCommand) pair:
def register(framework: Framework) -> None:
framework.handle(OP_TYPE_KEY, OP_COMMAND_UPDATE, handle_key_update)
framework.handle(OP_TYPE_KEY, OP_COMMAND_SIGN, handle_key_sign)
Each handler receives a hex-encoded originalMessage and returns (data, status, error):
data: hex-encoded return data written back on-chain, orNonestatus:0= error,1= success,>=2= pendingerror: error message string if the handler failed
The base/ package (Python/TypeScript) provides hex_to_bytes/bytes_to_hex and the Framework HTTP server. Do not modify base/ — it is framework infrastructure.
fce-sign Project Structure
sign/
├── contracts/ # Solidity contracts (shared)
├── config/
│ ├── extension.env # Generated by pre-build.sh
│ ├── coston2/deployed-addresses.json
│ └── proxy/ # ext-proxy TOML configs
├── scripts/ # use-chain.sh, pre-build.sh, start-services.sh, post-build.sh, test.sh
├── go/internal/extension/ # Go business logic (modify)
├── go/tools/cmd/ # Deploy CLIs (Go-only, shared across all languages)
├── python/app/ # Python business logic (modify)
├── python/base/ # Framework infrastructure (do not modify)
├── typescript/src/app/ # TypeScript business logic (modify)
├── typescript/src/base/ # Framework infrastructure (do not modify)
├── docker-compose.yaml
├── .env.example
└── .env.local.coston2
Building Your Own Extension from fce-sign
- Clone the repo and pick a language.
- Define
opType/opCommandconstants in both the Solidity contract and your handler. - Modify
contracts/InstructionSender.solwith your constants and parameters. - Write handlers in
go/internal/extension/(Go) orapp/(Python/TypeScript). - Follow the deployment steps above —
go/tools/deploy CLIs are shared regardless of language.
Cleanup
Stop the stack:
./scripts/stop-services.sh
Full reset (start from scratch):
./scripts/stop-services.sh
docker compose down --rmi local
rm -f .env config/extension.env config/proxy/extension_proxy.coston2.docker.toml
Then restart from Step 0. Note: on-chain state (deployed contracts, registered extensions, TEEs) cannot be reset — each pre-build.sh deploys new contracts.
fce-sign Troubleshooting
- Proxy won't start / DB sync error — check
docker compose logs ext-proxy; verify DB credentials inconfig/proxy/extension_proxy.coston2.docker.toml. - Transaction reverts — insufficient C2FLR; use the Coston2 faucet.
MachineManager.TooMany()—config/extension.envextension ID doesn't match the on-chain TEE record (usually afterpre-build.sh --force). Do a full reset or keep the existingextension.envand re-run onlypost-build.sh+test.sh.Verification.ChallengeExpired— re-runpost-build.sh.InvalidGovernanceHash— theGOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLDused byset-governancedon't match the governance hash signed by the TEE node; leave both unset for the default deployer-only setup, or ensure.envand the node container agree, then re-runpost-build.sh.code hashes do not match—SIMULATED_TEEand containerMODEdisagree; useSIMULATED_TEE=truewithMODE=1(injected by Docker Compose).- TEE registration times out — try
docker compose restart ext-proxy; FDC attestation requires active relay providers on Coston2. - ngrok URL changed — update
EXT_PROXY_URLin.env.local.coston2, re-runuse-chain.sh, restart the ngrok tunnel if needed (ngrok http 6674), restart Docker stack, re-runpost-build.shandtest.sh.
fce-weather-insurance: Parametric Insurance Example
The fce-weather-insurance repo is a full FCC application: parametric rainfall insurance settled from OpenWeatherMap data inside the enclave. It shows the pattern for TEE workloads that fetch off-chain data, sign a result, and have an on-chain contract verify that signature before moving funds — the same shape as any oracle-backed settlement. See flare-foundation/fce-weather-insurance-x402-agent for an AI agent that buys and settles policies against this extension using x402 payments.
Flow
- A policyholder buys rainfall cover for a date + location, paying an ERC-20 premium (
payToken); the contract reserves the payout from pool liquidity. - From
settlementUnlockAt(00:00 UTC the day after the coverage date), a keeper callsrequestSettlement, which routes aWEATHER/SETTLEinstruction to the extension. - The extension fetches that day's precipitation from OpenWeatherMap inside the enclave and signs the settlement result.
- Anyone calls
settle()with the signedActionResult; the contract wraps the result hash in a domain-separated payload,ecrecovers the signer, requires it equals the registeredteeAddress, and pays out if the rainfall threshold was met.
OP identifiers (OP_TYPE_WEATHER = bytes32("WEATHER"))
| Command | Purpose |
|---|---|
FETCH |
Return current weather JSON for a city (testing / dApp display). |
SETTLE |
Fetch daily rainfall for a policy and return a signed settlement payload. |
BUY |
Decrypt an ECIES-encrypted private policy and return attested terms. |
As always, the constants must match exactly across WeatherInsurance.sol and internal/config/config.go.
Public vs private policies
- Public buy (
buyPolicy): all terms are on-chain; callerapproves the premium first. - Private buy (
buyPolicyPrivate): the buyer ECIES-encrypts ABI-encodedPrivateBuyParamswith the extension public key; the ciphertext is sent as aWEATHER/BUYinstruction. The TEE decrypts via the node's/decryptendpoint, holds the threshold in enclave memory keyed bytermsCommitment, and the buyer finalizes withrelayPrivateBuy(which verifies the TEE signature). Only the commitment lives on-chain until settlement.
TEE signature verification
Both relayPrivateBuy and settle verify the TEE result the same way: reconstruct the hash the node signed and recover the signer, requiring it equal teeAddress.
bytes32 resultHash = keccak256(abi.encodePacked(
keccak256(_resultData), _actionId, keccak256(bytes(_submissionTag)), _status));
// Domain-separated payload — must match go-flare-common signing.TEEActionResult
bytes32 payloadHash = keccak256(abi.encode(bytes32("TEE_ACTION_RESULT"), block.chainid, resultHash));
address signer = _recover(_ethSigned(payloadHash), _signature); // EIP-191 personal-sign
require(signer == teeAddress, "bad TEE signature");
The node signs keccak256(abi.encode(TEE_ACTION_RESULT_PREFIX, chainId, ActionResult.Hash())) — not ActionResult.Hash() alone — with the EIP-191 prefix; only successful (status == 1) results are accepted. Verifying against the raw resultHash fails against current TEE node signatures. SettlementTime (a library) computes settlementUnlockAt = midnight UTC the day after coverage.
Deploy notes (differ from the scaffold/fce-sign)
- Activate with
./scripts/use-chain.sh local coston2(no language arg — the app is Go). - Set
OPENWEATHERMAP_API_KEYandPAY_TOKENin.env.local.coston2. A mock ERC-20 exists on Coston2 at0x53192e788991AD96bC180249B15AefB94E597dD1, or deploy your own. - Deploy with
./scripts/pre-build.shthen./scripts/extension-setup.sh(the latter callssetPayTokenon the deployed contract). The rest (ngrok on6674,post-build.sh, attestation, code-hash whitelisting) matches the scaffold lifecycle below.
Attestation and Reproducible Builds
The TEE's trust comes from remote attestation: the Confidential Space VM measures the running image and reports a code hash. Flare's data providers (FTDC) only accept results from a TEE whose code hash has been whitelisted on-chain for that extension. This makes builds security-critical:
MODE=0is the production attestation backend;MODE=1produces simulated attestation that FTDC rejects. For testnet/mainnet the image must bakeMODE=0and.envmust setLOCAL_MODE=false/SIMULATED_TEE=false.- Reproducibility: set
SOURCE_DATE_EPOCH(e.g. the last commit time) so the same source yields the same code hash. The Go path is bit-for-bit reproducible across machines (single static binary). Python/TypeScript reach same-machine determinism but cross-machine bit-for-bit is best-effort (pip wheels /node_modulesembed host paths) — a rebuild on a different machine may change the code hash and force re-registration.fce-signpicks the language viaLANGUAGE=go|python|typescriptin.env.
Deployment Lifecycle (Coston / Coston2)
The scaffold scripts chain four phases (./scripts/full-setup.sh --chain coston2 --test runs all of them in one shot; each can also run individually):
- pre-build (
pre-build.sh) — compile + deploy theInstructionSender, register the extension onTeeExtensionRegistry, writeEXTENSION_ID+INSTRUCTION_SENDERtoconfig/extension.env. Re-running reuses an existingconfig/extension.envrather than refusing to run; pass--forceonly when you intentionally want a new extension. - start services (
docker compose up -d --build) — runredis, theext-proxy, and theextension-tee(your code) as containers. LocallyLOCAL_MODE=trueskips attestation. - post-build (
post-build.sh) —allow-tee-versionwhitelists the code hash,set-governanceregisters the extension's TEE governance signer set/threshold (defaults to the deployer with threshold 1 unlessGOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLDare set), thenregister-teeregisters the TEE machine on-chain. Useregister-tee -command rRapso re-runs issue a fresh attestation challenge (capitalR) and avoidVerification.ChallengeExpired. - test (
test.sh) — send instructions through the deployed TEE and verify the round-trip.
Real testnet adds: a funded deployer key (Coston2 faucet), a publicly reachable proxy URL (any HTTPS tunnel to port 6674 — e.g. ngrok or cloudflared), indexer-DB credentials for the proxy, and a GCP Confidential Space VM to run the image. Verify the deploy by curling the proxy /info and confirming machineData: platform starts with 0x4743505f414d445f534556… (GCP_AMD_SEV), codeHash is a real measured hash (not the simulated 0x194844cf…), and extensionId/initialOwner match config/extension.env. If the FlareTeeManager diamond is redeployed, all registrations are wiped — re-run pre-build for a fresh EXTENSION_ID, have the VM operator restart with the new ID, then re-run post-build and test.
Common failure modes
Verification.TeeNotFound—NORMAL_PROXY_URLpoints at the wrong chain's FTDC proxy.Verification.ChallengeExpired— re-run post-build; ensureregister-teeuses-command rRap.InvalidGovernanceHash—GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLDdon't match the governance hash the TEE node signed; leave unset for deployer-only defaults, or align both sides and re-run post-build.code hashes do not match—SIMULATED_TEEand the image'sMODEdisagree; both must be "real" (SIMULATED_TEE=false,MODE=0).connect: connection refusedfrom ext-proxy — VPN/route to Flare's indexer DB is down.
Deeper troubleshooting (dedicated guide: FCC Troubleshooting)
Most failures are a local stack drifted from the current on-chain deployment or a registered URL that no longer resolves — check these before assuming the network is at fault:
- TEE never reaches production (availability check
404s indefinitely, empty machine queue,getRandomTeeIdsreturns nothing, callers revert withTooMany()): first check whether you're running withSIMULATED_TEE=true— a simulated attestation is not something the real Coston2 data providers can cosign, so the availability check never gets voted on and the machine stays atINITIALIZEDforever, with every poll for it returning404. That's expected for local development, not a bug; the machine will simply never showPRODUCTIONonchain. Beyond that, read back what you actually registered with the scaffold'squery-teetool —go run ./tools/cmd/query-tee -reg <FlareTeeManager address> <your TEE id>(take the registry address fromconfig/coston2/deployed-addresses.json, not the tool's built-in default). Compare the registeredurlcharacter-by-character with what you're serving now — quick tunnels (ngrok/Cloudflare quick tunnels) mint a new hostname on every restart, so a restarted tunnel silently orphans the on-chain registration. Use a named Cloudflare tunnel or a reserved ngrok domain instead. An emptyteeIdfromquery-teemeans you're talking to a different deployment than you think — pull latestmain, take every address fresh fromconfig/coston2/deployed-addresses.json, and re-run pre-build/post-build (a redeploy wipes existing registrations). - Proxy panics on startup or rejects TEE responses (
hex string without 0x prefix,verifying response signature: invalid signature,error signing: could not sign): version skew betweentee-nodeandtee-proxy— their wire format changes while FCC is in development. Never bump one side alone; the Go module pin andproxy/Dockerfile'sTEE_PROXY_VERSIONmust move together, and building from on-disk local siblings (rather than the pinned versions) is what usually introduces the skew. - Instructions never reach your extension despite an instruction ID and no other errors — you likely reused a reserved
F_-prefixedOPTypeor one of the reserved command names above under a customOPType; this passes local pair validation with no error, so the instruction is just silently not delivered. - Indexer DB auth/connection fails (
Access denied for user 'indexer-reader', timeouts against a VPN-only host) — the host/port/database name are already documented (Step 4 above); only the read-only username and password are missing, since they're issued per requester and rotated periodically. Request current credentials via technical support or X. Credentials shared in community chats go stale faster than the docs do, and older repository documents may point at the Coston (not Coston2) indexer, which uses different credentials and needs a VPN — Coston2 doesn't. - Web2Json verifier request fails only against the OpenWeatherMap URL from the Weather Insurance example — check your OpenWeatherMap plan before assuming the verifier is broken. The Weather Insurance Extension example calls an endpoint that needs a paid OpenWeatherMap plan, so a free-tier key fails there even though the same verifier works correctly against other URLs.
- Include your extension ID, TEE machine address, failing instruction ID, and
query-teeoutput when asking for help — those four details usually identify the cause immediately.
When to Use a Different Skill
- Reading FTSO price feeds, FDC attestations, FAssets minting/redemption, or Smart Accounts — use those dedicated skills.
- General network facts (chain IDs, RPCs, faucets, explorers) — use
flare-general.
Use this skill when the task involves confidential/TEE execution: building or deploying an extension, the InstructionSender/registry pattern, attestation and code-hash whitelisting, parametric insurance on TEE, or the fce-extension-scaffold / fce-sign / fce-weather-insurance repos. See also dev.flare.network/fcc for the official Flare Developer Hub FCC documentation.