# Vaultfire V3 — Protocol Specification (PR2)

**Version:** V3 draft spec
**Author role:** Lead protocol architect
**Prepared:** 2026-08-20
**Repo HEAD:** `a80144d1acbbbbf24b9eca7cd91e61daad0c0ad6`

This document is the normative spec for Vaultfire V3. Every contract implemented in PR3–PR6 must satisfy this spec, and every invariant listed here has a failing Foundry test filed under `v3-src/test/invariants/` in PR2. No implementation code lives in this PR.

Rules being enforced:
- Additive namespace: `V3` contracts, events, ABIs, routes, SDK modules are all separately named. No V2 collision.
- No touching V2. All migration is voluntary.
- Pause preserves withdrawals.
- Rewards never come from bond principal.
- Initial-pilot bond settlements are principal-only; no reward or owner share
  may be drawn from bond principal.
- Signature replay impossible.

---

## 1. Scope and boundaries

**In-scope V3 components (7 modules):**

| # | Module | Contracts | Purpose |
|---|---|---|---|
| M1 | Identity + Mandate Registry | `ERC8004IdentityRegistryV3`, `MandateRegistryV3`, `VaultfireNameServiceV3`, `LegacyKYAAdapter` | Who an agent is, what actions they're authorized to sign. |
| M2 | Bonds | `SafeBondFactoryV3`, `PartnershipBondV3Safe`, `AccountabilityBondV3Safe`, `BondMathLib`, `BondStateEnum`, `ForceExitLib`, `RewardProgramV3` | Two-party stakes with mutual consent + oracle-attested settlement. |
| M3 | Reputation | `StreetCredV3`, `ERC8004ReputationRegistryV3`, `ERC8004ValidationRegistryV3`, `VaultfireERC8004AdapterV3Safe`, `ReputationDecayV3` | Explainable, versioned reputation with slashing. |
| M4 | Escrow + Vaults | `TaskEscrowV3`, `MutualAidPoolV3`, `VaultfireBIPVaultV3`, `VaultfireBondInsurancePoolV3` | Value custody with timeouts and refunds. |
| M5 | Cross-chain | `VaultfireTeleporterBridgeV3`, `TrustPortabilityExtensionV3` | Signed cross-chain messaging. |
| M6 | Oracle + Attestation | `MultiOracleConsensusV3`, `FlourishingMetricsOracleV3`, `BeliefOracleV3`, `ProductionBeliefAttestationVerifierV3`, `DilithiumAttestorV3` | Off-chain truth injection. |
| M7 | Governance | `MultisigGovernanceV3`, `TimelockControllerV3` | All money-moving admin routes here. |

**Out of scope:** V2 contracts (untouched), Solana programs (V3 still Solana-read-only), any UI beyond feature-flagged V3 pages, any pilot for chains other than Base.

---

## 2. The 15 mandatory invariants

Each invariant has a failing Foundry test in `v3-src/test/invariants/InvXX_*.t.sol` written in PR2 before any V3 fix code is written.

### Inv01 — Assets cover liabilities

For every V3 contract holding value, at every block:
```
assetsHeld(chain, address) ≥ Σ open_liabilities(bondId)
```
Failing test: `Inv01_AssetsCoverLiabilities.t.sol` — deposits N bonds, does random distributions/challenges, asserts native + ERC-20 balance ≥ sum of `remainingStake + bondFund` per open bond.

### Inv02 — No orphan principal

Every unit of principal a user posts is either:
- Still recorded as their `remainingStake`, or
- Sent to a documented recipient via a `BondSettledV3(bondId, participant, amount, reason)` event.

There is no "vault holding assets with no bondId claim."
Failing test: `Inv02_NoOrphanPrincipal.t.sol` — fuzz N deposits and settlements; sum of principal in = sum of `remainingStake` + sum of `BondSettledV3.amount`.

### Inv03 — Mutual consent required

`PartnershipBondV3Safe.createBond` requires **both** counterparties to sign an EIP-712 struct with matching `bondId`, `stakeAmount`, `partnershipType`, `deadline`. Neither party can bind the other without a fresh signature.
Failing test: `Inv03_MutualConsent.t.sol` — attempt to create bond with only one signature ⇒ revert. Attempt with mismatched fields ⇒ revert.

### Inv04 — No signature replay

Every signature carries `(chainId, verifyingContract, bondId, nonce, deadline)`. Nonces are consumed. Replay across chains impossible.
Failing test: `Inv04_NoSignatureReplay.t.sol` — sign, submit, submit again ⇒ revert. Same signature on chain A used on chain B ⇒ revert. Expired ⇒ revert.

### Inv05 — No self-report payout

