SuperAPI AI Directory

flare-fcc

General & other

View repository

Updated 10-15 21:42

About this skill

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-sign warning).

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 — set LANGUAGE=go|python|typescript in .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 calls settle() 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() and getTeeExtensionInstructionsSender(uint256 extensionId) — used to discover an extension's ID. Public extension IDs start at 0x10000 (65536); IDs below that are reserved for system extensions, so setExtensionId() scans from 0x10000 up to nextPublicExtensionId(), not from zero.
    • Access control: when you register an extension you bind it to one InstructionSender address. The registry rejects any sendInstructions call whose msg.sender isn'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 > 1 to 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:

  1. Know its extension ID — to look up serving TEE machines. The scaffold's setExtensionId() scans the registry (starting at 0x10000, the first public extension ID) after registration and caches the ID (set-once).
  2. Call sendInstructions on TeeExtensionRegistry with at least one teeId, the opType/opCommand bytes32 identifiers, a non-empty message, and (usually empty) cosigners.
  3. Be payable and forward msg.value — the registry charges a per-instruction fee.
  4. 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 /info endpoint. 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):

  1. go/internal/config/config.go — OPType/OPCommand string constants and version
  2. go/pkg/types/types.go — request/response/state structs
  3. go/internal/extension/extension.go — routing cases + handlers (the main customization point)
  4. contracts/InstructionSender.sol — matching bytes32 constants + send functions
  5. tools/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 6674 makes 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.env exists, pre-build refuses to run again. Use --force only when intentionally creating a new extension — it deploys a new InstructionSender and registers a new extension ID, which will cause MachineManager.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, or None
  • status: 0 = error, 1 = success, >=2 = pending
  • error: 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

  1. Clone the repo and pick a language.
  2. Define opType/opCommand constants in both the Solidity contract and your handler.
  3. Modify contracts/InstructionSender.sol with your constants and parameters.
  4. Write handlers in go/internal/extension/ (Go) or app/ (Python/TypeScript).
  5. 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 in config/proxy/extension_proxy.coston2.docker.toml.
  • Transaction reverts — insufficient C2FLR; use the Coston2 faucet.
  • MachineManager.TooMany() — config/extension.env extension ID doesn't match the on-chain TEE record (usually after pre-build.sh --force). Do a full reset or keep the existing extension.env and re-run only post-build.sh + test.sh.
  • Verification.ChallengeExpired — re-run post-build.sh.
  • InvalidGovernanceHash — the GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD used by set-governance don't match the governance hash signed by the TEE node; leave both unset for the default deployer-only setup, or ensure .env and the node container agree, then re-run post-build.sh.
  • code hashes do not match — SIMULATED_TEE and container MODE disagree; use SIMULATED_TEE=true with MODE=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_URL in .env.local.coston2, re-run use-chain.sh, restart the ngrok tunnel if needed (ngrok http 6674), restart Docker stack, re-run post-build.sh and test.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

  1. A policyholder buys rainfall cover for a date + location, paying an ERC-20 premium (payToken); the contract reserves the payout from pool liquidity.
  2. From settlementUnlockAt (00:00 UTC the day after the coverage date), a keeper calls requestSettlement, which routes a WEATHER/SETTLE instruction to the extension.
  3. The extension fetches that day's precipitation from OpenWeatherMap inside the enclave and signs the settlement result.
  4. Anyone calls settle() with the signed ActionResult; the contract wraps the result hash in a domain-separated payload, ecrecovers the signer, requires it equals the registered teeAddress, 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; caller approves the premium first.
  • Private buy (buyPolicyPrivate): the buyer ECIES-encrypts ABI-encoded PrivateBuyParams with the extension public key; the ciphertext is sent as a WEATHER/BUY instruction. The TEE decrypts via the node's /decrypt endpoint, holds the threshold in enclave memory keyed by termsCommitment, and the buyer finalizes with relayPrivateBuy (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_KEY and PAY_TOKEN in .env.local.coston2. A mock ERC-20 exists on Coston2 at 0x53192e788991AD96bC180249B15AefB94E597dD1, or deploy your own.
  • Deploy with ./scripts/pre-build.sh then ./scripts/extension-setup.sh (the latter calls setPayToken on the deployed contract). The rest (ngrok on 6674, 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=0 is the production attestation backend; MODE=1 produces simulated attestation that FTDC rejects. For testnet/mainnet the image must bake MODE=0 and .env must set LOCAL_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_modules embed host paths) — a rebuild on a different machine may change the code hash and force re-registration. fce-sign picks the language via LANGUAGE=go|python|typescript in .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):

  1. pre-build (pre-build.sh) — compile + deploy the InstructionSender, register the extension on TeeExtensionRegistry, write EXTENSION_ID + INSTRUCTION_SENDER to config/extension.env. Re-running reuses an existing config/extension.env rather than refusing to run; pass --force only when you intentionally want a new extension.
  2. start services (docker compose up -d --build) — run redis, the ext-proxy, and the extension-tee (your code) as containers. Locally LOCAL_MODE=true skips attestation.
  3. post-build (post-build.sh) — allow-tee-version whitelists the code hash, set-governance registers the extension's TEE governance signer set/threshold (defaults to the deployer with threshold 1 unless GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD are set), then register-tee registers the TEE machine on-chain. Use register-tee -command rRap so re-runs issue a fresh attestation challenge (capital R) and avoid Verification.ChallengeExpired.
  4. 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_URL points at the wrong chain's FTDC proxy.
  • Verification.ChallengeExpired — re-run post-build; ensure register-tee uses -command rRap.
  • InvalidGovernanceHash — GOVERNANCE_SIGNERS/GOVERNANCE_THRESHOLD don'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_TEE and the image's MODE disagree; both must be "real" (SIMULATED_TEE=false, MODE=0).
  • connect: connection refused from 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, getRandomTeeIds returns nothing, callers revert with TooMany()): first check whether you're running with SIMULATED_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 at INITIALIZED forever, with every poll for it returning 404. That's expected for local development, not a bug; the machine will simply never show PRODUCTION onchain. Beyond that, read back what you actually registered with the scaffold's query-tee tool — go run ./tools/cmd/query-tee -reg <FlareTeeManager address> <your TEE id> (take the registry address from config/coston2/deployed-addresses.json, not the tool's built-in default). Compare the registered url character-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 empty teeId from query-tee means you're talking to a different deployment than you think — pull latest main, take every address fresh from config/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 between tee-node and tee-proxy — their wire format changes while FCC is in development. Never bump one side alone; the Go module pin and proxy/Dockerfile's TEE_PROXY_VERSION must 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_-prefixed OPType or one of the reserved command names above under a custom OPType; 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-tee output 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.