You are building plugins for ReineiraOS — open settlement infrastructure on Arbitrum. Plugins extend the protocol through two Solidity interfaces. This file is your complete reference.
- Code version: 0.1.0
- Platform version: 0.1
- Version config:
reineira.jsonat project root
Platform version is led by smart contract interfaces (IConditionResolver, IUnderwriterPolicy). When platform bumps (0.1 → 0.2), interface signatures may change and contracts generated by this version may be incompatible. Check MCP for current platform version.
A Hardhat project for building and deploying:
- Condition resolvers — contracts that control when escrows release funds
- Insurance policies — contracts that evaluate risk and judge disputes using FHE
Builders describe what they want. You generate the contract, tests, and deployment.
This repo is part of the ReineiraOS builder platform:
| Repo | Purpose |
|---|---|
| reineira-code (this repo) | Smart contracts — resolvers, policies, tests, deploy |
| reineira-atlas | Startup OS — strategy, ops, growth, compliance, pitch |
| platform-modules | App starters — backend, platform app, payment link |
- Stablecoin-agnostic — ConfidentialEscrow uses
IFHERC20, not hardcoded to any token - Cross-chain — CCTP v2 for USDC cross-chain transfers
- Contract addresses — query from MCP or protocol docs, never hardcode
contracts/
interfaces/ — protocol interfaces (read-only reference)
resolvers/ — IConditionResolver implementations (you create these)
policies/ — IUnderwriterPolicy implementations (you create these)
test/
resolvers/ — resolver test files
policies/ — policy test files
scripts/
deploy.ts — universal deploy script
deployments/ — deployment records (auto-generated)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IConditionResolver {
/// @notice Called on every redeem attempt. Return true to allow release.
/// @dev Must be a view function. No state changes. Keep gas low.
function isConditionMet(uint256 escrowId) external view returns (bool);
/// @notice Called once at escrow creation. Parse and store configuration.
/// @dev Called atomically during ConfidentialEscrow.create().
function onConditionSet(uint256 escrowId, bytes calldata data) external;
}Rules for resolvers:
isConditionMetMUST beview— no state changes, no gas surprisesonConditionSetruns once — validate inputs strictly, store config- Support ERC-165:
supportsInterface(type(IConditionResolver).interfaceId) - One escrow ID → one condition state (replay protection)
- Keep
isConditionMetgas under 50k — it's called on every redeem attempt
Resolver storage pattern:
mapping(uint256 => YourConfigStruct) public configs;
function onConditionSet(uint256 escrowId, bytes calldata data) external {
YourConfigStruct memory config = abi.decode(data, (YourConfigStruct));
// validate config fields
configs[escrowId] = config;
}
function isConditionMet(uint256 escrowId) external view returns (bool) {
YourConfigStruct memory config = configs[escrowId];
// evaluate condition against config
return /* condition logic */;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { euint64, ebool } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
interface IUnderwriterPolicy {
/// @notice Called when coverage is purchased. Store policy-specific data.
function onPolicySet(uint256 coverageId, bytes calldata data) external;
/// @notice Return an encrypted risk score (0-10000 basis points).
/// @dev 100 bps = 1% premium. Score determines buyer's premium.
function evaluateRisk(uint256 escrowId, bytes calldata riskProof) external returns (euint64 riskScore);
/// @notice Judge a dispute. Return encrypted boolean (true = valid claim).
function judge(uint256 coverageId, bytes calldata disputeProof) external returns (ebool valid);
}Rules for policies:
- Return values MUST be FHE-encrypted (
euint64,ebool) - Always call
FHE.allowThis(value)andFHE.allow(value, msg.sender)on return values - Support ERC-165:
supportsInterface(type(IUnderwriterPolicy).interfaceId) - Risk score: 0-10000 basis points (100 = 1%, 500 = 5%, 1000 = 10%)
judge()receives arbitrary proof bytes — decode and validate them
FHE pattern (MUST follow exactly):
import { FHE, euint64, ebool } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
function evaluateRisk(uint256, bytes calldata) external returns (euint64) {
uint64 score = 500; // 5% premium
euint64 encrypted = FHE.asEuint64(score);
FHE.allowThis(encrypted); // allow this contract to use value
FHE.allow(encrypted, msg.sender); // allow caller (protocol) to use value
return encrypted;
}
function judge(uint256, bytes calldata) external returns (ebool) {
bool isValid = true;
ebool encrypted = FHE.asEbool(isValid);
FHE.allowThis(encrypted);
FHE.allow(encrypted, msg.sender);
return encrypted;
}import { ERC165 } from '@openzeppelin/contracts/utils/introspection/ERC165.sol';
// For resolvers:
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return interfaceId == type(IConditionResolver).interfaceId || super.supportsInterface(interfaceId);
}
// For policies:
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return interfaceId == type(IUnderwriterPolicy).interfaceId || super.supportsInterface(interfaceId);
}Do not hardcode addresses in generated contracts. Query addresses from:
- MCP server —
mcp__reineira__get_contractsfor live addresses per network - Protocol docs —
reineira.xyz/docs/reference/contracts - Deployment records —
deployments/arbitrumSepolia.jsonfor your own deployed contracts
Key protocol contracts available on Arbitrum Sepolia:
- ConfidentialEscrow — core escrow engine (IFHERC20, stablecoin-agnostic)
- CCTPV2EscrowReceiver — cross-chain deposit receiver
- PolicyRegistry — underwriter policy registration
- ConfidentialCoverageManager — insurance coverage manager
- PoolFactory — insurance pool creation
- OperatorRegistry, TaskExecutor, FeeManager — infrastructure
- TrustedForwarder — ERC-2771 meta-transactions
- Chainlink feeds, Reclaim Verifier, UMA OO V3 — external verification
Resolvers can verify any condition. Common patterns:
- Proves HTTPS endpoint returned expected data (PayPal, Stripe, bank APIs)
- Proof verified on-chain via Reclaim verifier (address from MCP or protocol docs)
- Pattern: buyer generates proof off-chain → submits to resolver → resolver verifies + marks fulfilled
- Storage:
mapping(uint256 => bool) public fulfilled+mapping(bytes32 => bool) public usedProofs
- Reads price feeds deployed natively on Arbitrum
- Feed addresses: query MCP or protocol docs (e.g. ETH/USD has 8 decimals)
isConditionMetcallsIChainlinkFeed.latestRoundData()and checks threshold- Always validate staleness:
require(block.timestamp - updatedAt <= maxStaleness) - Interface:
function latestRoundData() returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80)
- Resolves binary or numeric outcomes via Optimistic Oracle
- UMA OO V3 address: query MCP or protocol docs
isConditionMetcallshasPrice()thengetPrice()and compares to required outcome- Identifier:
bytes32(e.g.,keccak256("YES_OR_NO_QUERY")) - Resolution values:
1e18= YES,0= NO
- N-of-M approval pattern
- Storage:
mapping(uint256 => mapping(address => bool)) public approvals isConditionMetcounts approvals >= thresholdonConditionSetstores signers array and threshold
- Simplest pattern:
block.timestamp >= deadline onConditionSetstores deadline, validates it's in the future
import hre from 'hardhat';
import { expect } from 'chai';
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { time } from '@nomicfoundation/hardhat-network-helpers';
describe('MyResolver', function () {
async function deployFixture() {
const resolver = await hre.viem.deployContract('MyResolver');
return { resolver };
}
it('should store config on onConditionSet', async function () {
const { resolver } = await loadFixture(deployFixture);
const data = hre.viem.encodePacked(/* your config encoding */);
await resolver.write.onConditionSet([0n, data]);
// assert stored state
});
it('should return false before condition met', async function () {
const { resolver } = await loadFixture(deployFixture);
expect(await resolver.read.isConditionMet([0n])).to.equal(false);
});
it('should return true after condition met', async function () {
const { resolver } = await loadFixture(deployFixture);
// trigger condition
expect(await resolver.read.isConditionMet([0n])).to.equal(true);
});
});import hre from 'hardhat';
import { ethers } from 'hardhat';
import { expect } from 'chai';
describe('MyPolicy', function () {
before(async function () {
// REQUIRED: initialize FHE mock backend
const [signer] = await ethers.getSigners();
await hre.cofhe.initializeWithHardhatSigner(signer);
});
it('should return encrypted risk score', async function () {
const policy = await hre.viem.deployContract('MyPolicy');
// evaluateRisk returns encrypted value — test that it doesn't revert
await policy.write.evaluateRisk([0n, '0x']);
});
it('should judge disputes', async function () {
const policy = await hre.viem.deployContract('MyPolicy');
const proof = ethers.AbiCoder.defaultAbiCoder().encode(['bool', 'uint256'], [true, Math.floor(Date.now() / 1000)]);
await policy.write.judge([0n, proof]);
});
});import { time } from '@nomicfoundation/hardhat-network-helpers';
// Advance block timestamp
await time.increaseTo(deadline);
// Advance by duration
await time.increase(86400); // 1 dayimport { ethers } from 'hardhat';
// Encode single value
const data = ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [deadline]);
// Encode struct-like data
const data = ethers.AbiCoder.defaultAbiCoder().encode(
['address', 'int256', 'bool', 'uint256'],
[feedAddress, threshold, aboveOrBelow, maxStaleness],
);
// Encode string
const data = ethers.AbiCoder.defaultAbiCoder().encode(['string'], ['MERCHANT_ID_123']);Deploy any contract:
npx hardhat run scripts/deploy.ts --network arbitrumSepoliaThe deploy script prompts for the contract name and saves to deployments/arbitrumSepolia.json.
Environment variables needed (see .env.example):
PRIVATE_KEY=0x...
ARBITRUM_SEPOLIA_RPC_URL=https://...
ETHERSCAN_API_KEY=... # for contract verification
ESCROW_ADDRESS=... # query from MCP or protocol docs
POLICY_REGISTRY_ADDRESS=... # query from MCP or protocol docs
After deploying a resolver, attach it to an escrow via the SDK:
import { ReineiraSDK } from '@reineira-os/sdk';
const sdk = ReineiraSDK.create({
network: 'testnet',
privateKey: process.env.PRIVATE_KEY,
});
await sdk.initialize();
const resolverData = ethers.AbiCoder.defaultAbiCoder().encode(
['uint256'], // your resolver's expected data format
[1700000000], // your resolver's config values
);
const escrow = await sdk.escrow
.build()
.amount(sdk.stablecoin(1000)) // stablecoin-agnostic — escrow accepts any IFHERC20 token
.owner('0xRecipient...')
.condition('0xYourDeployedResolver...', resolverData)
.create();When reviewing generated contracts, verify:
-
isConditionMetisview— no state changes -
onConditionSetvalidates all inputs (non-zero addresses, valid ranges, future deadlines) - ERC-165
supportsInterfaceis implemented - No reentrancy vectors in
onConditionSetorsubmitProof - Replay protection: proof hashes tracked, escrow IDs mapped 1:1
- External calls in
isConditionMetuse known contract addresses, not user-supplied - Oracle data freshness validated (check
updatedAttimestamps) - FHE values always have
FHE.allowThis()+FHE.allow(value, msg.sender)called - No plaintext secrets stored on-chain
- Gas consumption of
isConditionMetis bounded (no unbounded loops)
- Pragma:
^0.8.24 - License:
SPDX-License-Identifier: MIT - Compiler: solc 0.8.25, EVM target: cancun, optimizer: 200 runs
- Imports: use
@openzeppelin/contracts/and@fhenixprotocol/cofhe-contracts/ - Error handling: use custom errors over require strings for gas efficiency
- Events: emit events on state changes in
onConditionSetand proof submission - NatSpec: document all public functions with
@noticeand@param
- Making
isConditionMetnon-view — it must beview. State changes here will cause the protocol to revert. - Forgetting FHE.allowThis() — without this, the protocol can't read your encrypted return value. The transaction will succeed but the value will be unusable.
- Not validating
onConditionSetinputs — this runs once at escrow creation. If invalid data gets stored, the escrow is permanently broken. - Trusting user-supplied addresses in view calls — never call
IChainlinkFeed(userAddress).latestRoundData()whereuserAddresscomes fromresolverData. Store the feed address inonConditionSetand validate it. - No replay protection for proof-based resolvers — if a proof can be submitted twice, the resolver is broken. Track
usedProofs[keccak256(proof)]. - Unbounded gas in
isConditionMet— this is called on every redeem attempt. Iterating over arrays or making multiple external calls can make redemption prohibitively expensive.