DIFPv0.4
Djowda Interconnected Food Protocol — an open protocol for food ecosystem coordination across farmers, factories, wholesalers, stores, restaurants, and end consumers, anchored to Earth's surface via a universal spatial grid.
Almost one billion people face food insecurity every year — not because the world doesn't produce enough food, but because the coordination infrastructure between producers and consumers is broken. Farmers don't know who needs what. Stores don't know what's available nearby. Surplus rots while scarcity persists — sometimes in the same city.
DIFP is a proposed solution to this coordination problem. It defines a common language for every actor in the food ecosystem — from seed providers to delivery riders — to discover each other, signal availability, and complete exchanges in real time, anchored to a precise geographic cell on Earth's surface.
This specification is intentionally open. Any developer, cooperative, government agency, or NGO may implement DIFP. No royalties. No gatekeeper. A single open standard means a Tunisian marketplace and an Indian cooperative can interoperate with zero extra integration work — because they speak the same protocol.
— Abstract
DIFP is a lightweight, open, spatial food coordination protocol. It specifies:
| Module | Description |
|---|---|
| Participant identity | How food ecosystem actors identify themselves globally without a central registry |
| Spatial addressing | How Earth's surface is divided into ~500m × 500m cells, each acting as a coordination zone |
| Presence and discovery | How participants announce and find each other within and across cells |
| Trade message format | A universal structure for orders, asks, and donations between any two participants |
| Protocol federation | How independent DIFP implementations discover and interoperate with each other |
DIFP is transport-agnostic. The reference implementation uses Firebase Realtime Database. Conformant implementations may use REST, WebSockets, MQTT, or any equivalent transport.
01 Terminology
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.
| Term | Definition |
|---|---|
Component | Any participant registered on a DIFP network (farmer, store, user, etc.) |
Cell | A ~500 × 500 m geographic zone identified by a numeric cell ID |
Cell ID | A 64-bit integer encoding the position of a cell in the global grid |
DID | Decentralized Identifier — a self-sovereign identity string of the form difp://{cellId}/{type}/{id} |
Trade | A structured message representing an order, ask, or donation between two components |
Interactor | A software module handling the full discovery + trade lifecycle for one component pair |
Fan-out | An atomic multi-path write ensuring consistency across all affected data nodes |
PAD | Pre-loaded Application Data — static catalog content shipped with the app |
DIFP Node | A server or service implementing this specification |
Federation | The mechanism by which distinct DIFP Nodes discover and exchange data with each other |
02 Ecosystem Actors
DIFP defines ten canonical component types. Each type maps to a distinct role within the food chain. Implementations MUST support all ten types; they MAY add custom types using the extension mechanism defined in Section 11.
| Code | Actor | Role in Food Chain |
|---|---|---|
sp | Seed Provider | Supplies seeds and agricultural inputs to farmers |
f | Farmer | Primary producer — grows and harvests food |
fa | Factory | Processes raw produce into packaged goods |
w | Wholesaler | Aggregates and distributes in bulk to stores and restaurants |
s | Store | Retail point of sale to end consumers |
r | Restaurant | Prepares and serves food to consumers |
u | User | End consumer — orders from stores and restaurants |
t | Transport | Bulk logistics between any two non-consumer nodes |
d | Delivery | Last-mile delivery from store or restaurant to user |
a | Admin | Platform oversight — read access across all node types |
2.1 Supply Chain Flow
03 Spatial Addressing — The MinMax99 Grid
DIFP uses a deterministic global grid to assign every participant to a precise geographic zone. This is the foundational innovation of the protocol: proximity-first coordination replaces search-radius queries.
3.1 Grid Constants
All conformant implementations MUST use the following constants without modification:
// All conformant DIFP nodes MUST use these exact values EARTH_WIDTH_METERS = 40,075,000 // equatorial circumference EARTH_HEIGHT_METERS = 20,000,000 // meridian half-circumference CELL_SIZE_METERS = 500 // cell edge length NUM_COLUMNS = 82,000 // EARTH_WIDTH / CELL_SIZE NUM_ROWS = 42,000 // EARTH_HEIGHT / CELL_SIZE TOTAL_CELLS ≈ 3.44 × 10⁹ // 82,000 × 42,000
CELL_SIZE_METERS is not DIFP-conformant and will produce incompatible cell IDs.
3.2 GeoToCellNumber — Canonical Conversion
Any implementation MUST produce identical cell IDs from identical (latitude, longitude) inputs using the following algorithm:
function geoToCellNumber(latitude, longitude) → int64: // Step 1: longitude → x in meters (linear) x = (longitude + 180.0) × (EARTH_WIDTH_METERS / 360.0) // Step 2: latitude → y in meters (Mercator, origin top-left) y = (EARTH_HEIGHT_METERS / 2.0) − ln(tan(π/4 + toRadians(latitude) / 2)) × (EARTH_HEIGHT_METERS / (2π)) // Step 3: meters → integer cell indices xCell = floor(x / CELL_SIZE_METERS) yCell = floor(y / CELL_SIZE_METERS) // Step 4: clamp to grid bounds xCell = clamp(xCell, 0, NUM_COLUMNS − 1) yCell = clamp(yCell, 0, NUM_ROWS − 1) // Step 5: encode as single int64 return xCell × NUM_ROWS + yCell
3.3 Reference Test Vectors
All conformant implementations MUST produce these exact cellId values from the given inputs. If your port does not match, it is not DIFP-conformant.
{
"description": "Reference test vectors for DIFP geoToCellNumber",
"version": "0.1",
"constants": {
"EARTH_WIDTH_METERS": 40075000,
"EARTH_HEIGHT_METERS": 20000000,
"CELL_SIZE_METERS": 500,
"NUM_COLUMNS": 82000,
"NUM_ROWS": 42000
},
"vectors": [
{ "description": "Algiers, Algeria", "latitude": 36.7538, "longitude": 3.0588, "cellId": 1711767603 },
{ "description": "Paris, France", "latitude": 48.8566, "longitude": 2.3522, "cellId": 1705129761 },
{ "description": "Tokyo, Japan", "latitude": 35.6895, "longitude": 139.6917, "cellId": 2989365749 },
{ "description": "New York, USA", "latitude": 40.7128, "longitude": -74.0060, "cellId": 991131039 },
{ "description": "Gulf of Guinea (0,0 origin)", "latitude": 0.0, "longitude": 0.0, "cellId": 1683170000 },
{ "description": "Sydney, Australia", "latitude": -33.8688, "longitude": 151.2093, "cellId": 3097104003 },
{ "description": "São Paulo, Brazil", "latitude": -23.533773, "longitude": -46.625290, "cellId": 1247170691 },
{ "description": "Lagos, Nigeria", "latitude": 6.45407, "longitude": 3.39467, "cellId": 1714879281 },
{ "description": "North Pole (clamped)", "latitude": 90.0, "longitude": 0.0, "cellId": 1683150000 },
{ "description": "Antimeridian East", "latitude": 0.0, "longitude": 179.9999, "cellId": 3366278000 }
]
}
3.4 Reverse Lookup
function cellNumberToXY(cellId) → (xCell, yCell): xCell = floor(cellId / NUM_ROWS) yCell = cellId mod NUM_ROWS return (xCell, yCell)
3.5 Neighbor Resolution
A component with discovery radius r MUST query cells in a (2r+1) × (2r+1) square centered on its own cell:
function getNearbyCells(centerCellId, radius) → list<int64>: (xC, yC) = cellNumberToXY(centerCellId) result = [] for dx in [−radius .. +radius]: for dy in [−radius .. +radius]: x = clamp(xC + dx, 0, NUM_COLUMNS − 1) y = clamp(yC + dy, 0, NUM_ROWS − 1) result.add(x × NUM_ROWS + y) return result
04 Component Identity
DIFP uses a Decentralized Identifier (DID) scheme that requires no central authority. Any conformant implementation can generate and verify identities offline.
4.1 DID Format
// Schema difp://{cellId}/{typeCode}/{componentId} // Examples difp://3440210/f/ali-farm-01 // Farmer in cell 3,440,210 (Algiers) difp://3440210/s/safeway-dz-042 // Store in the same cell difp://1820044/fa/cevital-plant-1 // Factory in cell 1,820,044
4.2 Identity Registration
On first registration a DIFP Node MUST: (1) accept the participant's GPS coordinates, (2) compute the cell ID using geoToCellNumber, (3) assign a componentId unique within the node, (4) construct and store the full DID, (5) issue a signed identity token to the client.
4.3 Legacy Encoding (Firebase Reference Implementation)
Implementations using Firebase Auth MAY encode identity in the displayName field as a compact string:
// Format: "{cellId}_{typeCode}" "3440210_f" // Farmer in cell 3,440,210 "3440210_s" // Store in the same cell "1820044_fa" // Factory in cell 1,820,044
This encoding is specific to the Firebase reference implementation. All other inter-node communication MUST use the full DID format.
05 Presence & Discovery
Every registered component MUST publish a presence record. This record is the atomic unit of discovery — it lets any other participant find and evaluate a potential counterpart.
5.1 Presence Record Schema
PresenceRecord {
did: string // Full DIFP DID (required)
component_name: string // Display name (required)
phone_number: string // Contact number (required)
cell_id: int64 // Numeric cell ID (required)
component_type: string // Type code from Section 2 (required)
avatar_id: string // Reference to avatar asset (optional)
status: enum // open | closed | busy (required)
working_time: string // "HH:MM-HH:MM" local time (optional)
last_update: int64 // Unix timestamp ms (required)
user_id: string // Auth provider user ID (required)
is_asking: boolean // Currently broadcasting an Ask (optional)
is_donating: boolean // Currently broadcasting a Donation (optional)
}
5.2 Firebase Reference Node Layout
C/{typeCode}/{componentId}/ ← presence records
// Example:
C/f/ali-farm-01/
did: "difp://3440210/f/ali-farm-01"
component_name: "Ali's Farm"
cell_id: 3440210
status: "open"
...
cell_id index queries on Firebase.
06 Catalog System
DIFP separates item metadata (static) from item availability and pricing (live). This split dramatically reduces bandwidth and enables offline operation.
6.1 Item Identity
// Format: difp:item:{countryCode}:{category}:{itemSlug}:{version}
difp:item:dz:vegetables:tomato_kg:v1
difp:item:in:grains:basmati_rice_kg:v2
difp:item:fr:dairy:camembert_250g:v1
6.2 Static Item Record (PAD)
CatalogItem {
item_id: string // namespaced identifier
name: map<lang, string> // {"ar": "طماطم", "fr": "Tomate", "en": "Tomato"}
category: string // vegetables | grains | dairy | meat | ...
unit: string // kg | g | l | ml | piece | box | ...
thumbnail: string // asset reference (.webp)
gtin: string? // optional GS1 barcode for interoperability
}
Static records are distributed as a PAD — a compressed package (~6,000 items per country per component type) downloaded once at installation. The PAD MUST NOT change at runtime; updates arrive only via app updates.
6.3 Live Availability Record
LiveItem {
item_id: string // references CatalogItem.item_id
price: number // in local currency units
available: boolean
last_update: int64 // Unix timestamp ms
}
Only LiveItem records travel over the wire during normal usage. Implementations MUST NOT include item names or thumbnails in live sync payloads.
07 The Trade Protocol
A Trade is the fundamental coordination primitive of DIFP. All exchanges — commercial, charitable, or informational — are modeled as Trades between exactly two participants.
7.1 Trade Types
| Code | Type | Description |
|---|---|---|
o | Order | A purchase request — includes items, quantities, prices, and total |
a | Ask | A resource availability inquiry — no price; signals demand to nearby suppliers |
d | Donation | An offer to transfer resources at no cost — signals surplus to nearby receivers |
7.2 Trade Message Schema
TradeMessage {
// Routing
sId: string // Sender DID
sT: string // Sender type code
sC: string // Sender cell ID
rId: string // Receiver DID
rT: string // Receiver type code
rC: string // Receiver cell ID
// Trade metadata
ty: string // Trade type: o | a | d
st: string // Status: p | a | pr | c | x | dn
// Payload
items: map<item_id, OrderLine | 1>
// OrderLine = { q: number, p: number, u: string }
// For Ask/Donation: flat map { item_id: 1 }
total: number? // Order total — order type only
listSize: integer // Count of distinct items
// Timestamps
createdAt: int64
lastUpdated: int64
// Contact
info: { phone: string, address: string, comment: string }
dCause: string // Denial reason (if status = dn)
}
7.3 Status Lifecycle
7.4 Role-Based Transition Rules
| Actor | From | To |
|---|---|---|
| Sender | PENDING | CANCELLED |
| Receiver | PENDING | ACCEPTED |
| Receiver | PENDING | DENIED |
| Either | ACCEPTED | PROCESSING |
| Either | PROCESSING | COMPLETED |
08 Data Node Layout
This section describes the Firebase Realtime Database node structure used in the reference implementation. Other transports MUST map to equivalent structures.
8.1 Four-Node Pattern
8.2 Inbox / Outbox Summary Schema
TradeSummary {
fId: string // counterpart DID
fT: string // counterpart type code
ty: string // trade type
st: string // current status
pv: string // human-readable preview string
ls: integer // list size (item count)
lu: int64 // last update timestamp
}
8.3 Analytics Node Schema
TradeAnalytics {
createdAt: int64
acceptedAt: int64?
processingAt: int64?
completedAt: int64?
finalStatus: string
durationToAccept: integer // seconds
durationToComplete: integer // seconds
senderCell: string // for map route animation
receiverCell: string // for map route animation
}
09 Atomic Write Guarantees (Fan-Out)
Every state-changing operation in DIFP MUST update all affected nodes atomically. No intermediate state should be observable by other clients.
9.1 Trade Creation — Required Writes
// Atomic write set for createTrade(trade) TD/{tradeId} ← full canonical record T/{senderType}/{senderId}/o/{tradeId} ← sender outbox summary T/{receiverType}/{receiverId}/i/{tradeId} ← receiver inbox summary TA/{tradeId}/createdAt ← analytics seed TA/{tradeId}/senderCell ← routing cell TA/{tradeId}/receiverCell ← routing cell
9.2 Status Update — Required Writes
// Atomic write set for updateStatus(tradeId, newStatus) TD/{tradeId}/st ← canonical status TD/{tradeId}/lastUpdated ← timestamp T/{senderType}/{senderId}/o/{tradeId}/st ← mirror to sender T/{senderType}/{senderId}/o/{tradeId}/lu ← mirror timestamp T/{receiverType}/{receiverId}/i/{tradeId}/st ← mirror to receiver T/{receiverType}/{receiverId}/i/{tradeId}/lu ← mirror timestamp TA/{tradeId}/{statusTimestampKey} ← analytics event
updateChildren() — a batch write, database transaction, or two-phase commit — to guarantee these writes succeed or fail together.
10 Protocol Federation
DIFP is designed to be adopted by independent operators — NGOs, national food agencies, regional cooperatives. Federation enables these distinct nodes to interoperate without a central broker.
10.1 Well-Known Endpoint
GET https://{host}/.well-known/difp/info
Response:
{
"protocol": "DIFP",
"version": "0.3",
"nodeId": "string",
"coverage": [cellId, cellId, ...],
"contact": "email",
"federates": ["https://node2.example.com", ...]
}
10.2 Cell Presence Lookup
GET https://{host}/.well-known/difp/cell/{cellId}
Response: list of PresenceRecord for that cell
10.3 Cross-Node Trade Routing
When a sender on Node A initiates a trade with a receiver on Node B: Node A validates the receiver DID → packages the TradeMessage → POSTs to Node B's trade inbox endpoint → Node B delivers to receiver's inbox → all subsequent status updates are mirrored to both nodes.
POST https://{host}/.well-known/difp/trade/incoming
Body: TradeMessage (signed with sender node's key)
Response:
{
"accepted": true,
"tradeId": "string"
}
11 Conformance Requirements
11.1 MUST Implement
geoToCellNumber with exact constants from Section 3.1difp://{cellId}/{typeCode}/{componentId} as canonical identity scheme11.2 SHOULD Implement
getNearbyCells from Section 3.411.3 MAY Implement
12 Food Security Mandate
This section is normative for implementations that wish to display the DIFP Food Security Compliant badge.
12.1 Ask & Donation Support
Implementations MUST support the Ask (a) and Donation (d) trade types. These are the primary mechanism by which DIFP addresses food insecurity — they allow participants to signal demand and surplus without a commercial transaction.
12.2 No Gatekeeping on Food Donation
A DIFP Node MUST NOT require payment, subscription, or identity verification beyond basic registration before a component may broadcast a Donation trade. Donations are a human right, not a premium feature.
12.3 Offline Resilience
Implementations operating in low-connectivity regions SHOULD implement the PAD catalog system and local caching of PresenceRecords so that participants can at minimum browse nearby suppliers without an active connection.
12.4 Open Interoperability
Implementations MUST NOT introduce proprietary barriers that prevent a DIFP-conformant client from another node from discovering, contacting, or trading with their participants. Closed walled gardens are incompatible with the mission of DIFP.
15 Message Envelope
DIFP v0.2 introduces a universal message envelope — a fixed outer structure that wraps every message in the network, regardless of its content type, sender role, or transport layer. Every client, node, service, and IoT device speaks the same envelope. The payload is typed and varies; the envelope never does.
envelope = network behavior · payload = business logic. The envelope answers who, what, when, and how. The payload answers what specifically.
15.1 Envelope Schema (normative)
{
// Identity
"id": "string", // unique message ID — deduplication + tracing
"type": "string", // message type (e.g. "trade.ask") — determines payload schema
"version": "0.2", // protocol version — backward compatibility
// Sender
"from": {
"did": "string", // full DIFP DID — sender identity
"node": "string", // originating node ID — routing context
"publicKey": "string", // Ed25519 public key — signature verification
"role": "client | node | service | device"
},
// Routing
"target": {
"type": "cell | node | broadcast | direct",
"value": "string" // cell ID, node ID, or DID depending on type
},
"mode": "event | request | response",
"cell": "string", // sender's spatial cell — geographic routing anchor
// Timing & replay protection
"timestamp": "string", // ISO 8601 UTC
"ttl": 300, // seconds — prevents infinite propagation
"nonce": 1024, // monotonic per DID — prevents replay attacks
// Integrity & trust
"hash": "string", // SHA-256 of canonical(envelope_without_sig + payload)
"signature": "string", // Ed25519 sign(hash, privateKey)
// Tracing (optional)
"context": {
"traceId": "string", // cross-message trace chain
"parentId": "string" // parent message ID (for request→response linking)
},
// Business payload (schema determined by "type")
"payload": {}
}
15.2 Field Reference
| Field | Required | Purpose |
|---|---|---|
id | ✓ | Unique message ID for deduplication and tracing. Format: msg-{timestamp}-{random} |
type | ✓ | Dot-namespaced message type (e.g. trade.ask). Determines payload schema and node processing logic. |
version | ✓ | Protocol version. Nodes reject messages with incompatible versions. |
from.did | ✓ | Full DIFP DID of sender. Used for identity and routing. |
from.node | ✓ | Originating node ID. Used for federation routing and failover. |
from.publicKey | ✓ | Ed25519 public key for signature verification. Nodes cache this per DID. |
from.role | ✓ | Sender role. Determines processing rules and trust level. See Section 17. |
target.type | ✓ | Routing scope: cell, node, broadcast, or direct. |
target.value | ✓ | Routing destination: cell ID, node ID, or DID depending on target.type. |
mode | ✓ | Message intent: event (broadcast), request (expects reply), response (reply to request). |
cell | ✓ | Sender's geographic cell ID — the spatial routing anchor. All messages carry this regardless of target. |
timestamp | ✓ | ISO 8601 UTC. Nodes reject messages where timestamp > now + tolerance. |
ttl | ✓ | Seconds before message expires. Nodes MUST NOT forward messages with ttl ≤ 0. |
nonce | ✓ | Monotonically increasing integer per DID. Prevents replay attacks. |
hash | ✓ | SHA-256 of canonical JSON of (envelope_without_hash_and_sig + payload). Detects tampering. |
signature | ✓ | Ed25519 signature over hash. Guarantees authenticity. |
context | — | Optional. Enables message chain tracing for debugging and analytics. |
payload | ✓ | Type-specific business data. Schema defined by type value. See Section 16. |
15.3 Canonical Hashing (normative)
Nodes MUST compute hash identically. Any deviation produces an invalid signature. The canonical form is defined as:
// Step 1: construct the signable object signable = { ...envelope_fields_except_hash_and_signature, payload: payload } // Step 2: canonical JSON (keys sorted, no whitespace, UTF-8) canonical = JSON.stringify(signable, sortedKeys) // Step 3: hash hash = SHA256(canonical) // Step 4: sign signature = Ed25519.sign(hash, privateKey)
15.4 Full Message Example — trade.ask (Farmer → Cell)
{
"id": "msg-20260417-9f3a2b7c",
"type": "trade.ask",
"version": "0.2",
"from": {
"did": "difp://3440210/f/ali-farm-01",
"node": "node-oran-01",
"publicKey": "ed25519:7f8a9cABC...",
"role": "client"
},
"target": { "type": "cell", "value": "3440210" },
"mode": "event",
"cell": "3440210",
"timestamp": "2026-04-17T14:32:10Z",
"ttl": 300,
"nonce": 1024,
"hash": "sha256:98fa3c...",
"signature": "sig:ab34f9c...",
"context": { "traceId": "trace-abc123" },
"payload": {
// trade.ask payload — schema defined in Section 16 (coming in v0.2 payload spec)
}
}
15.5 Full Message Example — node.sync (Node → Node)
{
"id": "msg-20260417-sync-4a1c",
"type": "node.sync",
"version": "0.2",
"from": {
"did": "difp://3440210/a/node-oran-01",
"node": "node-oran-01",
"publicKey": "ed25519:nodePubKey...",
"role": "node"
},
"target": { "type": "node", "value": "node-algiers-02" },
"mode": "request",
"cell": "3440210",
"timestamp": "2026-04-17T14:33:00Z",
"ttl": 60,
"nonce": 2048,
"hash": "sha256:7bca12...",
"signature": "sig:cc91ab...",
"context": { "traceId": "trace-sync-001" },
"payload": {
// node.sync payload — defined in federation spec
}
}
16 Message Type System
All DIFP message types follow a dot-namespaced format: <domain>.<action>. The domain groups related messages; the action describes the intent. The type field determines which payload schema applies and how nodes process the message.
type values and their behavioral class.
16.1 Message Classes
| Class | Behavior | Examples |
|---|---|---|
| Command | Triggers an action on the receiving node | trade.accept, trade.cancel |
| Event | Broadcasts a state change — no response expected | presence.announce, trade.ask |
| Query | Expects a structured response | query.cell, query.resource |
| System | Node-to-node protocol messages | node.sync, node.ping |
16.2 Full Type Registry
| Domain | Type | Class | Description |
|---|---|---|---|
| identity | identity.register | Command | Register a new DID on the network |
identity.update | Command | Update DID metadata | |
identity.revoke | Command | Revoke a DID (e.g. decommission a node) | |
| presence | presence.announce | Event | Participant announces availability in a cell |
presence.update | Event | Update status or capabilities | |
presence.leave | Event | Participant leaves the cell or goes offline | |
| trade | trade.ask | Event | Broadcast demand signal — I need this resource |
trade.offer | Event | Broadcast supply signal — I have this resource | |
trade.donate | Event | Broadcast surplus at no cost | |
trade.accept | Command | Accept a pending trade | |
trade.reject | Command | Deny a pending trade | |
trade.complete | Command | Mark trade as completed | |
trade.cancel | Command | Cancel a pending trade (sender only) | |
| query | query.cell | Query | Discover participants in a cell or radius |
query.resource | Query | Find who has a specific resource available | |
query.actor | Query | Look up a specific DID's presence record | |
| radar | radar.snapshot | Query | Request supply/demand aggregate for a cell |
radar.update | Event | Node publishes updated radar data for a cell | |
| logistics | logistics.request | Event | Request transport or delivery capacity |
logistics.offer | Event | Offer available transport or delivery capacity | |
logistics.update | Event | Update logistics status | |
| node | node.announce | System | Node announces itself to the network |
node.sync | System | Request state sync from another node | |
node.ping | System | Health check between nodes | |
node.failover | System | Signal node going offline, transfer responsibility | |
| reputation | reputation.update | Event | Update reputation score after completed trade |
dispute.open | Command | Open a dispute on a trade | |
dispute.resolve | Command | Resolve a dispute | |
| sensor | sensor.report | Event | IoT device reports a reading (harvest ready, soil moisture, etc.) |
automation.trigger | Command | Automated action triggered by sensor threshold |
custom.myapp.action) and MUST NOT collide with any type in this registry.
17 Roles & Routing
The three new envelope fields — from.role, target, and mode — together define how a message flows through the network and how nodes decide to process it. One protocol, multiple actors, deterministic behavior.
17.1 from.role — Sender Role
Declares what kind of actor sent the message. Nodes use this to apply appropriate trust policies and processing rules.
| Role | Actor | Trust Level | Typical Message Types |
|---|---|---|---|
client | User-facing app (farmer app, store POS, NGO dashboard) | Standard — signature required | trade.*, presence.*, query.* |
node | DIFP server implementing this spec | Elevated — node key verified | node.*, radar.*, query.* |
service | Third-party service (analytics, AI, dashboard) | Standard — read-mostly | query.*, radar.* |
device | IoT sensor, automated system | Standard — device key verified | sensor.*, automation.*, trade.donate |
17.2 target.type — Routing Scope
Defines where the message should go and how far it propagates.
| Type | Value field contains | Propagation | Use case |
|---|---|---|---|
cell | Cell ID (int64 as string) | Node serves all participants in that cell + neighbors at configured radius | Geographic broadcast — trade.ask, presence.announce |
node | Node ID string | Routed directly to the named node only | Federation — node.sync, node.ping |
broadcast | "*" | All reachable nodes (TTL-limited) | Network-wide events — node.announce |
direct | Recipient DID | Routed to the specific DID's home node | Private messages — trade.accept, query.response |
17.3 mode — Message Intent
| Mode | Behavior | Response expected | Examples |
|---|---|---|---|
event | Fire-and-forget broadcast. Node stores and propagates. | No | trade.ask, presence.announce, sensor.report |
request | Expects a response message referencing this message's id via context.parentId. | Yes | query.cell, node.sync, node.ping |
response | Reply to a prior request. Must set context.parentId to the original message id. | No | query.response, node.sync reply |
17.4 Actor Flow Examples
"from.role": "client" "target.type": "cell" "mode": "event" "type": "trade.ask" → Node stores + propagates to cell neighbors. No response required.
"from.role": "node" "target.type": "node" "mode": "request" "type": "node.sync" → Target node processes + sends back mode:"response" with context.parentId set.
"from.role": "node" "target.type": "direct" "mode": "response" "type": "query.response" → Client receives result. context.parentId links to original query.cell request.
"from.role": "device" "target.type": "cell" "mode": "event" "type": "sensor.report" → Node receives, evaluates threshold, may auto-trigger trade.donate broadcast.
18 Node Processing Pipeline
Every DIFP node MUST implement the following processing pipeline for every received message. The steps are sequential and non-skippable. A message that fails any step MUST be dropped and the sender optionally notified.
version is not supported by this node.ttl > 0 · timestamp ≤ now + tolerance · cell is a valid grid cell ID · nonce is greater than the last seen nonce for this DID (replay protection) · type is a known type in the registry.hash from the canonical envelope + payload. Compare with the hash field. Verify signature using from.publicKey and the computed hash. Reject if either check fails.payload against the JSON Schema for the message type. Schemas are defined in the DIFP v0.2 Payload Specification. Unknown types with valid envelopes MUST be forwarded but not processed.query.cell request MUST produce a query.response).ttl. Forward based on target.type: cell messages propagate to cell-neighbor nodes; broadcast propagates to all known federated nodes; direct and node-targeted messages route to the specific destination. Messages with ttl ≤ 0 MUST NOT be forwarded.19 PAD System Overview
The PAD — Preloaded Assets Delivery — is the catalog layer of DIFP. It is the mechanism that makes real-time food coordination lightweight, offline-capable, and scalable to millions of participants simultaneously. Rather than transmitting full item records over the wire, DIFP pre-loads a complete food database onto every participant's device at installation time. During live coordination, only a minimal delta — item ID, price, and availability — is ever transmitted.
19.1 The Fundamental Split
| Layer | Name | Contents | When transmitted | Typical size |
|---|---|---|---|---|
| Static | PAD (difp_pad) | Full item metadata — name, brand, description, category, image, unit | Once, at app installation or update | ~5–15 MB compressed per country |
| Live | Delta record | Item ID + availability + price only | Every trade message, presence update, or catalog sync | ~20–30 bytes per item |
19.2 Why This Architecture Wins
Consider a store broadcasting its current inventory of 200 items. Without PAD, each item requires transmitting its name, description, category, image URL, and unit — easily 500 bytes per item, or 100KB per broadcast. With PAD, the same broadcast transmits only item IDs, prices, and availability flags — approximately 6KB total. At 10,000 stores in a single city, the difference between network-crushing and network-invisible is the PAD system.
For participants in low-connectivity regions — rural farms, remote cooperatives, markets with 2G coverage — PAD is not an optimization. It is the difference between the protocol working and not working.
19.3 PAD Scope
Each PAD package is scoped by two dimensions: country code and component type. A farmer in Algeria downloads pad_dz_f. A store in India downloads pad_in_s. A restaurant in France downloads pad_fr_r.
| PAD ID format | Example | Coverage |
|---|---|---|
pad_{countryCode}_{typeCode} | pad_dz_f | Algeria — Farmer catalog (~6,000 items) |
pad_in_s | India — Store catalog (~6,000 items) | |
pad_fr_r | France — Restaurant catalog (~6,000 items) | |
pad_ng_w | Nigeria — Wholesaler catalog (~6,000 items) |
20 Item Schema — difp_pad
The difp_pad format defines the full static item record. This is what lives on-device after installation. Every field in this schema is static — it never changes between app updates. The live delta (Section 21) references items from this schema by id only.
20.1 Full Item Record (difp_pad)
DifpPadItem {
id: integer // unique item identifier within this PAD package
name: string // item display name — localized per country
brand: string? // brand name (optional — null for generic items)
description: string? // short item description (optional)
category: string // top-level category (see Section 20.3)
sub_category: string // sub-category within category
image: string // asset reference ID (resolved against PAD asset bundle)
unit: string // base unit: kg | g | l | ml | piece | box | dozen | ...
score: number // quality/popularity score 0.0–5.0 (used for sorting)
// DIFP extended fields (added to base CSV schema)
gtin: string? // GS1 barcode — enables interoperability with existing supply chain systems
item_uid: string // globally namespaced DIFP identifier
// format: difp:item:{countryCode}:{category}:{slug}:v{n}
tags: list<string>? // searchable tags (optional)
season: string? // availability season hint: all | summer | winter | ... (optional)
}
20.2 CSV File Format (canonical PAD distribution format)
PAD packages are distributed as compressed CSV files. CSV is the canonical format because it is the most compact, universally parseable, and easy to generate from any existing food database or POS system.
// Header row (normative — field order fixed) id,name,brand,description,price,is_available,category,sub_category,image,unit,score // Example rows 1,Tomato,,Fresh local tomatoes,,true,vegetables,fresh_produce,img_0001,kg,4.2 2,Olive Oil,Hamoud,Extra virgin cold-pressed,,true,oils,vegetable_oil,img_0002,l,4.7 3,Durum Wheat Semolina,,Coarse ground,,true,grains,semolina,img_0003,kg,4.5 4,Feta Cheese,Président,White brined cheese,,false,dairy,cheese,img_0004,kg,3.9
price and is_available columns appear in the CSV header for schema completeness but MUST be empty in PAD files. These fields belong exclusively to the live delta layer (Section 21). A PAD file that contains price or availability data is non-conformant.
20.3 Category Taxonomy
DIFP v0.3 defines a normative top-level category set. Sub-categories are implementation-defined within each category.
| Category | Code | Examples |
|---|---|---|
| Vegetables | vegetables | tomatoes, potatoes, onions, peppers, leafy greens |
| Fruits | fruits | citrus, stone fruits, melons, berries, tropical |
| Grains & Cereals | grains | wheat, rice, barley, corn, semolina, flour |
| Legumes | legumes | lentils, chickpeas, beans, peas, soybeans |
| Meat & Poultry | meat | beef, lamb, chicken, turkey, rabbit |
| Fish & Seafood | fish | fresh fish, frozen fish, shellfish, canned fish |
| Dairy & Eggs | dairy | milk, cheese, butter, yogurt, eggs |
| Oils & Fats | oils | olive oil, sunflower oil, butter, margarine |
| Spices & Herbs | spices | cumin, coriander, saffron, mint, harissa |
| Beverages | beverages | water, juice, tea, coffee, soft drinks |
| Packaged & Processed | packaged | canned goods, sauces, condiments, pasta, preserves |
| Bakery | bakery | bread, pastries, biscuits, flatbreads |
| Agricultural Inputs | inputs | seeds, fertilizers, pesticides (for farmer/seed provider components) |
| Packaging & Supply | supply | crates, bags, containers (for factory/wholesaler components) |
20.4 Unit Vocabulary (normative)
All implementations MUST use the following unit codes. Custom units MUST NOT be used in PAD files intended for cross-node exchange.
// Weight kg // kilogram g // gram lb // pound t // metric tonne // Volume l // litre ml // millilitre fl_oz // fluid ounce // Count piece // individual item (egg, fruit, etc.) dozen // 12 pieces box // manufacturer-defined box quantity crate // standard agricultural crate bag // standard bag bundle// tied bundle (herbs, vegetables)
20.5 Global Item UID
Every PAD item MUST carry a globally unique item_uid following the DIFP namespacing convention. This enables cross-node catalog lookup and interoperability with the DIFP trade message schema.
// Format difp:item:{countryCode}:{category}:{slug}:v{version} // Examples difp:item:dz:vegetables:tomato_kg:v1 // Algerian tomatoes, per kg difp:item:in:grains:basmati_rice_kg:v2 // Indian basmati rice (second revision) difp:item:fr:dairy:camembert_250g:v1 // French camembert, 250g unit difp:item:ng:legumes:cowpea_kg:v1 // Nigerian cowpeas, per kg
21 Live Delta Sync
The live delta is the only catalog data that ever travels over the wire during active coordination. It is deliberately minimal: three fields per item, transmitted as a compact key-value map indexed by item ID. Everything else is resolved client-side against the on-device PAD.
21.1 Delta Record Schema
DeltaRecord {
// Only these three fields are transmitted live
id: integer // references DifpPadItem.id in the local PAD
a: boolean // availability (true = in stock, false = out of stock)
p: number // current price in local currency units
}
21.2 Wire Format Examples
When a store publishes its current catalog, it transmits only an array of delta records. The receiving client resolves item names, images, and descriptions locally from its PAD.
// Store broadcasting 4 items — entire payload is ~120 bytes { "catalog": [ { "id": 1, "a": true, "p": 120 }, // Tomato, 1kg — 120 DZD, in stock { "id": 2, "a": true, "p": 680 }, // Olive Oil, 1L — 680 DZD, in stock { "id": 3, "a": true, "p": 95 }, // Semolina, 1kg — 95 DZD, in stock { "id": 4, "a": false, "p": 0 } // Feta Cheese — out of stock ] }
21.3 How the Client Resolves a Delta
// On receiving a delta record from the network: function resolveDelta(delta) → DisplayItem: padItem = localPAD.lookup(delta.id) // O(1) — indexed by ID on device return { name: padItem.name, image: padItem.image, category: padItem.category, unit: padItem.unit, brand: padItem.brand, description: padItem.description, price: delta.p, // from live delta available: delta.a // from live delta } // If padItem is not found (PAD version mismatch): // → display item ID only, flag for PAD update // → MUST NOT discard the trade or block coordination
21.4 Delta Inside a DIFP Message Envelope
When a catalog delta is transmitted as part of a DIFP v0.2+ message, it is placed in the payload field of the message envelope. The message type is trade.offer or presence.announce depending on context.
{
"id": "msg-20260427-ab12",
"type": "presence.announce",
"version": "0.3",
"from": {
"did": "difp://3440210/s/safeway-dz-042",
"node": "node-oran-01",
"role": "client"
},
"target": { "type": "cell", "value": "3440210" },
"mode": "event",
"cell": "3440210",
"timestamp": "2026-04-27T09:15:00Z",
"ttl": 120,
"payload": {
"status": "open",
"pad_version": "pad_dz_s_v4", // PAD version the sender used to build this catalog
"catalog": [
{ "id": 1, "a": true, "p": 120 },
{ "id": 2, "a": true, "p": 680 },
{ "id": 3, "a": true, "p": 95 },
{ "id": 4, "a": false, "p": 0 }
]
}
}
21.5 Bandwidth Comparison
| Scenario | Without PAD | With PAD (delta only) | Reduction |
|---|---|---|---|
| Single item broadcast | ~400–600 bytes | ~25 bytes | ~95% |
| Store with 200 items | ~100 KB | ~5 KB | ~95% |
| 10,000 stores in one city | ~1 GB | ~50 MB | ~95% |
| Nationwide sync (100k stores) | ~10 GB | ~500 MB | ~95% |
22 PAD Distribution & Versioning
PAD packages are versioned, signed, and distributed independently from the application logic. A participant's PAD is updated separately from app updates — allowing catalog improvements (new items, corrected names, updated images) to ship without requiring a full app release.
22.1 PAD Version String
// Format pad_{countryCode}_{typeCode}_v{n} // Examples pad_dz_f_v1 // Algeria Farmer — version 1 pad_dz_s_v4 // Algeria Store — version 4 (updated 3 times) pad_in_r_v2 // India Restaurant — version 2
22.2 PAD Package Structure
pad_{country}_{type}_v{n}.zip
├── items.csv ← full difp_pad item records (no price/availability)
├── manifest.json ← version, country, type, item count, checksum, date
└── assets/
├── img_0001.webp ← item images (webp, 200×200px max)
├── img_0002.webp
└── ... ← one image per item
22.3 Manifest Schema
PADManifest {
pad_id: string // e.g. "pad_dz_s_v4"
country: string // ISO 3166-1 alpha-2 country code
component: string // type code from Section 2
version: integer // monotonically increasing
item_count: integer // total number of items in items.csv
checksum: string // SHA-256 of items.csv
issued: string // ISO 8601 date
min_app: string // minimum DIFP protocol version required
signature: string // Ed25519 signature by Djowda PAD signing key
}
22.4 Distribution Channels
PAD packages are distributed through three channels in priority order:
GET /.well-known/difp/pad/{padId}. This enables community-operated nodes to distribute locally-tailored catalogs (e.g. a cooperative's specific product list) while extending the canonical PAD with regional items.22.5 PAD Well-Known Endpoints
// Check for PAD updates GET /.well-known/difp/pad/latest?country={cc}&type={typeCode} Response: { "pad_id": "pad_dz_s_v4", "version": 4, "size_bytes": 8420000, "checksum": "sha256:abc...", "download": "https://{host}/pad/pad_dz_s_v4.zip" } // Download a specific PAD version GET /.well-known/difp/pad/{padId}.zip
items.csv against the manifest before applying a PAD update. Clients SHOULD verify the Ed25519 signature on the manifest against the published DIFP PAD signing key. A PAD that fails either check MUST be discarded.
23 PAD Conformance Requirements
This section is normative. Conformant DIFP v0.3 implementations MUST satisfy all MUST requirements below in addition to all prior conformance requirements from Section 11.
23.1 MUST Implement
id, a, and p transmitted in catalog payloadspad_version field)23.2 SHOULD Implement
/.well-known/difp/pad/latest) for node-distributed catalogs23.3 MUST NOT
id, a, and p — additional metadata MUST come from the local PAD24 Lobby Layer
The Lobby layer is the second spatial abstraction in DIFP, sitting directly above the MinMax99 cell grid. It answers a fundamental network question that cells alone cannot: given a location anywhere on Earth, which node should I contact to find participants there?
Cells handle precision. Lobbies handle routing. A cell tells you where a participant is. A lobby tells you which node knows about participants in that region.
24.1 Lobby Constants (normative)
All conformant implementations MUST use the following constants without modification. These values are derived directly from the Layer 0 grid constants and are not independently configurable.
// Lobby grid constants — derived from MinMax99, MUST NOT be changed independently LOBBY_SIZE = 41 // cells per lobby on each axis (41 × 41 = 1,681 cells) NUM_LOBBY_COLUMNS = 82,000 / 41 = 2,000 // exact — 82,000 is divisible by 41 NUM_LOBBY_ROWS = ceil(42,000 / 41) = 1,025 // ceiling — 42,000 mod 41 = 16 (partial strip) TOTAL_LOBBIES = 2,000 × 1,025 = 2,050,000
42,000 mod 41 = 16. Conformant implementations MUST handle this boundary correctly — do not assume all lobbies contain a full 41×41 grid of cells.
24.2 Physical Coverage
Each lobby covers a geographic area of approximately 20.5km × 20.5km (41 cells × 500m per cell). This is intentionally sized to match a typical urban district, a rural municipality, or a regional market catchment area — making it a natural unit for node responsibility partitioning.
| Layer | Unit | Edge length | Total count | Purpose |
|---|---|---|---|---|
| Layer 0 | Cell | 500m × 500m | ~3.44 billion | Participant addressing, trade routing |
| Layer 1 | Lobby | ~20.5km × 20.5km | ~2.05 million | Node discovery, registry routing |
24.3 CellToLobbyNumber — Canonical Algorithm
The mapping from cell to lobby is deterministic and requires no network call. Any implementation MUST produce identical lobby IDs from identical cell IDs.
// Layer 1 encoding — same row-major philosophy as Layer 0 function cellIdToLobbyId(cellId) → int64: [xCell, yCell] = cellNumberToXY(cellId) // Layer 0 reverse lookup lobbyX = floor(xCell / LOBBY_SIZE) // 0..1999 lobbyY = floor(yCell / LOBBY_SIZE) // 0..1024 return lobbyX * NUM_LOBBY_ROWS + lobbyY // row-major encoding // Reverse: lobbyId → [lobbyX, lobbyY] function lobbyIdToXY(lobbyId) → (lobbyX, lobbyY): lobbyX = floor(lobbyId / NUM_LOBBY_ROWS) // 0..1999 lobbyY = lobbyId mod NUM_LOBBY_ROWS // 0..1024 return (lobbyX, lobbyY) // Full reverse: (lobbyId, localX, localY) → cellId function lobbyIdAndLocalToCellId(lobbyId, localX, localY) → int64: [lobbyX, lobbyY] = lobbyIdToXY(lobbyId) xCell = lobbyX * LOBBY_SIZE + localX // 0..41 exclusive yCell = lobbyY * LOBBY_SIZE + localY // 0..41 exclusive return xCell * NUM_ROWS + yCell // Layer 0 encoding // Local position within a lobby function cellIdToLocalXY(cellId) → (localX, localY): [xCell, yCell] = cellNumberToXY(cellId) localX = xCell mod LOBBY_SIZE // 0..40 localY = yCell mod LOBBY_SIZE // 0..40 return (localX, localY)
24.4 Reference Test Vectors
Validate your Layer 1 implementation against these vectors. All use the same reference coordinates as the Layer 0 test vectors from Section 3.3.
| Location | Cell ID (Layer 0) | Lobby ID (Layer 1) | Local (X,Y) |
|---|---|---|---|
| Algiers, Algeria | 1,711,767,603 | 40,774 | (23, 3) |
| Paris, France | 1,705,129,761 | 40,598 | (28, 21) |
| Lagos, Nigeria | 1,714,879,281 | 40,831 | (0, 6) |
| New York, USA | 991,131,039 | 23,598 | (10, 39) |
| Tokyo, Japan | 2,989,365,749 | 71,175 | (26, 24) |
| Sydney, Australia | 3,097,104,003 | 73,740 | (3, 3) |
24.5 Node Lobby Computation
A node determines which lobbies it is responsible for by mapping all stored cell IDs through cellIdToLobbyId and collecting the unique set. This is the set the node publishes to the registry.
function computeOwnedLobbies(storedCellIds) → Set<int64>: lobbies = new Set() for cellId in storedCellIds: lobbies.add(cellIdToLobbyId(cellId)) return lobbies // deduplicated — typically far fewer entries than cells
25 Node Registry
The Node Registry is a publicly queryable index that maps lobby IDs to the DIFP nodes that hold data for those lobbies. It is the routing backbone of the federated network — without it, a client has no way to discover which node to contact for a given location.
In DIFP v0.4, the registry is intentionally kept simple: it is a pure spatial routing index with no health scoring, no node roles, and no replication semantics. Those capabilities are deferred to v0.5.
25.1 Registry Data Model
// Conceptual model — implementation storage is not prescribed Registry { entries: Map<lobbyId, Set<string>> // lobbyId → set of node endpoint URLs } // Example state: { 83907: ["https://node-oran-01.difp", "https://node-algiers-02.difp"], 84582: ["https://node-paris-01.difp"], 76786: ["https://node-lagos-01.difp", "https://node-lagos-02.difp"] }
25.2 Registry Architecture — v0.4
DIFP v0.4 supports two registry deployment patterns. Both expose the same query interface (Section 27), so clients are unaffected by which pattern a network uses.
| Pattern | Description | Use case | Status |
|---|---|---|---|
| Centralized | A single registry service receives all LOBBY_ANNOUNCE messages and responds to all queries | Small networks, bootstrapping, testing | ✅ v0.4 |
| Federated | Multiple registry services, each receiving announces from regional nodes. Clients query any registry; registries MAY cross-reference each other | Production networks, regional deployments | ✅ v0.4 |
| Distributed (DHT) | Fully peer-to-peer, Kademlia-style | Censorship-resistant, fully decentralized | 🔜 v0.5+ |
25.3 Registry Well-Known Endpoint
Any service acting as a DIFP registry MUST expose the following endpoints. These are additive to the node well-known endpoints defined in Section 10.
// Query which nodes serve a lobby GET /.well-known/difp/registry/lobby/{lobbyId} Response: { "lobbyId": 83907, "nodes": ["https://node-oran-01.difp", "https://node-algiers-02.difp"] } // Bulk query — resolve multiple lobbies at once (for neighbor queries) POST /.well-known/difp/registry/lobby/batch Body: { "lobbyIds": [83907, 83908, 84582] } Response: { "results": { "83907": ["https://node-oran-01.difp"], "83908": ["https://node-oran-01.difp"], "84582": ["https://node-paris-01.difp"] } } // List all known registries (for federation bootstrapping) GET /.well-known/difp/registry/peers Response: { "registries": [ "https://registry-global.difp", "https://registry-africa.difp" ] }
25.4 Node Registration at Startup
When a DIFP node starts, it MUST announce its lobbies to at least one registry. The sequence is:
computeOwnedLobbies(storedCellIds) to produce the unique set of lobby IDs this node serves.LOBBY_ANNOUNCE message (Section 27.1) to one or more known registry endpoints. The message includes the node's endpoint URL and the computed lobby set.26 Discovery Flow
The lobby and registry layers exist to enable a single end-to-end use case: given a GPS location, find the DIFP nodes that hold participant data for that location. This section defines that flow precisely.
26.1 Full Discovery Sequence
// Step 1 — client computes spatial address from GPS cellId = geoToCellNumber(latitude, longitude) lobbyId = cellIdToLobbyId(cellId) // Step 2 — client queries registry for nodes serving that lobby nodes = GET /.well-known/difp/registry/lobby/{lobbyId} // → ["https://node-oran-01.difp", "https://node-algiers-02.difp"] // Step 3 — client queries nodes directly for presence data for nodeEndpoint in nodes: presenceRecords = GET {nodeEndpoint}/.well-known/difp/cell/{cellId} if presenceRecords is not empty: break // or merge results from multiple nodes // Step 4 — client uses presence records for trade initiation // (per existing Sections 5–7)
26.2 Neighbor Discovery
When a client needs to discover participants across a radius (Section 3.4), the lobby layer optimizes the registry queries. Instead of querying one lobby, the client resolves the lobby IDs for all neighbor cells and batches the registry lookup.
// Radius discovery with lobby optimization nearbyCells = getNearbyCells(centerCellId, radius) nearbyLobbies = unique(nearbyCells.map(cellIdToLobbyId)) // typically 1–4 lobbies for small radii // Single batched registry call instead of N individual calls results = POST /.well-known/difp/registry/lobby/batch { "lobbyIds": nearbyLobbies } // Then query unique nodes from the merged result uniqueNodes = unique(flatten(results.values())) for nodeEndpoint in uniqueNodes: presenceRecords += GET {nodeEndpoint}/.well-known/difp/cell/{cellId} // for each cell in nearbyCells served by this node
26.3 No Registry Available
If a client cannot reach any registry, it MUST fall back to direct node queries using hardcoded or cached node endpoints from prior sessions. A client MUST NOT fail silently — it SHOULD notify the user that discovery may be incomplete.
26.4 Multiple Nodes for Same Lobby
The registry may return multiple nodes for a single lobby. This is normal and expected — multiple nodes may serve overlapping geographic areas. Clients MUST try nodes in order and MAY merge results from multiple nodes. Clients MUST deduplicate participants across node responses using their DID.
27 Registry Messages
DIFP v0.4 defines three canonical message types for the registry system. These extend the message type registry from Section 16 and follow the same dot-namespaced format and envelope structure from Section 15.
27.1 LOBBY_ANNOUNCE
Sent by a node to a registry to declare which lobbies it serves. This is the only write operation in the v0.4 registry protocol.
// Envelope type: "registry.announce" | mode: "event" | role: "node" { "id": "msg-20260510-la-9f3a", "type": "registry.announce", "version": "0.4", "from": { "did": "difp://3440210/a/node-oran-01", "node": "node-oran-01", "role": "node" }, "target": { "type": "direct", "value": "difp://registry/global" }, "mode": "event", "cell": "3440210", "timestamp": "2026-05-10T08:00:00Z", "ttl": 3600, "payload": { "nodeEndpoint": "https://node-oran-01.difp", "lobbies": [83907, 83908, 83935, 84000] // deduplicated lobby IDs this node currently holds data for } }
27.2 REGISTRY_QUERY
Sent by a client or node to a registry to discover which nodes serve a given lobby. This is a request mode message — a REGISTRY_RESPONSE MUST be returned.
// Envelope type: "registry.query" | mode: "request" | role: "client" or "node" { "id": "msg-20260510-rq-4b2c", "type": "registry.query", "version": "0.4", "from": { "did": "difp://3440210/u/consumer-42", "node": "node-oran-01", "role": "client" }, "target": { "type": "direct", "value": "difp://registry/global" }, "mode": "request", "cell": "3440210", "timestamp": "2026-05-10T09:32:00Z", "ttl": 30, "payload": { "lobbyId": 83907 // for batch queries, use "lobbyIds": [83907, 83908, ...] } }
27.3 REGISTRY_RESPONSE
Returned by a registry in reply to a registry.query. The context.parentId MUST match the originating query message id.
// Envelope type: "registry.response" | mode: "response" | role: "node" { "id": "msg-20260510-rr-7f9d", "type": "registry.response", "version": "0.4", "from": { "did": "difp://0/a/registry-global", "node": "registry-global", "role": "node" }, "target": { "type": "direct", "value": "difp://3440210/u/consumer-42" }, "mode": "response", "cell": "0", "timestamp": "2026-05-10T09:32:00Z", "ttl": 60, "context": { "parentId": "msg-20260510-rq-4b2c" }, "payload": { "lobbyId": 83907, "nodes": [ "https://node-oran-01.difp", "https://node-algiers-02.difp" ] } }
27.4 Updated Message Type Registry
The following types are added to the registry from Section 16.2:
| Domain | Type | Class | Description |
|---|---|---|---|
| registry | registry.announce | Event | Node announces its lobby coverage to a registry |
registry.query | Query | Client or node queries a registry for nodes serving a lobby | |
registry.response | System | Registry responds to a query with a list of node endpoints |
28 v0.4 Conformance Requirements
This section is normative. Conformant DIFP v0.4 implementations MUST satisfy all requirements below in addition to all conformance requirements from Sections 11 and 23.
28.1 MUST Implement (Nodes)
cellIdToLobbyId algorithm with exact constants from Section 24.1computeOwnedLobbies — deduplicated lobby set from stored cell IDsregistry.announce message on startup and on lobby set change28.2 MUST Implement (Registries)
registry.announce messages — update lobby → nodes mappingGET /.well-known/difp/registry/lobby/{lobbyId} with node listPOST /.well-known/difp/registry/lobby/batch with batched results28.3 MUST Implement (Clients)
lobbyId client-side from GPS before querying registry28.4 SHOULD Implement
/.well-known/difp/registry/peers28.5 Explicitly Out of Scope (v0.4)
13 Expansion Roadmap
The following table shows the DIFP roadmap. ✅ = shipped. 🔜 = upcoming.
14 How to Implement DIFP
A practical guide for developers who want to build a DIFP-conformant node from scratch.
geoToCellNumber (Section 3.2) to your language. Validate against the reference test vectors in Section 3.3: (36.7538, 3.0588) → 1711767603 (Algiers), (48.8566, 2.3522) → 1705129761 (Paris), (35.6895, 139.6917) → 2989365749 (Tokyo), (40.7128, -74.0060) → 991131039 (New York). This is the only piece of the protocol that MUST be bit-for-bit identical across all implementations.cell_id and component_type. Expose the well-known endpoints from Section 10.— License & Acknowledgements
DIFP was developed by the Djowda Platform team as an open contribution to the global food security challenge. The spatial grid algorithm (MinMax99) was designed by the Djowda engineering team.
Creative Commons Attribution 4.0 International (CC-BY 4.0). You are free to share and adapt this specification for any purpose, including commercial, provided you give appropriate credit to the Djowda Platform and indicate if changes were made. This license is intentionally permissive — the wider the adoption, the more food reaches the people who need it.
To contribute, open an issue or pull request at https://djowda.com/difp or contact sales@djowda.com.