A caller cannot both submit the settlement input AND trigger the payout in the same call, nor within an atomic block, nor without an oracle confirmation. `settlementInputsFrozen[bondId]` must be `true` before `distributeBond` can succeed, and only the oracle (via multi-oracle consensus) can flip that flag.
Failing test: `Inv05_NoSelfReportPayout.t.sol` — participant calls `submitMetrics` then `distributeBond` in same block ⇒ revert unless oracle confirmed.

### Inv06 — Initial-pilot bond payout ≤ collateral

For every pilot bond, sum of all `BondSettledV3` credits for that bond ID equals
the funded collateral. Bond terms reject all nonzero reward hashes and reward
program IDs, partnership deployment rejects a reward-program binding, and
partnership settlement requires a zero `partnershipShareBps`. No owner,
treasury, or `RewardProgramV3` address receives bond principal.
`RewardProgramV3` remains an independent, separately funded module and has no
bond-settlement integration in the initial pilot.
Failing test: `Inv06_TotalPayoutBounded.t.sol` — try to settle a nonzero
partnership share or draw a reward from bond principal ⇒ revert.

### Inv07 — Governance cannot withdraw pending funds

`MultisigGovernanceV3` has no path to move any bond's `remainingStake` or `bondFund` for a bond that is not in `TerminalSettled` or `TerminalCancelled` state.
Failing test: `Inv07_GovCannotWithdrawPending.t.sol` — governance calls any conceivable path ⇒ revert.

### Inv08 — Settlement inputs frozen at settlement time

Once `distributeBond` starts, all inputs read from oracles/metrics are copied to memory and used consistently. No re-read within the settlement path. No oracle can flip a value mid-settlement.
Failing test: `Inv08_SettlementInputsFrozen.t.sol` — install malicious oracle, race distribution; assert no observable value change during the tx.

### Inv09 — Time-bounded terminal path

Every bond and every escrow task has a deadline after which either party can call `forceExit` to move to `TerminalCancelled` and unlock principal for withdrawal. No indefinite lock.
Failing test: `Inv09_TimeBoundedTerminalPath.t.sol` — advance time past deadline; anyone can call forceExit; principal recoverable.

### Inv10 — Upheld challenges persist

If a challenge is upheld, it is recorded permanently. Distribution cannot proceed without accounting for it.
Failing test: `Inv10_UpheldChallengesPersist.t.sol` — challenge upheld ⇒ subsequent distribution reverts or accounts for slashing.

### Inv11 — Pause preserves withdrawals

`whenNotPaused` never guards a withdrawal path. Withdrawals of principal are always callable.
Failing test: `Inv11_PausePreservesWithdrawals.t.sol` — governance pauses; user calls withdraw ⇒ success.

### Inv12 — No unbounded loops

No external function iterates over unbounded participant lists, metrics arrays, or challenge arrays. Explicit pagination on all reads; explicit bounds on all writes.
Failing test: `Inv12_NoUnboundedLoops.t.sol` — try to submit 10_000 metrics; either succeeds with paginated interface or reverts with `BoundExceeded`.

### Inv13 — Private evidence caps = public evidence caps

If a bond enables private evidence (ZK-attested), the maximum weight private evidence can carry equals the maximum weight equivalent public evidence carries. No preference for opaque data.
Failing test: `Inv13_PrivatePublicParity.t.sol` — attempt to boost weight by using private path ⇒ same result as equivalent public submission.

### Inv14 — Forced transfers cannot violate accounting

Even if someone sends native tokens or ERC-20 directly to a bond contract (donation), the accounting is not affected. `assetsHeld` may exceed `Σ liabilities`; the surplus is skimmed to the protocol treasury via an explicit function that only governance can call, never auto-credited to a bond.
Failing test: `Inv14_ForcedTransferSafe.t.sol` — donate ETH; assert no bond field changes; assert surplus is skimmable only by governance.

### Inv15 — Self-bonds don't count

An address cannot create a bond where both `human` and `aiAgent` (or both counterparties) resolve to the same identity via the mandate registry. Sybil attempts to self-fund reputation are rejected.
Failing test: `Inv15_NoSelfBonds.t.sol` — same address as both parties ⇒ revert. Two addresses controlled by the same mandate ⇒ revert.

---

## 3. Contract-level specifications

### 3.1 MandateRegistryV3

Purpose: single source of truth for "who signed on whose behalf." Introduces mandate objects that map a principal (an ERC-8004 identity) to a set of authorized signers under an explicit scope (`createBond`, `distributeBond`, `challenge`, `settle`) with a bounded time window.

