End-to-end order of operations. Steps are sequential; each depends on the previous one.
- Foundry, Docker, Go ≥ 1.23, Node ≥ 22, an HTTPS tunnel (
ngrokorcloudflared) - A Coston2-funded key: faucet
- Coston2 indexer DB credentials from Flare support — the extension proxy cannot start without them, and they are issued by a human. Request them first; everything else can proceed while you wait, except steps 3+.
- The FCC scaffold:
git clone https://github.com/flare-foundation/fce-extension-scaffold.git
Both registry constructor args are the FlareTeeManager diamond proxy — the diamond serves the extension-registry and machine-registry facets from one address. The scaffold does the same (tools/pkg/utils/instructions.go: "Both registry args are the FlareTeeManager diamond proxy").
On Coston2 that address is:
0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE
It is read from the scaffold's config/coston2/deployed-addresses.json. FCC is pre-release, so it is not in the FlareContractRegistry yet and can change between deployments — re-check it if deployment reverts.
cd contracts
export TEE_EXTENSION_REGISTRY=0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE
export TEE_MACHINE_REGISTRY=0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE
export BLAZESWAP_ROUTER=0x8D29b61C41CF318d15d031BE2928F79630e068e6 # enables swap settlement
export FXRP_ASSET_MANAGER=0xc1Ca88b937d0b528842F95d5731ffB586f4fbDFA # enables redeem settlement
forge script script/Deploy.s.sol \
--rpc-url https://coston2-api.flare.network/ext/C/rpc \
--broadcast --private-key $DEPLOYER_KEYResolve AssetManagerFXRP yourself rather than trusting a copied address — it is in the registry, and its fAsset() tells you the FXRP token in the same breath:
REG=0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019
AM=$(cast call $REG 'getContractAddressByName(string)(address)' AssetManagerFXRP --rpc-url $COSTON2_RPC)
cast call $AM 'fAsset()(address)' --rpc-url $COSTON2_RPC # → 0x0b6A3645c240605887a5532109323A3E12273dc7If the deploy ran without those two optional vars, wire them afterwards — both are owner-only:
cast send $WRAITH 'setAssetManager(address)' $AM --rpc-url $COSTON2_RPC --private-key $DEPLOYER_KEY
cast send $WRAITH 'setRouter(address)' $ROUTER --rpc-url $COSTON2_RPC --private-key $DEPLOYER_KEYCross-chain and consensus orders additionally need the FDC verifier. It lives in the registry, so resolve it rather than pasting an address:
FDC=$(cast call $REG 'getContractAddressByName(string)(address)' FdcVerification --rpc-url $COSTON2_RPC)
cast send $WRAITH 'setFdcVerification(address)' $FDC --rpc-url $COSTON2_RPC --private-key $DEPLOYER_KEYWithout it, tickAttested and tickAttestedWeb2 revert with
FDC verification not set and the two attested kinds never fire — deliberately,
since an order that asked for a verified second source must not settle without
one.
contracts/.env.deploy holds these for local use and is gitignored. Source it with set -a && . ./.env.deploy && set +a.
Follow extension/README.md: copy the Wraith OPType/OPCommand into the scaffold's config, route WRAITH/EVAL_ORDER to the Wraith handler, and point the scaffold's registration at the deployed WraithOrders address (it is its own InstructionSender).
In the scaffold, with .env configured (SIMULATED_TEE=true, LOCAL_MODE=false, tunnel URL in EXT_PROXY_URL, indexer credentials in the proxy TOML):
./scripts/pre-build.sh # registers the extension → extension ID
./scripts/start-services.sh --chain coston2
./scripts/post-build.sh # registers the TEE machineDo not re-run pre-build.sh --force casually — it mints a new extension ID while the TEE machine stays bound to the old one (MachineManager.TooMany()).
# Once registration is confirmed on-chain:
cast send $WRAITH "setExtensionId()" --rpc-url $RPC --private-key $DEPLOYER_KEY
# No TEE address to register — execute() reads the active machine set from
# TeeMachineRegistry directly, so post-build.sh registering the machine is
# the only step needed. Confirm it landed:
cast call $TEE_MACHINE_REGISTRY "getActiveTeeMachines(uint256)(address[],string[])" \
$EXTENSION_ID --rpc-url $RPCcd keeper && npm install
export WRAITH_ADDRESS=$WRAITH KEEPER_PRIVATE_KEY=0x... EXT_PROXY_URL=https://<tunnel>
# Optional: the second oracle consensus orders need. Unset, they never fire.
export FDC_API_URL=https://api.coingecko.com/api/v3/simple/price
export FDC_QUERY_PARAMS='{"ids":"flare-networks","vs_currencies":"usd","include_last_updated_at":"true"}'
# No API key needed on Coston2: the verifier and DA Layer both accept Flare's
# published key, and the keeper defaults to it.
npm startcd frontend && npm install
cp .env.example .env.local # fill in WRAITH_ADDRESS, FXRP, TOKEN_OUT, proxy URL
npm run devTo offer gasless order creation, fund a separate key with C2FLR and set both halves — the server key that signs, and the public flag that reveals the option:
RELAYER_PRIVATE_KEY=0x... # server-side only, never NEXT_PUBLIC_
NEXT_PUBLIC_RELAYER_ENABLED=true
The relayer reimburses itself out of the escrowed token, so it needs only enough C2FLR for gas. Leaving the flag unset hides the option, which is the right default: offering a gasless path that then fails is worse than not offering one.
- Create an order in the UI with a trigger that is currently false. Confirm the explorer shows only ciphertext.
- Watch the keeper tick it; confirm no execution (the TEE returns a no-op).
- Create an order whose trigger is already true. Confirm the keeper relays the signed result and
execute()settles it. cast call $WRAITH "getOrder(uint256)" <id>— confirmexecuted == trueand the actionId cannot be replayed.