# Weather Insurance Extension

> Build and deploy an extension that settles rainfall insurance.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

Source: https://dev.flare.network/fcc/guides/weather-insurance-extension

Build and deploy a **Trusted Execution Environment (TEE) extension** that settles **parametric rainfall insurance** using weather data from [OpenWeatherMap](https://openweathermap.org/api) inside a secure enclave. This guide walks through every step — from writing the smart contract and extension handler to deploying on Coston2 and running an end-to-end policy buy and settlement test. The code for this example is available on [GitHub](https://github.com/flare-foundation/fce-weather-insurance).

New to Flare TEE?

A TEE extension is an off-chain program that runs inside a Trusted Execution Environment. It receives **instructions** from on-chain transactions, processes them in a secure enclave, and writes the signed results back to the blockchain. The TEE framework handles attestation, key management, and message routing — you only write the business logic.

## Overview[​](#overview "Direct link to Overview")

The Weather Insurance extension demonstrates a full [Flare Confidential Compute (FCC)](/fcc/overview) application workflow:

1.  A policyholder buys rainfall cover for a specific date and location, paying a premium in an ERC-20 token.
2.  After midnight on the day following the coverage date, a keeper sends a `SETTLE` instruction to the extension.
3.  The extension fetches that day's precipitation from the OpenWeatherMap API inside the enclave.
4.  The extension signs the settlement result; anyone calls `settle()` on-chain to verify the TEE signature and pay out if the rainfall threshold was met.

The extension also supports:

-   **`FETCH`** — ad-hoc current weather for a city (useful for testing).
-   **`BUY`** — private policy purchase where coverage terms are [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme)\-encrypted and only decrypted inside the TEE.

We will build this in three parts:

1.  **on-chain contract** (`WeatherInsurance`) that manages policies and routes instructions;
2.  **off-chain handler** (Go) that calls the OpenWeatherMap API and signs results;
3.  **deployment tooling** that ties everything together.

## Architecture[​](#architecture "Direct link to Architecture")

The extension stack consists of three components running as Docker services:

-   **`extension-tee`:** Your extension code (Go). Receives decoded instructions from the proxy, calls the OpenWeatherMap API, and returns signed results.
-   **`ext-proxy`:** The TEE extension proxy. Watches the chain for new instructions targeting your extension, forwards them to your handler, and submits results back on-chain.
-   **`redis`:** In-memory store used by the proxy for internal state.

## Onchain Contract[​](#onchain-contract "Direct link to Onchain Contract")

The `WeatherInsurance` smart contract is the onchain entry point.

It interacts with two Flare system contracts:

-   **`TeeExtensionRegistry`:** Registers extensions and routes instructions to TEE machines.
-   **`TeeMachineRegistry`:** Tracks registered TEE machines and provides random selection.

The contract also manages policy state, ERC-20 premiums and payouts, and on-chain verification of signatures.

<details>
<summary>Contract Code</summary>

Contract Code

contracts/WeatherInsurance.sol

```
// SPDX-License-Identifier: MITpragma solidity ^0.8.27;// TODO: Replace local interfaces with imports from flare-smart-contracts-v2 once published as a package.import { ITeeExtensionRegistry } from "./interfaces/ITeeExtensionRegistry.sol";import { ITeeMachineRegistry } from "./interfaces/ITeeMachineRegistry.sol";import { SettlementTime } from "./SettlementTime.sol";/// @notice Minimal ERC-20 surface used for premiums and payouts.interface IERC20 {    function transfer(address to, uint256 amount) external returns (bool);    function transferFrom(address from, address to, uint256 amount) external returns (bool);    function balanceOf(address account) external view returns (uint256);}/// @title WeatherInsurance/// @author Flare Foundation/// @notice Parametric rainfall insurance settled by a Flare Confidential Compute/// (FCC) TEE extension. A policyholder buys cover for a specific date; from 00:00 UTC/// on the day after that date a keeper asks the TEE to fetch the day's rainfall from/// OpenWeatherMap's One Call `day_summary` endpoint. The TEE returns a signed/// settlement and `settle()` verifies the TEE signature on-chain (ecrecover/// against the registered TEE address) before paying out if the measured/// precipitation met the policy threshold.////// Premiums and payouts are denominated in an ERC-20 `payToken` (set by the/// owner). Native value is only used for the TEE instruction fee forwarded to/// the registry in requestSettlement/getWeather.////// This contract also doubles as the FCC InstructionSender: it owns the/// extension registration and routes WEATHER/{FETCH,SETTLE,BUY} instructions/// through the Flare TEE Manager diamond.////// DO NOT MODIFY: the registry wiring in the constructor, setExtensionId(),/// and _getExtensionId().contract WeatherInsurance {    // --- FCC operation identifiers (must match internal/config/config.go) ---    /// @notice Operation type for weather-data actions.    // forge-lint: disable-next-line(unsafe-typecast)    bytes32 public constant OP_TYPE_WEATHER = bytes32("WEATHER");    /// @notice Command for the ad-hoc current-weather FETCH action.    // forge-lint: disable-next-line(unsafe-typecast)    bytes32 public constant OP_COMMAND_FETCH = bytes32("FETCH");    /// @notice Command for the policy SETTLE action (fetch daily rainfall).    // forge-lint: disable-next-line(unsafe-typecast)    bytes32 public constant OP_COMMAND_SETTLE = bytes32("SETTLE");    /// @notice Command for a private policy BUY action (ECIES-encrypted params).    // forge-lint: disable-next-line(unsafe-typecast)    bytes32 public constant OP_COMMAND_BUY = bytes32("BUY");    // --- Registries ---    /// @notice Reference to the TEE extension registry contract.    ITeeExtensionRegistry public immutable TEE_EXTENSION_REGISTRY;    /// @notice Reference to the TEE machine registry contract.    ITeeMachineRegistry public immutable TEE_MACHINE_REGISTRY;    uint256 private _extensionId;    // --- Policy state ---    /// @notice A parametric rainfall policy. Amounts are in `payToken` units.    struct Policy {        address policyholder;       // who gets paid on trigger        string date;                // coverage date, "YYYY-MM-DD" (OpenWeatherMap day_summary)        uint256 rainThresholdMmE2;  // payout iff measured precipitation (mm × 100) >= this        uint256 payout;             // payToken paid to the holder on trigger        uint256 premium;            // payToken paid at purchase        uint256 measuredMmE2;       // precipitation reported by the TEE (mm × 100), set on settle        bool settled;        bool paidOut;        bool isPrivate;             // if true, rain threshold is not stored on-chain (TEE-held)        bytes32 termsCommitment;    // keccak256(abi.encode PrivateBuyParams); links TEE memory to policy        string lat;                 // decimal latitude for One Call day_summary (set at buy)        string lon;                 // decimal longitude for One Call day_summary (set at buy)        uint64 settlementUnlockAt;  // unix sec: 00:00 UTC on the day after coverage date    }    /// @notice ABI payload of a SETTLE instruction (decoded by the TEE).    struct SettleMessage {        uint256 policyId;        address contractAddr;       // this contract — echoed back so settle() can bind the result        string date;                // coverage date (on-chain for all policies)        string lat;        string lon;        bytes32 termsCommitment;    // nonzero => TEE loads coverage terms from private BUY memory    }    /// @notice ABI payload of an ad-hoc FETCH instruction (decoded by the TEE).    struct GetWeatherMessage {        string city;    }    /// @notice Decrypted by the TEE; not sent on-chain in plaintext during buyPolicyPrivate.    struct PrivateBuyParams {        address holder;        address contractAddr;        string date;        uint256 rainThresholdMmE2;        uint256 payout;        uint256 premium;        string lat;        string lon;    }    /// @notice Contract owner (deployer). Funds the pool and registers the TEE address.    address public owner;    /// @notice Registered TEE signing address; settlements must be signed by it.    address public teeAddress;    /// @notice ERC-20 used for premiums and payouts. Set by the owner before use.    IERC20 public payToken;    /// @notice payToken reserved to cover payouts of unsettled policies.    uint256 public reserved;    Policy[] public policies;    event PayTokenSet(address indexed token);    event PolicyBought(uint256 indexed policyId, address indexed holder, string date, uint256 rainThresholdMmE2, uint256 payout, uint256 premium);    /// @notice Emitted for private buys instead of PolicyBought (no coverage terms in logs).    event PrivatePolicyRelayed(uint256 indexed policyId, address indexed holder, bytes32 indexed termsCommitment);    event PrivateBuyRequested(bytes32 indexed instructionId, address indexed holder);    event SettlementRequested(uint256 indexed policyId, bytes32 instructionId);    event Settled(uint256 indexed policyId, uint256 measuredMmE2, bool paidOut);    event TeeAddressSet(address indexed teeAddress);    event PoolFunded(address indexed from, uint256 amount);    modifier onlyOwner() {        require(msg.sender == owner, "not owner");        _;    }    /// @notice Initializes the contract with registry addresses.    /// @param _teeExtensionRegistry Address of the TEE extension registry.    /// @param _teeMachineRegistry Address of the TEE machine registry.    constructor(        ITeeExtensionRegistry _teeExtensionRegistry,        ITeeMachineRegistry _teeMachineRegistry    ) {        require(address(_teeExtensionRegistry) != address(0), "TeeExtensionRegistry cannot be zero address");        require(address(_teeMachineRegistry) != address(0), "TeeMachineRegistry cannot be zero address");        require(address(_teeExtensionRegistry).code.length > 0, "TeeExtensionRegistry has no code");        require(address(_teeMachineRegistry).code.length > 0, "TeeMachineRegistry has no code");        TEE_EXTENSION_REGISTRY = _teeExtensionRegistry;        TEE_MACHINE_REGISTRY = _teeMachineRegistry;        owner = msg.sender;    }    /// @notice Finds and sets this contract's extension id. Can only be set once.    /// DO NOT MODIFY this function.    function setExtensionId() external {        require(_extensionId == 0, "Extension ID already set.");        uint256 c = TEE_EXTENSION_REGISTRY.extensionsCounter();        for (uint256 i = 0; i < c; ++i) {            if (TEE_EXTENSION_REGISTRY.getTeeExtensionInstructionsSender(i) == address(this)) {                _extensionId = i;                return;            }        }        revert("Extension ID not found.");    }    // --- Pool funding (payToken) ---    /// @notice Set the ERC-20 used for premiums and payouts. Owner only.    function setPayToken(address _token) external onlyOwner {        require(_token != address(0), "zero token");        payToken = IERC20(_token);        emit PayTokenSet(_token);    }    /// @notice Fund the payout pool with `_amount` payToken. Caller must approve first.    function fundPool(uint256 _amount) external {        require(address(payToken) != address(0), "payToken not set");        require(payToken.transferFrom(msg.sender, address(this), _amount), "fund transfer failed");        emit PoolFunded(msg.sender, _amount);    }    /// @notice Owner withdraws unreserved payToken liquidity (premiums + unclaimed pool).    function withdraw(uint256 _amount) external onlyOwner {        require(_amount <= availableLiquidity(), "exceeds unreserved liquidity");        require(payToken.transfer(owner, _amount), "withdraw failed");    }    /// @notice payToken liquidity not reserved against outstanding policy payouts.    function availableLiquidity() public view returns (uint256) {        if (address(payToken) == address(0)) {            return 0;        }        return payToken.balanceOf(address(this)) - reserved;    }    // --- Policy lifecycle ---    /// @notice Buy a rainfall policy for `_date`. The premium is pulled in payToken,    ///         so the caller must `approve` this contract for `_premium` first.    /// @param _date Coverage date as "YYYY-MM-DD".    /// @param _rainThresholdMmE2 Trigger threshold in mm × 100 (e.g. 100 = 1.00 mm).    /// @param _payout payToken paid to the holder if the threshold is met. Must be    ///        covered by currently unreserved liquidity (after the premium is added).    /// @param _premium payToken pulled from the caller at purchase.    /// @return policyId The new policy's id.    function buyPolicy(        string calldata _date,        uint256 _rainThresholdMmE2,        uint256 _payout,        uint256 _premium,        string calldata _lat,        string calldata _lon    ) external returns (uint256 policyId) {        policyId = _createPolicy(msg.sender, _date, _rainThresholdMmE2, _payout, _premium, _lat, _lon, false);    }    /// @notice Request a private policy buy. Policy parameters are ECIES-encrypted under    ///         the TEE public key and passed as opaque bytes; only the ciphertext is visible    ///         in this transaction. After the TEE processes the instruction, call    ///         `relayPrivateBuy` with the signed ActionResult.    /// @param _encryptedPolicy ECIES ciphertext of ABI-encoded PrivateBuyParams.    function buyPolicyPrivate(bytes calldata _encryptedPolicy) external payable {        require(_encryptedPolicy.length > 0, "empty ciphertext");        address[] memory teeIds = TEE_MACHINE_REGISTRY.getRandomTeeIds(_getExtensionId(), 1);        address[] memory cosigners = new address[](0);        ITeeExtensionRegistry.TeeInstructionParams memory params = ITeeExtensionRegistry.TeeInstructionParams({            opType: OP_TYPE_WEATHER,            opCommand: OP_COMMAND_BUY,            message: _encryptedPolicy,            cosigners: cosigners,            cosignersThreshold: 0,            claimBackAddress: msg.sender        });        bytes32 instructionId = TEE_EXTENSION_REGISTRY.sendInstructions{value: msg.value}(teeIds, params);        emit PrivateBuyRequested(instructionId, msg.sender);    }    /// @notice Finalize a private buy with a TEE-signed result from the BUY instruction.    /// @dev The TEE node signs `ActionResult.Hash()` with its registered key using the    ///      EIP-191 personal-sign prefix. We reconstruct that hash from the result    ///      fields the proxy returned and recover the signer, requiring it to equal    ///      `teeAddress`. `_resultData` is the exact bytes the TEE returned in    ///      ActionResult.Data:    ///      abi.encode(address holder, address contractAddr, string date,    ///      uint256 rainThresholdMmE2, uint256 payout, uint256 premium, string lat,    ///      string lon).    ///      - holder: policy buyer; must equal msg.sender.    ///      - contractAddr: target WeatherInsurance; must equal address(this).    ///      - date: coverage date as "YYYY-MM-DD".    ///      - rainThresholdMmE2: rainfall trigger threshold in mm × 100 (e.g. 100 = 1.00 mm).    ///      - payout: payToken paid to holder if triggered; must be covered by pool liquidity.    ///      - premium: payToken pulled from holder at relay (caller must approve first).    ///      - lat: latitude for settlement weather fetch.    ///      - lon: longitude for settlement weather fetch.    ///      A `termsCommitment` over those fields is stored on the policy; threshold and    ///      coordinates stay off-chain until settlement reveals them.    /// @param _resultData     Raw `ActionResult.Data` returned by the TEE extension after it    ///                        decrypts the ECIES ciphertext from `buyPolicyPrivate`, validates    ///                        the terms, and ABI-encodes them. Must be the exact byte string    ///                        the proxy signed (do not re-encode). Decoded as:    ///                        `(holder, contractAddr, date, rainThresholdMmE2, payout, premium,    ///                        lat, lon)` — see @dev above. `holder` must be `msg.sender`;    ///                        `contractAddr` must be `address(this)`.    /// @param _actionId       `ActionResult.ID`: the instruction id emitted by    ///                        `buyPolicyPrivate` / `sendInstructions`. Binds this relay to one    ///                        FCC instruction so the signed result cannot be replayed against    ///                        another action. Included in `ActionResult.Hash()` as a plain    ///                        `bytes32` (not hashed again).    /// @param _submissionTag  `ActionResult.SubmissionTag` from the original action payload    ///                        (typically `"submit"`). Hashed as `keccak256(bytes(tag))` inside    ///                        `ActionResult.Hash()`. Must match the tag the TEE node signed.    /// @param _status         `ActionResult.Status` reported by the extension: `0` = error,    ///                        `1` = success, `2` = still processing. Only `1` is accepted;    ///                        any other value reverts with `"TEE reported failure"`. Part of    ///                        the signed hash so a failed TEE result cannot be relayed.    /// @param _signature      ECDSA signature from the registered TEE node over    ///                        `ActionResult.Hash()` = `keccak256(abi.encodePacked(    ///                        keccak256(_resultData), _actionId,    ///                        keccak256(bytes(_submissionTag)), _status))`, wrapped with the    ///                        EIP-191 `"\x19Ethereum Signed Message:\n32"` prefix. Recovered    ///                        signer must equal `teeAddress`.    /// @return policyId The new policy's id.    function relayPrivateBuy(        bytes calldata _resultData,        bytes32 _actionId,        string calldata _submissionTag,        uint8 _status,        bytes calldata _signature    ) external returns (uint256 policyId) {        require(teeAddress != address(0), "TEE address not set");        require(_status == 1, "TEE reported failure");        // Reconstruct ActionResult.Hash() = keccak256(keccak256(data) || id || keccak256(tag) || status).        bytes32 resultHash = keccak256(            abi.encodePacked(                keccak256(_resultData),                _actionId,                keccak256(bytes(_submissionTag)),                _status            )        );        // Recover the signer from the signature and verify it matches the TEE address.        address signer = _recover(_ethSigned(resultHash), _signature);        // Verify the signer matches the registered TEE address.        require(signer == teeAddress, "bad TEE signature");        // ActionResult.Data from the TEE BUY instruction (see @dev above for field meanings).        (            address holder,            address contractAddr,            string memory date,            uint256 rainThresholdMmE2,            uint256 payout,            uint256 premium,            string memory lat,            string memory lon        ) = abi.decode(_resultData, (address, address, string, uint256, uint256, uint256, string, string));        require(contractAddr == address(this), "buy not for this contract");        require(msg.sender == holder, "not holder");        // Hash attested terms for settlement verification; rain threshold stays off-chain (isPrivate).        bytes32 commitment = _privateTermsCommitment(            holder, contractAddr, date, rainThresholdMmE2, payout, premium, lat, lon        );        policyId = _createPolicy(holder, date, rainThresholdMmE2, payout, premium, lat, lon, true);        policies[policyId].termsCommitment = commitment;        emit PrivatePolicyRelayed(policyId, holder, commitment);    }    /// @notice Ask the TEE to settle a policy by fetching its date's rainfall.    /// @dev Allowed only from settlementUnlockAt onward (midnight UTC after coverage date).    function requestSettlement(uint256 _policyId) external payable {        Policy storage p = policies[_policyId];        require(p.policyholder != address(0), "no such policy");        require(!p.settled, "already settled");        require(block.timestamp >= p.settlementUnlockAt, "settlement not open yet");        // Pick one registered TEE for this extension; no cosigners on settlement.        address[] memory teeIds = TEE_MACHINE_REGISTRY.getRandomTeeIds(_getExtensionId(), 1);        address[] memory cosigners = new address[](0);        // TEE fetches rainfall for date/lat/lon; termsCommitment links private policies to TEE-held terms.        bytes memory message = abi.encode(SettleMessage({            policyId: _policyId,            contractAddr: address(this),            date: p.date,            lat: p.lat,            lon: p.lon,            termsCommitment: p.termsCommitment        }));        ITeeExtensionRegistry.TeeInstructionParams memory params = ITeeExtensionRegistry.TeeInstructionParams({            opType: OP_TYPE_WEATHER,            opCommand: OP_COMMAND_SETTLE,            message: message,            cosigners: cosigners,            cosignersThreshold: 0,            claimBackAddress: msg.sender        });        // After processing, call settle() with the signed ActionResult from the proxy.        bytes32 instructionId = TEE_EXTENSION_REGISTRY.sendInstructions{value: msg.value}(teeIds, params);        emit SettlementRequested(_policyId, instructionId);    }    /// @notice Finalize a policy with a TEE-signed settlement and pay out if triggered.    /// @dev The TEE node signs `ActionResult.Hash()` with its registered key using the    ///      EIP-191 personal-sign prefix. We reconstruct that hash from the result    ///      fields the proxy returned and recover the signer, requiring it to equal    ///      `teeAddress`. `_resultData` is the exact bytes the TEE returned in    ///      ActionResult.Data: abi.encode(address contractAddr, uint256 policyId,    ///      uint256 precipitationMmE2, string date, uint256 rainThresholdMmE2, bool triggered).    ///      For private policies, rainThresholdMmE2 is verified against    ///      termsCommitment and written on-chain; isPrivate is cleared before payout.    /// @param _resultData     ActionResult.Data bytes (the settlement payload).    /// @param _actionId       ActionResult.ID.    /// @param _submissionTag  ActionResult.SubmissionTag (e.g. "submit").    /// @param _status         ActionResult.Status (1 = success).    /// @param _signature      TEE node signature over ActionResult.Hash().    function settle(        bytes calldata _resultData,        bytes32 _actionId,        string calldata _submissionTag,        uint8 _status,        bytes calldata _signature    ) external {        require(teeAddress != address(0), "TEE address not set");        require(_status == 1, "TEE reported failure");        // Reconstruct ActionResult.Hash() = keccak256(keccak256(data) || id || keccak256(tag) || status).        bytes32 resultHash = keccak256(            abi.encodePacked(                keccak256(_resultData),                _actionId,                keccak256(bytes(_submissionTag)),                _status            )        );        address signer = _recover(_ethSigned(resultHash), _signature);        require(signer == teeAddress, "bad TEE signature");        // ActionResult.Data from the TEE SETTLE instruction (see @dev above for field meanings).        (            address contractAddr,            uint256 policyId,            uint256 precipitationMmE2,            string memory date,            uint256 revealedThresholdMmE2,            bool triggered        ) = abi.decode(_resultData, (address, uint256, uint256, string, uint256, bool));        require(contractAddr == address(this), "settlement not for this contract");        Policy storage p = policies[policyId];        require(p.policyholder != address(0), "no such policy");        require(!p.settled, "already settled");        require(block.timestamp >= p.settlementUnlockAt, "settlement not open yet");        // Private policy: verify revealed threshold against BUY commitment, then store on-chain.        if (p.termsCommitment != bytes32(0)) {            require(p.isPrivate, "not private");            require(                _privateTermsCommitment(                    p.policyholder,                    address(this),                    date,                    revealedThresholdMmE2,                    p.payout,                    p.premium,                    p.lat,                    p.lon                ) == p.termsCommitment,                "terms mismatch"            );            require(triggered == (precipitationMmE2 >= revealedThresholdMmE2), "triggered mismatch");            p.date = date;            p.rainThresholdMmE2 = revealedThresholdMmE2;            p.isPrivate = false;        }        p.settled = true;        p.measuredMmE2 = precipitationMmE2;        reserved -= p.payout; // release payout liquidity whether or not we pay out        bool pay = precipitationMmE2 >= p.rainThresholdMmE2;        if (pay) {            p.paidOut = true;            require(payToken.transfer(p.policyholder, p.payout), "payout transfer failed");        }        emit Settled(policyId, precipitationMmE2, p.paidOut);    }    /// @notice Whether settlement may be requested now (block.timestamp >= settlementUnlockAt).    function canRequestSettlement(uint256 _policyId) external view returns (bool) {        Policy storage p = policies[_policyId];        if (p.policyholder == address(0) || p.settled) {            return false;        }        return block.timestamp >= p.settlementUnlockAt;    }    /// @notice Register the active TEE signing address (read off TeeMachineRegistry).    function setTeeAddress(address _teeAddress) external onlyOwner {        require(_teeAddress != address(0), "zero TEE address");        teeAddress = _teeAddress;        emit TeeAddressSet(_teeAddress);    }    // --- Ad-hoc current weather (unchanged FETCH path; handy for testing) ---    /// @notice Request current weather for a city from the TEE (no policy involved).    /// @param _city UTF-8 city name (e.g. "Berlin,DE").    function getWeather(string calldata _city) external payable {        address[] memory teeIds = TEE_MACHINE_REGISTRY.getRandomTeeIds(_getExtensionId(), 1);        address[] memory cosigners = new address[](0);        ITeeExtensionRegistry.TeeInstructionParams memory params = ITeeExtensionRegistry.TeeInstructionParams({            opType: OP_TYPE_WEATHER,            opCommand: OP_COMMAND_FETCH,            message: abi.encode(GetWeatherMessage({city: _city})),            cosigners: cosigners,            cosignersThreshold: 0,            claimBackAddress: msg.sender        });        TEE_EXTENSION_REGISTRY.sendInstructions{value: msg.value}(teeIds, params);    }    // --- Views ---    function policyCount() external view returns (uint256) {        return policies.length;    }    // --- Internal ---    /// @notice Returns the cached extension ID, reverting if not set.    function _getExtensionId() internal view returns (uint256) {        require(_extensionId != 0, "Extension ID is not set.");        return _extensionId;    }    /// @notice Commitment over private buy terms (matches TEE extension storage key).    function _privateTermsCommitment(        address holder,        address contractAddr,        string memory date,        uint256 rainThresholdMmE2,        uint256 payout,        uint256 premium,        string memory lat,        string memory lon    ) internal pure returns (bytes32) {        return keccak256(abi.encode(holder, contractAddr, date, rainThresholdMmE2, payout, premium, lat, lon));    }    /// @notice Pull premium, reserve payout liquidity, and append a policy record.    /// @param _isPrivate If true, rain threshold is kept in TEE memory only (date is stored on-chain).    function _createPolicy(        address _holder,        string memory _date,        uint256 _rainThresholdMmE2,        uint256 _payout,        uint256 _premium,        string memory _lat,        string memory _lon,        bool _isPrivate    ) internal returns (uint256 policyId) {        require(address(payToken) != address(0), "payToken not set");        require(_premium > 0, "premium required");        require(_payout > 0, "payout required");        require(_holder != address(0), "zero holder");        require(bytes(_lat).length > 0, "lat required");        require(bytes(_lon).length > 0, "lon required");        require(bytes(_date).length > 0, "date required");        uint64 unlockAt = uint64(SettlementTime.unlockAt(_date));        require(payToken.transferFrom(_holder, address(this), _premium), "premium transfer failed");        require(_payout <= availableLiquidity(), "insufficient pool liquidity for payout");        policyId = policies.length;        policies.push(Policy({            policyholder: _holder,            date: _date,            rainThresholdMmE2: _isPrivate ? 0 : _rainThresholdMmE2,            payout: _payout,            premium: _premium,            measuredMmE2: 0,            settled: false,            paidOut: false,            isPrivate: _isPrivate,            termsCommitment: bytes32(0),            lat: _lat,            lon: _lon,            settlementUnlockAt: unlockAt        }));        reserved += _payout;        if (!_isPrivate) {            emit PolicyBought(policyId, _holder, _date, _rainThresholdMmE2, _payout, _premium);        }    }    /// @notice EIP-191 personal-sign hash of a 32-byte digest.    function _ethSigned(bytes32 _hash) private pure returns (bytes32) {        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash));    }    /// @notice Recover the signer of a 65-byte [r||s||v] secp256k1 signature.    function _recover(bytes32 _digest, bytes calldata _sig) private pure returns (address) {        require(_sig.length == 65, "bad signature length");        bytes32 r;        bytes32 s;        uint8 v;        assembly {            r := calldataload(_sig.offset)            s := calldataload(add(_sig.offset, 32))            v := byte(0, calldataload(add(_sig.offset, 64)))        }        if (v < 27) {            v += 27;        }        require(v == 27 || v == 28, "bad signature v");        address signer = ecrecover(_digest, v, r, s);        require(signer != address(0), "invalid signature");        return signer;    }}
```

</details>

note

The constructor takes the addresses of the two Flare system contracts. These are already deployed on Coston2 — the deployment tooling reads their addresses from `config/coston2/deployed-addresses.json`. This is a temporary solution, because Flare Confidential Compute is still in development. On release, both addresses will be available through the [`FlareContractRegistry`](/network/guides/flare-contracts-registry) contract.

### Operation Identifiers[​](#operation-identifiers "Direct link to Operation Identifiers")

Constants must match `internal/config/config.go`:

contracts/WeatherInsurance.sol

```
bytes32 public constant OP_TYPE_WEATHER = bytes32("WEATHER");bytes32 public constant OP_COMMAND_FETCH  = bytes32("FETCH");bytes32 public constant OP_COMMAND_SETTLE = bytes32("SETTLE");bytes32 public constant OP_COMMAND_BUY    = bytes32("BUY");
```

Command

Purpose

`FETCH`

Return current weather JSON for a city (testing and dApp display).

`SETTLE`

Fetch daily rainfall for a policy and return a signed settlement payload.

`BUY`

Decrypt ECIES-encrypted private policy terms and return attested parameters.

### Policy Lifecycle[​](#policy-lifecycle "Direct link to Policy Lifecycle")

A `Policy` struct stores coverage metadata on-chain:

contracts/WeatherInsurance.sol

```
struct Policy {    address policyholder;    string date;                // "YYYY-MM-DD"    uint256 rainThresholdMmE2;  // trigger threshold in mm x 100    uint256 payout;    uint256 premium;    uint256 measuredMmE2;       // set on settle    bool settled;    bool paidOut;    bool isPrivate;             // threshold held in TEE until settlement    bytes32 termsCommitment;    string lat;    string lon;    uint64 settlementUnlockAt;  // 00:00 UTC day after coverage date}
```

Settlement unlock time is computed by the `SettlementTime` library — the earliest `requestSettlement` / `settle` call is midnight on the day after the coverage date.

### Buying a Policy[​](#buying-a-policy "Direct link to Buying a Policy")

**Public buy** — all terms are visible on-chain:

contracts/WeatherInsurance.sol

```
function buyPolicy(    string calldata _date,    uint256 _rainThresholdMmE2,    uint256 _payout,    uint256 _premium,    string calldata _lat,    string calldata _lon) external returns (uint256 policyId)
```

The caller must `approve` the contract for `_premium` in `payToken` before calling. The contract pulls the premium, reserves `_payout` from pool liquidity, and records the policy.

**Private buy** — coverage terms stay encrypted on-chain:

1.  The buyer ECIES-encrypts ABI-encoded `PrivateBuyParams` with the Flare Confidential Compute extension public key.
2.  The `buyPolicyPrivate` function sends the ciphertext as a `WEATHER` / `BUY` instruction.
3.  After the extension processes the instruction, the buyer calls `relayPrivateBuy` with the signed result.
4.  The contract verifies the TEE signature via `ecrecover` against `teeAddress` and creates the policy.

### Settlement Flow[​](#settlement-flow "Direct link to Settlement Flow")

From `settlementUnlockAt` onward, a keeper triggers settlement:

contracts/WeatherInsurance.sol

```
function requestSettlement(uint256 _policyId) external payable {    // ...    bytes memory message = abi.encode(SettleMessage({        policyId: _policyId,        contractAddr: address(this),        date: p.date,        lat: p.lat,        lon: p.lon,        termsCommitment: p.termsCommitment    }));    // sendInstructions with opCommand = SETTLE}
```

Anyone can then call `settle()` with the TEE-signed `ActionResult` that is returned by the extension. The contract reconstructs `ActionResult.Hash()`, recovers the signer, and requires that the signer equal the registered `teeAddress`. If measured precipitation meets the threshold, `payToken` is transferred to the policyholder.

### TEE Signature Verification[​](#tee-signature-verification "Direct link to TEE Signature Verification")

Both `relayPrivateBuy` and `settle` verify TEE results using the same pattern:

```
bytes32 resultHash = keccak256(    abi.encodePacked(        keccak256(_resultData),        _actionId,        keccak256(bytes(_submissionTag)),        _status    ));address signer = _recover(_ethSigned(resultHash), _signature);require(signer == teeAddress, "bad TEE signature");
```

The TEE node signs `ActionResult.Hash()` with the [EIP-191 personal-sign](https://eips.ethereum.org/EIPS/eip-191) prefix. Only successful results are accepted.

### Extension ID Discovery[​](#extension-id-discovery "Direct link to Extension ID Discovery")

After the extension is registered on-chain, call `setExtensionId()` once to discover and cache the extension ID — same pattern as the [Private Key Extension](/fcc/guides/sign-extension).

## Off-chain Handler[​](#off-chain-handler "Direct link to Off-chain Handler")

The off-chain handler lives in `internal/extension/`. It exposes an HTTP server with two endpoints:

-   **`GET /state`** — reports whether the OpenWeatherMap API key is configured.
-   **`POST /action`** — receives TEE actions and routes them by `opType` / `opCommand`.

Constants match the Solidity contract:

internal/config/config.go

```
const (    OPTypeWeather   = "WEATHER"    OPCommandFetch  = "FETCH"    OPCommandSettle = "SETTLE"    OPCommandBuy    = "BUY")
```

### FETCH Handler[​](#fetch-handler "Direct link to FETCH Handler")

The `processWeatherFetch` function ABI-decodes a city name, calls the OpenWeatherMap API, and returns a JSON `WeatherReport` in `ActionResult.Data`:

internal/extension/extension.go

```
func (e *Extension) processWeatherFetch(action teetypes.Action, df *instruction.DataFixed) teetypes.ActionResult {    req, err := structs.Decode[types.GetWeatherRequest](types.GetWeatherMessageArg, df.OriginalMessage)    // ...    report, err := fetchWeather(req.City)    payload, err := json.Marshal(report)    return buildResult(action, df, payload, 1, nil)}
```

### SETTLE Handler[​](#settle-handler "Direct link to SETTLE Handler")

The `processWeatherSettle` function fetches daily precipitation from the OpenWeatherMap API and ABI-encodes the settlement:

internal/extension/extension.go

```
encoded, err := types.SettlementResultArgs.Pack(    req.ContractAddr,    req.PolicyId,    precipMmE2,    coverageDate,    revealedThreshold,    triggered,)return buildResult(action, df, encoded, 1, nil)
```

For private policies, the handler loads coverage terms from in-enclave memory using `termsCommitment` as the key.

### BUY Handler[​](#buy-handler "Direct link to BUY Handler")

The `processWeatherBuy` function decrypts ECIES ciphertext via the TEE node's `/decrypt` endpoint, validates `PrivateBuyParams`, stores terms in memory, and returns ABI-encoded parameters for `relayPrivateBuy`:

internal/extension/extension.go

```
plaintext, err := decryptViaNode(e.signPort, df.OriginalMessage)params, err := structs.Decode[types.PrivateBuyParams](types.PrivateBuyParamsArg, plaintext)// ...e.privateTerms[commitment] = paramsencoded, err := types.PrivateBuyResultArgs.Pack(params.Holder, params.ContractAddr, ...)return buildResult(action, df, encoded, 1, nil)
```

## Deploying and Testing on Coston2[​](#deploying-and-testing-on-coston2 "Direct link to Deploying and Testing on Coston2")

This walkthrough deploys a **local simulated TEE** against the real Coston2 chain using Docker and an `ngrok` tunnel. For production deployment on a Google Cloud Platform Confidential Space VM, see `DEPLOYMENT_STEPS.md` in the repository.

### Step 0: Activate local simulated mode[​](#step-0-activate-local-simulated-mode "Direct link to Step 0: Activate local simulated mode")

```
./scripts/use-chain.sh local coston2
```

This copies `.env.local.coston2` to `.env`, setting `SIMULATED_TEE=true` and `LOCAL_MODE=false`.

### Step 1: Configure deployer keys and API key[​](#step-1-configure-deployer-keys-and-api-key "Direct link to Step 1: Configure deployer keys and API key")

Edit `.env.local.coston2` and set your funded Coston2 credentials and OpenWeatherMap key:

.env.local.coston2

```
DEPLOYMENT_PRIVATE_KEY="<your-funded-coston2-private-key-hex-no-0x>"INITIAL_OWNER="0x<your-address>"OPENWEATHERMAP_API_KEY="<your-openweathermap-api-key>"PAY_TOKEN="0x53192e788991AD96bC180249B15AefB94E597dD1"
```

The `PAY_TOKEN` variable is the address of a mocked [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token on Coston2 — used for premiums and payouts during testing. You can use this token for testing purposes or deploy your own ERC-20 token.

Re-activate after editing:

```
./scripts/use-chain.sh local coston2
```

### Step 2: Deploy contract and register extension[​](#step-2-deploy-contract-and-register-extension "Direct link to Step 2: Deploy contract and register extension")

```
./scripts/pre-build.sh./scripts/extension-setup.sh
```

The `pre-build.sh` script compiles Solidity, deploys `WeatherInsurance`, and registers the extension on-chain. It writes `EXTENSION_ID` and `INSTRUCTION_SENDER` to `config/extension.env`.

The `extension-setup.sh` script calls `setPayToken` on the deployed contract.

warning

Once `config/extension.env` exists, the pre-build step refuses to run again. Use `./scripts/pre-build.sh --force` only when you intentionally want a new extension.

### Step 3: Start the ngrok tunnel[​](#step-3-start-the-ngrok-tunnel "Direct link to Step 3: Start the ngrok tunnel")

In a separate terminal:

```
ngrok http 6674
```

Copy the HTTPS URL and set it in `.env.local.coston2`:

.env.local.coston2

```
EXT_PROXY_URL="https://<your-ngrok-domain>"
```

Re-activate:

```
./scripts/use-chain.sh local coston2
```

### Step 4: Configure the indexer database[​](#step-4-configure-the-indexer-database "Direct link to Step 4: Configure the indexer database")

```
cp config/proxy/extension_proxy.coston2.docker.toml.example \   config/proxy/extension_proxy.coston2.docker.toml
```

Edit the `[db]` block with Coston2 indexer credentials — same process as the [Private Key Extension](/fcc/guides/sign-extension#step-4-configure-the-indexer-database).

Flare Indexer Access

To get the indexer credentials, please get in touch with us via [support](https://flare.network/resources/technical-support) or [X](https://x.com/FlareDevs) and share what you are building.

### Step 5: Start the extension stack[​](#step-5-start-the-extension-stack "Direct link to Step 5: Start the extension stack")

```
./scripts/start-services.sh
```

Wait for the proxy to become healthy:

```
until curl -sf http://localhost:6674/info >/dev/null 2>&1; do sleep 2; doneecho "Extension proxy is ready"
```

### Step 6: Verify the proxy[​](#step-6-verify-the-proxy "Direct link to Step 6: Verify the proxy")

```
curl -s "$EXT_PROXY_URL/info" | jq '.machineData'
```

For a simulated TEE, expect the same `codeHash` and `extensionId` pattern as the sign extension.

### Step 7: Register the TEE machine[​](#step-7-register-the-tee-machine "Direct link to Step 7: Register the TEE machine")

```
./scripts/post-build.sh./scripts/extension-post-setup.sh
```

The `post-build.sh` script allows the TEE version and registers the machine.

The `extension-post-setup.sh` script reads the TEE signing address from the proxy and calls `setTeeAddress` on the `WeatherInsurance` contract.

### Step 8: Run the end-to-end test[​](#step-8-run-the-end-to-end-test "Direct link to Step 8: Run the end-to-end test")

```
./scripts/test.sh
```

The test does the following:

1.  Funds the payout pool with WPT.
2.  Buys a private policy (ECIES-encrypted terms).
3.  Relays the TEE-signed buy result via the `relayPrivateBuy` function on the contract.
4.  Requests settlement and waits for the TEE to fetch OpenWeatherMap data.
5.  Calls the `settle` function on the contract with the signed result and verifies the payout.

If the test passes, your extension is fully operational.

## Web Frontend (Optional)[​](#web-frontend-optional "Direct link to Web Frontend (Optional)")

The repository includes a Next.js dApp in the `frontend/` directory for wallet connect, policy purchase (public or private), settlement, and live weather fetch.

After the extension stack is running and `config/extension.env` exists:

```
cp frontend/.env.local.example frontend/.env.local# Set NEXT_PUBLIC_WEATHER_INSURANCE from INSTRUCTION_SENDER in config/extension.env# Set EXT_PROXY_URL to http://127.0.0.1:6674 for local UIcd frontend && npm install && npm run dev
```

Open the application in your browser, connect MetaMask on **Coston2** (chain ID 114), and fund **C2FLR** and the mocked ERC-20 token for premiums and payouts.

The UI proxies TEE `/info` and `/action/result` through Next.js API routes to avoid browser CORS issues.

## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting")

**`OPENWEATHERMAP_API_KEY is not set` in TEE logs**

Set the key in `.env` and re-run `start-services.sh`.

**Proxy won't start or DB sync error**

Check the proxy logs and verify DB credentials in `config/proxy/extension_proxy.coston2.docker.toml`:

```
docker compose logs ext-proxy
```

**`MachineManager.TooMany()` during test**

The extension ID in `config/extension.env` does not match the on-chain TEE record. Run a [full reset](#cleanup) and start again, or keep the existing `config/extension.env` and re-run `post-build.sh` and `test.sh`.

**`bad TEE signature` on settle or relay**

Confirm `extension-post-setup.sh` ran successfully and `teeAddress` on the contract matches the registered TEE machine.

**`Verification.ChallengeExpired`**

Re-run `post-build.sh`.

**`ngrok` URL changed**

1.  Update `EXT_PROXY_URL` in `.env.local.coston2` and re-run `use-chain.sh`.
2.  Restart the Docker stack: `./scripts/stop-services.sh && ./scripts/start-services.sh`.
3.  Re-run `post-build.sh`, `extension-post-setup.sh`, and `test.sh`.

## Cleanup[​](#cleanup "Direct link to Cleanup")

**Stop the Docker stack**

```
./scripts/stop-services.sh
```

**Full reset**

```
./scripts/stop-services.shdocker compose down --rmi localrm -f .env config/extension.env config/proxy/extension_proxy.coston2.docker.toml
```

After a full reset, start again from [Step 0](#step-0-activate-local-simulated-mode).

What's next?

Read the [Flare Confidential Compute (FCC) overview](/fcc/overview) for more information on how to build and deploy TEE extensions on Flare. Compare with the [Private Key Extension](/fcc/guides/sign-extension) for a simpler introduction to the instruction routing pattern.