Storage:
```solidity
struct Mandate {
    address principal;      // ERC-8004 identity holder
    address signer;         // key allowed to act on principal's behalf
    bytes32 scope;          // keccak256("createBond") etc.
    uint64  notBefore;
    uint64  notAfter;
    uint96  nonce;          // consumed on use
    bool    revoked;
}
mapping(bytes32 mandateId => Mandate) public mandates;
```

Functions:
- `registerMandate(Mandate calldata m, bytes calldata sig)` — principal signs EIP-712.
- `revokeMandate(bytes32 mandateId)` — principal or `MultisigGovernanceV3`.
- `checkMandate(bytes32 mandateId, address caller, bytes32 scope) view returns (bool)` — used by every bond/escrow entry point.
- `consumeMandate(bytes32 mandateId)` — increments nonce, blocks replay.

Events:
- `MandateRegisteredV3(bytes32 indexed mandateId, address indexed principal, address indexed signer, bytes32 scope, uint64 notBefore, uint64 notAfter)`
- `MandateRevokedV3(bytes32 indexed mandateId, address indexed by)`

ERC-1271 support:
- If `principal` is a smart-contract wallet, verification falls back to `IERC1271.isValidSignature`.

### 3.2 SafeBondFactoryV3

Purpose: deploy `PartnershipBondV3Safe` and `AccountabilityBondV3Safe` with well-known constructor invariants.

Constructor guarantees:
- Star bond size < 24_576 bytes (P0-2 fix via `--via-ir` + `BondMathLib` extraction).
- Owner set to `MultisigGovernanceV3` at deploy time; single-EOA ownership is impossible.
- Non-zero addresses for consensus and dispute registry; the bond owner is
  fixed at deployment. For the initial pilot, `PartnershipBondV3Safe` requires
  a zero reward-program address.

### 3.3 PartnershipBondV3Safe

Bond lifecycle uses an explicit enum:
```solidity
enum BondState {
    Uninitialized,
    Pending,          // one party signed, waiting on the other
    Active,           // both signed, principal escrowed
    Challenged,       // an upheld challenge is pending resolution
    SettlementFrozen, // oracle inputs locked, distribution imminent
    TerminalSettled,
    TerminalCancelled // via forceExit or mutual cancel
}
```

Transitions are one-way; every transition emits `BondStateChangedV3(uint256 indexed bondId, BondState from, BondState to, address indexed by, uint256 timestamp)`.

Initial-pilot settlement is limited to the two signed parties:
`partnershipShareBps` must be zero during settlement and its rounding remainder
is credited to party B. The typed terms preserve
`flourishRewardConditionsHash` and `rewardProgramId` only for ABI
compatibility; both must be zero and cannot represent a reward commitment.

### 3.4 AccountabilityBondV3Safe

Same state enum, plus explicit `mission` field and `oracleAttestation` requirement. Fixes P0-4 (double-counted `remainingStake`) and P0-5 (missing `nonReentrant` on `requestDistribution`) by design.

### 3.5 BondMathLib

Pure library with no state. Extracts:
- `calculateBondValue(uint256 principal, uint256 humanBps, uint256 aiBps, uint256 partnershipBps)` returns split.
- `applyMultiplier(uint256 base, uint256 multiplierBps)` — capped at 300%.
- `assertShareBpsSumEq10000(...)` — reverts if sums don't add to 10000.

Purpose: bring star bond size below the 24 KB deployment limit (P0-2).

### 3.6 RewardProgramV3

Purpose: an independent, pre-funded reward module. It is not bound to
`PartnershipBondV3Safe` or `AccountabilityBondV3Safe` during the initial
pilot. `RewardProgramV3.claim(bondId, participant)` transfers ONLY from its
own treasury.

Invariant: `bondFund` never decreases due to a reward claim. Enforced structurally by never granting `RewardProgramV3` access to bond storage.

### 3.7 StreetCredV3

Explainable reputation. Every score is a struct:
```solidity
struct Score {
    uint32 version;         // schema version
    uint32 scoreBps;        // 0..10000
    uint32 evidenceCount;
    bytes32 lastEvidenceRoot; // Merkle root of contributing attestations
    uint64 lastUpdated;
    bool sybilFlagged;
}
```

`updateScore` only accepted from `ERC8004ValidationRegistryV3` or from a mandate-authorized attestor. Sybil-flagged addresses cannot boost their own score.

### 3.8 ERC8004ValidationRegistryV3

Purpose: reputation slashing with slashed-ETH treasury. Fixes R3 HIGH-R3-07 (locked slashed stake).

Slashed stake goes to `treasury` address (multisig-controlled). Never locked, never burned by default.

### 3.9 TaskEscrowV3

