Most blockchain indexing systems today assume a very simple model:
all blockchain data is public, and indexing is just a deterministic transformation of public events.
This assumption works well for analytics platforms like dashboards, DEX trackers, or portfolio tools.
However, it breaks down in real-world scenarios where:
- event data is sensitive (wallet behavior, internal flows, private classifications)
- indexing logic itself is proprietary (risk models, scoring systems, compliance logic)
- or you need selective disclosure (some users can see more data than others)
In traditional architectures, you are forced to choose between:
- transparency (but no privacy)
- privacy (but no verifiability)
Oasis Sapphire introduces a third option:
execute indexing logic inside a confidential runtime (TEE-backed), while still using standard EVM interfaces.
This tutorial builds a confidential event-driven indexer, where:
- raw blockchain events remain public
- classification logic is hidden
- only sanitized outputs are exposed externally
Think of it as:
“The Graph, but the mapping function is private and policy-controlled.”
Why This Is Architecturally Different
To understand why this matters, let's compare the standard indexing pipeline:
Traditional Indexing (The Graph-style)
Smart Contract -> Events -> Indexer -> Public Database -> API
Everything is:
- deterministic
- reproducible
- fully observable
This is great for trustlessness, but it creates problems:
- any adversary can reconstruct your analytics logic
- wallet behavior is fully exposed
- proprietary scoring models cannot exist
Confidential Indexing (Oasis Sapphire model)
Smart Contract -> Events
↓
Sapphire Confidential Runtime
↓
Private State Transformation
↓
Offchain Worker + Storage
↓
Sanitized Public API
Key difference:
The transformation function (indexing logic) is no longer public.
Real-World Use Case
We will build a confidential DeFi activity indexer that:
- tracks swaps on a DEX
- computes internal risk scores
- classifies activity (low / medium / high risk)
- hides wallet-level behavior
- exposes only aggregated metrics
This is useful for:
- institutional compliance dashboards
- private DeFi analytics platforms
- MEV-aware monitoring systems
- regulated crypto infrastructure
Why Not Just Use Existing Indexers?
Existing solutions like The Graph or custom ETL pipelines have a fundamental limitation:
they cannot hide the transformation logic itself.
Even if you obfuscate the backend:
- input events are public
- transformations are reproducible
- reverse-engineering is trivial
With Sapphire:
- logic executes in a TEE (Trusted Execution Environment)
- computation is not externally observable
- only final outputs are revealed
Project Structure (Production-Grade)
confidential-indexer/
│
├── contracts/
│ ├── Dex.sol
│ ├── ConfidentialIndexer.sol
│ ├── interfaces/
│
├── worker/
│ ├── indexer.ts
│ ├── eventDecoder.ts
│ ├── riskEngine.ts
│ ├── db.ts
│
├── api/
│ ├── server.ts
│ ├── routes/
│
├── scripts/
│ ├── deploy.ts
│
├── test/
│ ├── indexer.test.ts
│
└── hardhat.config.ts
Step 1: Event Source Contract (DEX Simulation)
We start with a simple decentralized exchange emitting swap events.
This is intentionally minimal, the complexity happens in the indexing layer.
// contracts/Dex.sol
pragma solidity ^0.8.20;
contract Dex {
event Swap(
address indexed user,
address indexed tokenIn,
address indexed tokenOut,
uint256 amount,
uint256 timestamp
);
function swap(
address tokenIn,
address tokenOut,
uint256 amount
) external {
emit Swap(
msg.sender,
tokenIn,
tokenOut,
amount,
block.timestamp
);
}
}
Key idea:
We deliberately include structured data so the indexer can apply richer classification logic.
Step 2: Confidential Indexer Contract (Sapphire Core Logic)
This is the core of the system.
Key properties:
- runs inside Sapphire confidential runtime
- decrypts event payloads
- applies private scoring rules
- stores encrypted internal state
// contracts/ConfidentialIndexer.sol
pragma solidity ^0.8.20;
import "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol";
contract ConfidentialIndexer {
struct SwapData {
address user;
address tokenIn;
address tokenOut;
uint256 amount;
uint256 timestamp;
}
struct PoolState {
uint256 riskScore;
uint256 volume;
}
mapping(bytes32 => PoolState) private pools;
event Indexed(bytes32 indexed poolId, uint256 riskScore, uint256 volume);
function processSwap(bytes calldata encryptedSwap) external {
// Step 1: decrypt inside TEE
bytes memory decrypted = Sapphire.decrypt(encryptedSwap);
SwapData memory data = abi.decode(decrypted, (SwapData));
// Step 2: derive pool identifier
bytes32 poolId = keccak256(
abi.encodePacked(data.tokenIn, data.tokenOut)
);
// Step 3: update private state
PoolState storage state = pools[poolId];
state.volume += data.amount;
uint256 risk = computeRisk(data, state);
state.riskScore = risk;
// Step 4: emit only sanitized output
emit Indexed(poolId, state.riskScore, state.volume);
}
function computeRisk(
SwapData memory data,
PoolState memory state
)
internal
pure
returns (uint256)
{
uint256 score = 0;
// Large trade heuristic
if (data.amount > 1_000_000 ether) {
score += 5;
}
// Volume spike heuristic
if (state.volume > 10_000_000 ether) {
score += 3;
}
return score;
}
}
Why this is important:
The key innovation is:
the risk model is never observable externally
That means:
- adversaries cannot game your scoring system
- analytics logic is not reproducible
- you can build proprietary indexers
Step 3: Event Worker (Offchain Index Aggregator)
This component listens to Sapphire outputs and stores them.
Unlike traditional indexers, it does NOT compute logic, only stores results.
// worker/indexer.ts
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const contract = new ethers.Contract(
process.env.CONTRACT_ADDRESS,
ABI,
provider
);
contract.on("Indexed", async (poolId, riskScore, volume) => {
console.log("Indexed update received");
await db.upsert({
poolId: poolId.toString(),
riskScore: riskScore.toString(),
volume: volume.toString(),
updatedAt: Date.now()
});
});
Step 4: Public API Layer (Sanitized Exposure)
This layer ensures no raw or sensitive data leaks.
// api/server.ts
import express from "express";
const app = express();
app.get("/pool/:id", async (req, res) => {
const pool = await db.get(req.params.id);
res.json({
pool: req.params.id,
riskScore: pool.riskScore,
volume: pool.volume,
// intentionally no user-level data exposed
granularity: "aggregated"
});
});
app.listen(3000);
Key Design Insight: Privacy Boundary Model
This architecture introduces a strict separation:
| Layer | Visibility |
|---|---|
| Smart contract events | Public |
| Sapphire execution logic | Confidential |
| Risk model / heuristics | Hidden |
| Aggregated outputs | Public (sanitized) |
Hard Engineering Problems (Important Section)
1. Determinism vs Confidential Execution
TEE environments must remain deterministic:
- same input -> same output
- but without revealing internal computation steps
2. Trust in Index Correctness
Because computation is hidden:
- you rely on enclave guarantees
- or multi-worker validation systems
3. Composability Limitations
Unlike The Graph:
- results are not universally reproducible
- external contracts cannot verify raw computation
4. State Replay Problem
Encrypted state introduces challenges:
- historical recomputation is non-trivial
- debugging requires replay tooling inside TEE
Why This Matters
This pattern enables a new category of systems:
- private blockchain analytics
- institutional DeFi infrastructure
- MEV-resistant monitoring tools
- compliance-aware data pipelines
It shifts the paradigm from:
“everything is transparent and verifiable”
to:
“computation is verifiable, but not necessarily observable”
Final Thoughts
Most blockchain infrastructure assumes transparency is always beneficial.
But in real systems, especially financial and institutional contexts:
- privacy is required
- analytics are proprietary
- and data exposure is a liability
Oasis Sapphire enables a third design space:
verifiable execution with confidential logic
This indexer pattern is just one example of what becomes possible when indexing itself becomes programmable and private.
Top comments (0)