A minimal Solidity reference implementation of the Pact specification using Foundry.
Pact is a specification for structured commitments between parties that require judgment to evaluate. Unlike purely deterministic smart contracts, Pacts handle subjective criteria like "quality of work" or "reasonable effort" through external resolvers (humans, AI, or DAOs).
Key principle: "If a resolver determines X was satisfied, then Y"
Pact sits between fully deterministic systems and completely subjective arrangements, providing:
- Minimal structure separating resolution, enforcement, and storage
- Resolver-agnostic evaluation (any entity can assess pacts)
- Composable outcomes (trigger transfers, gate access, chain to other pacts)
- Portable across on-chain, off-chain, or hybrid environments
pact-contracts/
├── src/
│ ├── interfaces/
│ │ ├── IPactRegistry.sol # Core pact interface
│ │ └── IResolver.sol # Resolver interface
│ ├── PactRegistry.sol # Main registry implementation
│ └── resolvers/
│ └── SimpleResolver.sol # Basic resolver (owner-controlled)
└── test/
└── PactRegistry.t.sol # Comprehensive test suite
Proposed → Active → Resolved
↓
Disputed → Resolved
States:
Proposed: Pact created, awaiting signaturesActive: Both parties signed, pact is in effectDisputed: Party raised a disputeResolved: Resolver determined the outcome
Each pact contains:
id: Unique identifier (keccak256 hash)parties: Client and provider addressestermsHash: IPFS hash or keccak256 of off-chain terms JSONresolver: Address of resolver contractstakeAmount: What's at risk (tracked, not escrowed)state: Current statesignatures: Whether each party signedresolution: Outcome once resolved
None: Not yet resolvedFulfilled: Provider met the termsBreached: Provider failed to meet termsPartial: Mixed outcome (resolver discretion)
// 1. Deploy registry and resolver
PactRegistry registry = new PactRegistry();
SimpleResolver resolver = new SimpleResolver(address(registry));
// 2. Create terms off-chain (JSON)
{
"description": "Build a website with 5 pages",
"acceptance_criteria": [
"All pages must be responsive",
"Must pass accessibility audit",
"Delivered within 30 days"
]
}
// Hash: 0xabc... (or IPFS: QmXyz...)
// 3. Client creates pact
bytes32 pactId = registry.createPact(
providerAddress,
0xabc..., // termsHash
address(resolver),
100 ether // stake amount (tracked, not transferred)
);
// 4. Both parties sign
registry.signPact(pactId); // client
registry.signPact(pactId); // provider → pact becomes Active
// 5. If needed, dispute
registry.disputePact(pactId); // either party
// 6. Resolver evaluates and resolves
resolver.submitResolution(
pactId,
IPactRegistry.Outcome.Fulfilled,
"ipfs://QmReasoning..." // detailed reasoning
);- ✅ Core pact lifecycle (propose, sign, activate, dispute, resolve)
- ✅ State machine enforcement
- ✅ Party-only and resolver-only access control
- ✅ Event emissions for key transitions
- ✅ Resolution tracking with reasoning hashes
- ✅ Simple resolver reference implementation
- ❌ No actual escrow/token transfers (just tracks amounts)
- ❌ No upgradability patterns (for simplicity)
- ❌ No governance (resolver is owner-controlled)
- ❌ No complex access control (just party/resolver checks)
- ❌ No automatic enforcement (pacts are commitment records)
This is a proof-of-concept to validate the spec on-chain, not a production-ready system.
Can implement IResolver for custom resolution logic:
contract AIResolver is IResolver {
function canResolve(bytes32 pactId) external view returns (bool) {
// Check if AI model can evaluate this pact type
}
function submitResolution(bytes32 pactId, Outcome outcome, string calldata reasonHash) external {
// Verify AI oracle signature
// Submit resolution to registry
}
}MIT