Agentic RWA index underwriter for tokenised equities on Robinhood Chain.
A mandate is a machine-readable object, not a PDF. It is validated, canonicalised and hashed, so the index it describes has a cryptographic identity. Rebalances are planned deterministically off-chain, committed as a hash, and only then executed — the vault refuses any leg set that does not match what was committed.
- Site: https://fiduciaindex.com
- Agent: https://app.virtuals.io/virtuals/139254
- Token:
$FIDUCIAon Robinhood Chain —0x84E9De94f7916D923Ded19aeE98FfA217423feAF
This repository is a protocol design plus a reference implementation. Being straight about what is and is not real:
| Component | State |
|---|---|
packages/mandate — deterministic core |
Runs. 46 tests passing. Validation, RFC 8785 canonicalisation, weight solving, integer NAV/drift, planHash |
packages/contracts — Solidity |
Written, complete. Not deployed. No Solidity tests yet |
packages/db — Postgres schema |
Written: 12 tables via Drizzle. Migration chain incomplete |
apps/api — Fastify service |
Partial. 3 of 9 route modules present |
apps/agents — 5 ACP agents |
Written against an AcpPort interface. No ACP adapter exists, so no agent runs |
apps/web — dashboard |
Scaffolding fragments. Does not render |
landing — marketing site |
Live at fiduciaindex.com |
The $FIDUCIA token is live on Virtuals. No token utility is implemented in
this repo — all protocol economics are denominated in USDG. Nothing here is
deployed to mainnet, and no ACP job has been created or settled.
packages/mandate is self-contained and has no dependency beyond viem and
zod. It is the piece the on-chain guard depends on.
cd packages/mandate
pnpm install
node --experimental-strip-types --test test/*.test.ts # 46 tests
node --experimental-strip-types examples/deterministic-plan.tsThe example walks a full cycle on fixed input — no network, no keys, no RPC — so it produces identical output on any machine:
1. MANDATE IDENTITY mandateHash 0xe3420dcc… (827 canonical bytes)
2. TARGET WEIGHTS NVDA raw 29.05% -> 24.53%, cap binds, sum exactly 10000 bps
3. CURRENT BOOK NAV 10000 USDG, NVDA +11.47% off target
4. REBALANCE PLAN drift 1147 -> 0 bps, turnover 2294 bps, 5 legs
planHash 0x07061e2f…
5. DETERMINISM 100 rebuilds -> identical planHash
6. TAMPER DETECTION halve one minAmountOut -> 0x76c1fff7… (vault reverts)
swap one calldata blob -> 0xfbccc8be… (vault reverts)
The interesting property is that a keeper is trusted to submit a rebalance but never trusted to choose it.
mandate document
│ validate (Zod) -> canonicalise (RFC 8785 JCS) -> keccak256
▼
mandateHash ────────────────────────────► MandateRegistry (append-only)
IndexFactory CREATE2 salt
holdings + oracle prices + signals
│ solveWeights cap/floor water-fill, sums to exactly 10000 bps
│ computeNav integer only, no floats anywhere near money
│ buildPlan minimum notional that clears the drift band
▼
legs[] ──► planHash = keccak256(abi.encode(legs))
│
├─ commitRebalance(planHash) block N
│
└─ executeRebalance(legs) block N+1
recomputes the hash and reverts PlanMismatch on any diff
Because swapData is inside the hash rather than passed alongside it, a keeper
cannot commit one plan and execute another with substituted router calldata.
IndexVault adds three further checks that do not depend on trusting the keeper:
drift must strictly decrease (driftAfter > driftBefore reverts), slippage is
checked against the oracle as well as minAmountOut (which a lying keeper could
otherwise set to zero), and settlement requires an independent evaluator's EIP-712
attestation.
packages/mandate deterministic core — validation, canonicalisation, planning
packages/contracts IndexVault, IndexFactory, MandateRegistry, NavOracle,
AttestationHub, StockTokenRegistry
packages/db Drizzle schema
packages/chain chain clients and ports (AcpPort has no adapter yet)
apps/api Fastify API
apps/agents underwriter, evaluator, rebalancer, indexer, fee
apps/web dashboard (incomplete)
landing marketing site — live
Integer arithmetic throughout. NAV, drift and every leg amount are bigint.
The Postgres schema uses a custom numeric(78,0) type rather than a float column.
Money never touches IEEE 754.
Canonicalisation is not cosmetic. Two byte-different but semantically identical mandates must hash identically, or an index becomes unaddressable by its own author. Addresses are lowercased and schema defaults are materialised before hashing; both are covered by tests.
Minimum-notional sizing. A rebalance trades the least amount that brings every name inside the drift band, rather than moving everything to target. Lower turnover, same mandate compliance.
The honest priority list: an AcpPort adapter so the agents can actually run,
Foundry tests plus a deployment to Robinhood Chain, and a cross-language check
that TypeScript planHash and Solidity keccak256(abi.encode(legs)) agree on
identical bytes. The tuple order is written to match and is covered by a test
against an independent encoding, but it has never been verified against a live
contract.
MIT