coopdevs / coopdevs/timeoverflow
Federation via Credit Commons Protocol: Cross-Bank Time Exchange
- Dominant language
- Ruby
- Stars
- 170
- Forks
- 75
- PR merge metrics
- No merged PRs in 30d
Description
# Federation via Credit Commons Protocol: Cross-Bank Time Exchange
## Summary
This issue proposes implementing the [Credit Commons Protocol](https://credit-commons.gitlab.io/credit-commons-documentation/) (CC) in TimeOverflow to enable cross-bank exchanges between independent TimeOverflow instances — and potentially with any other CC-compatible mutual credit system — without a centralised database.
---
## Background & Motivation
TimeOverflow is deployed as independent instances (one per time bank). Members of one bank currently cannot exchange time with members of another bank. This is a fundamental limitation for the movement.
Several approaches have been considered:
- **Blockchain**: Rejected. The "trustless" property is a category error for time banks, which are inherently trust-based communities. Operational burden on small orgs is prohibitive, and time credits are not fungible tokens.
- **Centralised federation hub**: Works (CES uses this model with "Clearing Central") but creates a single point of failure and political control.
- **Signed receipts + PKI (bilateral)**: Sound approach, but reinvents a wheel that Credit Commons already designs well.
- **Credit Commons Protocol**: ✅ Best fit. Designed exactly for this problem, has a published OpenAPI spec (v0.9.0), an active reference implementation in PHP, and a growing community.
The Credit Commons whitepaper (Slater & Jenkin, 2016) describes the model as *"a money system that is locally controlled but globally useful"* — which maps directly to TimeOverflow's architecture.
---
## What is the Credit Commons Protocol?
Credit Commons is an open protocol for federated mutual credit accounting. Key properties:
- **Tree structure**: Ledgers are arranged in a fractal tree (trunk → branches → twigs → leaves). Each TimeOverflow instance would be a *twig*; its members are *leaves*.
- **No shared database**: Each node maintains its own ledger. Only mirror accounts (one per peer connection) need to stay synchronised.
- **Cryptographic ratchet**: Node-to-node integrity is maintained via a hash chain on mirror accounts. The last hash serves as both authentication and sync check — no PKI certificates needed.
- **Two-phase transactions**: A validate phase (nothing written) followed by a write phase (on user confirmation). Handles distributed atomicity cleanly.
- **Sovereign nodes**: Each bank keeps full autonomy over its membership rules, balance limits, and governance. The protocol doesn't prescribe policy.
- **Protocol, not platform**: Like HTTP, any compliant implementation can participate. A Rails implementation of CC would be a significant contribution to the wider movement.
The protocol is described in detail at https://credit-commons.gitlab.io/credit-commons-documentation/ and the OpenAPI 3.0 spec is at https://gitlab.com/credit-commons-software-stack/cc-php-lib.
---
## API Overview (v0.9.0)
The spec has ~12 endpoints split into two layers:
### Client layer (user-facing, consumed by TimeOverflow UI)
| Method | Path | Purpose |
|--------|------|---------|
| GET | `/forms` | List available transaction workflows |
| GET | `/accounts` | Autocomplete account names across the tree |
| GET | `/account` | Account stats (balance, volume, trades) |
| GET | `/account/history` | Balance history for charts |
| GET | `/transactions` | Filter/list transactions |
| GET | `/entries` | Filter/list transaction entries |
| GET | `/transaction/{uuid}` | Retrieve a single transaction |
| GET | `/about` | Node info: conversion rate, stats, currency format |
### Core transversal layer (node-to-node federation)
| Method | Path | Purpose |
|--------|------|---------|
| POST | `/transaction` | Client proposes a transaction |
| POST | `/transaction/relay` | Node-to-node relay (the core federation endpoint) |
| PATCH | `/transaction/{uuid}/{dest_state}` | Advance transaction through workflow states |
**For a minimal viable implementation, only 3 endpoints are essential**: `POST /transaction/relay`, `PATCH /transaction/{uuid}/{dest_state}`, and `GET /about`.
---
## Authentication
Two security schemes:
- **HumanUser**: JWT bearer token with scopes (`passive`, `active`, `helper`, `admin`).
- **OtherNode**: Custom `Last-hash` header — the last hash from the mirror account's hash chain. This is simultaneously authentication AND integrity check. No certificates or OAuth required.
```
# Example node-to-node request header
Last-hash: sha256:a3f9c2...
```
If the hash doesn't match, the server returns a `HashMismatch` `CCFailure` and the connection is considered broken.
---
## Transaction Lifecycle
```
# 1. Client proposes (Alice@BankA paying Bob@BankB)
POST /transaction
body: { payer: "alice", payee: "bankb/bob", quant: 2.0,
description: "Garden help", workflow: "+|PPC-PE-CE=" }
# 2. BankA relays to BankB (validate phase — nothing written yet)
POST /transaction/relay [Last-hash: ]
→ 200 + { secs_valid_left: 300 } # validated, held temporarily
# 3. Alice confirms
PATCH /transaction/{uuid}/C
→ 201 # written on all nodes
# On failure:
POST /transaction/relay
→ 500 + { errors: [{ class: "HashMismatch", node: "bankb" }] }
```
### Workflow DSL
Transactions carry a compact workflow code, e.g.:
| Code | Meaning |
|------|---------|
| `+\|PPC-PE-CE=` | Payee credits payer, payer confirms |
| `-\|PPC-PE+CE-` | Payer bills payee, payee approves |
| `0_CCE=` | Admin creates transaction directly |
The typical timebanking flow (`+|PPC-PE-CE=`) maps cleanly to TimeOverflow's existing offer/confirm model.
---
## Implementation Proposal
### Scope: Twig-only (Phase 1)
Each TimeOverflow instance acts as a **twig node**. Members are leaves. A lightweight trunk node (which could be another TimeOverflow instance in trunk mode, or a thin standalone service) manages the inter-bank meta-ledger. This mirrors the CES/Clearing Central model but with a proper CC-compliant protocol.
This is deliberately conservative: get bilateral cross-bank exchange working first, then expand to multi-bank tree routing.
### Data Model Changes
```ruby
# New model: FederationPeer
# Represents a trusted peer node (another time bank)
create_table :federation_peers do |t|
t.string :name, null: false
t.string :base_url, null: false
t.string :last_hash_sent, null: false, default: ''
t.string :last_hash_received, null: false, default: ''
t.float :conversion_rate, null: false, default: 1.0
t.string :status # active / suspended / pending
t.timestamps
end
# New columns on transactions table:
add_column :transactions, :cc_uuid, :string
add_column :transactions, :cc_state, :string # P/V/C/E/X
add_column :transactions, :cc_workflow, :string
add_column :transactions, :is_transversal, :boolean, default: false
add_column :transactions, :federation_peer_id, :references
# New model: IntertradinAccount (one per FederationPeer)
# A virtual member account whose balance = net position with that peer.
# Sum of all intertrading accounts should always net to zero.
```
### New Routes
```ruby
# config/routes.rb
namespace :credit_commons, path: 'cc' do
get 'about' # node discovery
get 'accounts' # account autocomplete
get 'account' # account stats
post 'transaction/relay' # node-to-node relay
get 'transaction/:uuid', action: :show
patch 'transaction/:uuid/:dest_state', # state transitions
action: :transition
end
```
### New Service Objects
```
app/services/credit_commons/
hash_ratchet.rb # Compute, store, verify last_hash per peer
transaction_relay.rb # Outbound relay to peer nodes (HTTP client)
workflow_parser.rb # Decode workflow DSL strings
address_resolver.rb # Resolve "othertimebank/alice" paths
node_client.rb # Wraps Faraday/Net::HTTP calls to peer nodes
```
### New Controllers
```
app/controllers/credit_commons/
relay_controller.rb # POST /cc/transaction/relay
# PATCH /cc/transaction/:uuid/:state
about_controller.rb # GET /cc/about
account_controller.rb # GET /cc/account
```
### Middleware
A `CreditCommons::NodeAuthMiddleware` rack middleware validates the `Last-hash` header on all `/cc/*` node-to-node requests, updates the ratchet on success, and returns a `HashMismatch` CCFailure on mismatch.
### Integration Point in Existing Flow
When a user creates a transaction and the payee's address contains a `/` (indicating a remote account, e.g. `othertimebank/bob`), the existing transaction creation flow branches:
```ruby
# app/services/transaction_creator.rb (existing, modified)
if payee.transversal?
CreditCommons::TransactionRelay.new(transaction, peer).call
else
# existing local flow unchanged
end
```
---
## Known Hard Problems
The CC protocol documentation itself flags these as open/unsolved in v1:
1. **Transaction ordering**: Two simultaneous transactions from different nodes may arrive at a mirror account in different order, breaking the ratchet. Proposed mitigation: trunkward node determines write order (requires a serialisation buffer).
2. **Node offline mid-write-phase**: If a peer goes offline between validate and write, the transaction is in an ambiguous state. Mitigation: idempotent writes + a catch-up/reconciliation endpoint (not yet in spec).
3. **Rounding errors**: Conversion between units at different decimal precision. Mitigation: trunkward node determines the authoritative amount.
For Phase 1 (bilateral, low-volume time banks), problems 1 and 2 are low-risk and can be handled with simple retry logic and manual admin reconciliation.
---
## What We Are NOT Doing (Yet)
- Multi-hop tree routing (Phase 1 is bilateral / single-trunk only)
- Conversion rates (time is time: 1 hour = 1 hour everywhere)
- Business logic microservice (no fees, no automatic entries)
- A full governance/voting UI for federation agreements
---
## Relationship to Existing CC Ecosystem
- **Reference implementation**: `cc-node` (PHP). We would implement the protocol in Rails independently, but against the same OpenAPI spec — meaning TimeOverflow nodes would be interoperable with cc-node deployments and any other CC-compatible system.
- **Contact**: Matthew Slater ([@matslats](https://twitter.com/matslats)) is the protocol author and actively seeking implementors. A Rails implementation would be a significant contribution to the CC community.
- **Mutual Credit Services** (https://mutualcredit.services) is building commercial deployments on CC — potential collaboration/testing partner.
---
## Suggested Implementation Order
1. `GET /cc/about` — simplest endpoint, enables node discovery, good first PR
2. `FederationPeer` model + `HashRatchet` service — foundation for everything else
3. `POST /cc/transaction/relay` (inbound) — receive transactions from peers
4. `PATCH /cc/transaction/:uuid/:state` — write phase / state transitions
5. Outbound relay in transaction creation flow
6. `GET /cc/account`, `GET /cc/accounts` — needed for cross-bank member lookup in UI
7. Admin UI for managing federation peer agreements
---
## References
- [Credit Commons whitepaper](https://gitlab.com/credit-commons/credit-commons-documentation) — Slater & Jenkin, 2016
- [CC Protocol documentation](https://credit-commons.gitlab.io/credit-commons-documentation/)
- [OpenAPI spec v0.9.0](https://gitlab.com/credit-commons-software-stack/cc-php-lib)
- [Reference implementation (cc-node, PHP)](https://gitlab.com/credit-commons-software-stack/cc-node)
- [Community Exchange Systems](https://www.community-exchange.org) — prior art for intertrading
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.