USDC task escrow with timeout/refund. Adds explicit `deadline` and `forceRefund(taskId)` callable after deadline by either party. Fixes lack of terminal-path guarantee.

### 3.10 MutualAidPoolV3

Renamed from `AgentInsurancePool` — we don't have insurance semantics (no reserves, no actuarial pricing), so we don't market it as insurance. Payouts are capped, discretionary, and voted by governance. UI labels "Mutual Aid Pool — discretionary, not insurance."

### 3.11 VaultfireBIPVaultV3

ERC-4626 compliant vault. Property tests from OpenZeppelin reference suite must pass on mainnet fork before deploy.

### 3.12 MultisigGovernanceV3

Threshold N-of-M signers, timelock T. Fixes R3 MED-R3-08 (add `setThreshold` with 2-step confirmation), MED-R3-11 (add guardian rotation with cooldown), and provides the single ownership target for every V3 contract.

---

## 4. Event namespace

Every V3 event ends in `V3` OR has a distinct name from V2. Complete list of new V3 events (excerpt):

- `BondCreatedV3`, `BondStateChangedV3`, `BondSettledV3`, `BondForceExitedV3`
- `ChallengeSubmittedV3`, `ChallengeUpheldV3`, `ChallengeRejectedV3`
- `MetricsSubmittedV3`, `OracleMetricsConfirmedV3`
- `MandateRegisteredV3`, `MandateRevokedV3`, `MandateConsumedV3`
- `AttestationVerifiedV3`, `ReputationSlashedV3`, `ReputationDecayedV3`
- `RewardIssuedV3`, `RewardClaimedV3`
- `TaskCreatedV3`, `TaskBidPlacedV3`, `TaskAcceptedV3`, `TaskSettledV3`, `TaskRefundedV3`
- `MutualAidRequestedV3`, `MutualAidPaidV3`
- `ContractPausedV3`, `ContractUnpausedV3`
- `SurplusSkimmedV3(address indexed contract, uint256 amount, address indexed treasury)`
- `MigrationAcknowledgedV3(bytes32 indexed v2Ref, uint256 indexed bondIdV3, address indexed participant)`

Full list generated from `v3-src/contracts/**.sol` in PR3–PR6.

---

## 5. API namespace

Everything V3 lives under `/api/v3/*`:
- `/api/v3/protocol/solvency` — solvency evidence per contract, per block.
- `/api/v3/protocol/status` — pause state, upgrade state, kill switch.
- `/api/v3/protocol/version` — component versions.
- `/api/v3/agent/status` — combined V2+V3 status.
- `/api/v3/x402/trust/*` — priced trust reads (V3 shape).
- `/api/v3/x402/actions/*` — priced write actions (V3 signatures).
- `/api/v3/mandate/*` — mandate registry read.

Every response includes: `protocolVersion`, `contractStatus`, `blockNumber`, `blockHash`, `observedAt`, `finality`, `dataSourceErrors`, `schemaVersion`.

---

## 6. SDK namespace

New exports (side-by-side with V2):
- `VaultfireSDKv3`, `createVaultfireSDKv3`
- `BondsModuleV3`, `MandateModuleV3`, `RewardModuleV3`, `TasksModuleV3`, `TrustModuleV3`, `ReputationModuleV3`
- `pauseGate: boolean` — reads whether V3 is paused; refuses to submit writes when true.

Every module accepts a `version: "v3"` explicit argument. There is no auto-fallback from V3 to V2 or vice versa for writes.

---

## 7. Indexer

Runs two parallel decoders:
- V2 decoder — 178 event signatures at HEAD, keyed by `(chainId, address)`.
- V3 decoder — new signatures under the `V3` naming convention.

Every event lands with:
```
(chainId, txHash, logIndex) → normalized record
```
primary key. Reorg-safe. Removed-log-safe. Partial-RPC-failure-safe.

---

## 8. Website labels

Four allowed labels only:
- `Legacy V2` — active but deprecated
- `V3 test deployment` — testnet or unpaid pilot
- `V3 audited pilot` — post-audit, capped pilot
- `Read-only` — historical only

Every contract-touching UI element must render exactly one label. Fail CI if any component shows a contract without a label.

---

## 9. PR2 exit gate

- [x] Spec doc written.
- [x] 15 invariant test files exist, compile, and contain concrete V3 fixtures
  and assertions.
- [ ] `foundry.toml` committed with fork profiles.
- [ ] `v3-src/README.md` describes build + test commands.
- [ ] Branch `feat/vaultfire-v3-safe-migration` committed locally, PR2 tag applied, not pushed.

Explicit user approval required before PR3 (identity + mandate registry) can write any V3 fix code.
