New in v0.4 — Lobby Layer · Node Registry · Spatial Discovery · LOBBY_ANNOUNCE

Open Protocol Specification

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.

Status STABLE PREVIEW — v0.4 Lobby & Registry
Specification DIFP-CORE-0.4
Issued May 2026

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.

🌍 Mission: Reduce global food insecurity by eliminating coordination friction across the food supply chain.

Abstract

DIFP is a lightweight, open, spatial food coordination protocol. It specifies:

ModuleDescription
Participant identityHow food ecosystem actors identify themselves globally without a central registry
Spatial addressingHow Earth's surface is divided into ~500m × 500m cells, each acting as a coordination zone
Presence and discoveryHow participants announce and find each other within and across cells
Trade message formatA universal structure for orders, asks, and donations between any two participants
Protocol federationHow 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.

TermDefinition
ComponentAny participant registered on a DIFP network (farmer, store, user, etc.)
CellA ~500 × 500 m geographic zone identified by a numeric cell ID
Cell IDA 64-bit integer encoding the position of a cell in the global grid
DIDDecentralized Identifier — a self-sovereign identity string of the form difp://{cellId}/{type}/{id}
TradeA structured message representing an order, ask, or donation between two components
InteractorA software module handling the full discovery + trade lifecycle for one component pair
Fan-outAn atomic multi-path write ensuring consistency across all affected data nodes
PADPre-loaded Application Data — static catalog content shipped with the app
DIFP NodeA server or service implementing this specification
FederationThe 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.

CodeActorRole in Food Chain
spSeed ProviderSupplies seeds and agricultural inputs to farmers
fFarmerPrimary producer — grows and harvests food
faFactoryProcesses raw produce into packaged goods
wWholesalerAggregates and distributes in bulk to stores and restaurants
sStoreRetail point of sale to end consumers
rRestaurantPrepares and serves food to consumers
uUserEnd consumer — orders from stores and restaurants
tTransportBulk logistics between any two non-consumer nodes
dDeliveryLast-mile delivery from store or restaurant to user
aAdminPlatform oversight — read access across all node types

2.1 Supply Chain Flow

Seed Provider
Farmer
Factory
Wholesaler
Store
·
Restaurant
User
Supporting roles (connect at any layer): Transport · Delivery · Admin

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
These constants are fixed across all versions of the protocol. Any implementation that changes 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
Radius
Grid Size
Cell Count
Coverage
0
own cell only
1×1
1
~0.25
km²
1
3×3
9
~2.25
km²
2
5×5
25
~6.25
km²
5
11×11
121
~30
km²

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"
    ...
Implementation Note: Records MUST be kept flat (no nested objects beyond the PresenceRecord fields). Deep nesting prevents efficient 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

CodeTypeDescription
oOrderA purchase request — includes items, quantities, prices, and total
aAskA resource availability inquiry — no price; signals demand to nearby suppliers
dDonationAn 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

PENDING (p)
receiver accepts
ACCEPTED (a)
PROCESSING (pr)
COMPLETED (c) ✓
receiver denies
DENIED (dn) ✗
sender cancels
CANCELLED (x) ✗

7.4 Role-Based Transition Rules

ActorFromTo
SenderPENDINGCANCELLED
ReceiverPENDINGACCEPTED
ReceiverPENDINGDENIED
EitherACCEPTEDPROCESSING
EitherPROCESSINGCOMPLETED

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

TD/{tradeId}
Canonical full record — source of truth for all trade data
T/{typeCode}/{componentId}/i/{tradeId}
Receiver inbox summary
T/{typeCode}/{componentId}/o/{tradeId}
Sender outbox summary
TA/{tradeId}
Analytics — no PII, map-route data only

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
}
🔒 Privacy Rule: The TA (analytics) node MUST NEVER contain phone numbers, postal addresses, personal names, or item-level detail. It is used only for aggregate metrics and 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
Implementations MUST use their transport's equivalent of Firebase's multi-path 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

M
Grid algorithm: geoToCellNumber with exact constants from Section 3.1
M
DID format: difp://{cellId}/{typeCode}/{componentId} as canonical identity scheme
M
All 10 component types from Section 2
M
PresenceRecord schema from Section 5.1
M
TradeMessage schema from Section 7.2
M
Status lifecycle from Section 7.3, including role-based transition rules
M
Atomic write guarantees from Section 9
M
Well-known endpoints from Section 10.1 and 10.2

11.2 SHOULD Implement

S
PAD catalog system from Section 6 for offline item discovery
S
TA analytics node from Section 8.3 with privacy restrictions enforced
S
Neighbor radius discovery using getNearbyCells from Section 3.4
S
Cross-node trade routing from Section 10.3

11.3 MAY Implement

M
Custom component types using codes not in the canonical set — MUST NOT reuse canonical codes
M
Extended PresenceRecord fields — MUST preserve all required fields
M
Alternative transport layers (REST, MQTT, WebSockets) — MUST preserve message schemas
M
Message signing — RECOMMENDED for cross-node federation

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.

🌱 Intent: Every design decision in DIFP should be evaluated against the question: does this reduce the time and friction between a food surplus and a food need? If yes, it belongs in the protocol.

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.

Design principle: 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

FieldRequiredPurpose
idUnique message ID for deduplication and tracing. Format: msg-{timestamp}-{random}
typeDot-namespaced message type (e.g. trade.ask). Determines payload schema and node processing logic.
versionProtocol version. Nodes reject messages with incompatible versions.
from.didFull DIFP DID of sender. Used for identity and routing.
from.nodeOriginating node ID. Used for federation routing and failover.
from.publicKeyEd25519 public key for signature verification. Nodes cache this per DID.
from.roleSender role. Determines processing rules and trust level. See Section 17.
target.typeRouting scope: cell, node, broadcast, or direct.
target.valueRouting destination: cell ID, node ID, or DID depending on target.type.
modeMessage intent: event (broadcast), request (expects reply), response (reply to request).
cellSender's geographic cell ID — the spatial routing anchor. All messages carry this regardless of target.
timestampISO 8601 UTC. Nodes reject messages where timestamp > now + tolerance.
ttlSeconds before message expires. Nodes MUST NOT forward messages with ttl ≤ 0.
nonceMonotonically increasing integer per DID. Prevents replay attacks.
hashSHA-256 of canonical JSON of (envelope_without_hash_and_sig + payload). Detects tampering.
signatureEd25519 signature over hash. Guarantees authenticity.
contextOptional. Enables message chain tracing for debugging and analytics.
payloadType-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)
Keys MUST be sorted lexicographically. Whitespace MUST be removed. Floating point numbers MUST use their minimal decimal representation. Any deviation produces a different hash and an invalid signature across implementations.

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.

Payload schemas for each type are defined in the DIFP v0.2 Payload Specification (upcoming). This section defines the type registry — the full taxonomy of valid type values and their behavioral class.

16.1 Message Classes

ClassBehaviorExamples
CommandTriggers an action on the receiving nodetrade.accept, trade.cancel
EventBroadcasts a state change — no response expectedpresence.announce, trade.ask
QueryExpects a structured responsequery.cell, query.resource
SystemNode-to-node protocol messagesnode.sync, node.ping

16.2 Full Type Registry

DomainTypeClassDescription
identityidentity.registerCommandRegister a new DID on the network
identity.updateCommandUpdate DID metadata
identity.revokeCommandRevoke a DID (e.g. decommission a node)
presencepresence.announceEventParticipant announces availability in a cell
presence.updateEventUpdate status or capabilities
presence.leaveEventParticipant leaves the cell or goes offline
tradetrade.askEventBroadcast demand signal — I need this resource
trade.offerEventBroadcast supply signal — I have this resource
trade.donateEventBroadcast surplus at no cost
trade.acceptCommandAccept a pending trade
trade.rejectCommandDeny a pending trade
trade.completeCommandMark trade as completed
trade.cancelCommandCancel a pending trade (sender only)
queryquery.cellQueryDiscover participants in a cell or radius
query.resourceQueryFind who has a specific resource available
query.actorQueryLook up a specific DID's presence record
radarradar.snapshotQueryRequest supply/demand aggregate for a cell
radar.updateEventNode publishes updated radar data for a cell
logisticslogistics.requestEventRequest transport or delivery capacity
logistics.offerEventOffer available transport or delivery capacity
logistics.updateEventUpdate logistics status
nodenode.announceSystemNode announces itself to the network
node.syncSystemRequest state sync from another node
node.pingSystemHealth check between nodes
node.failoverSystemSignal node going offline, transfer responsibility
reputationreputation.updateEventUpdate reputation score after completed trade
dispute.openCommandOpen a dispute on a trade
dispute.resolveCommandResolve a dispute
sensorsensor.reportEventIoT device reports a reading (harvest ready, soil moisture, etc.)
automation.triggerCommandAutomated action triggered by sensor threshold
Implementations MUST NOT use undocumented type strings in production federation. Custom types MUST use a prefixed namespace (e.g. 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.

RoleActorTrust LevelTypical Message Types
clientUser-facing app (farmer app, store POS, NGO dashboard)Standard — signature requiredtrade.*, presence.*, query.*
nodeDIFP server implementing this specElevated — node key verifiednode.*, radar.*, query.*
serviceThird-party service (analytics, AI, dashboard)Standard — read-mostlyquery.*, radar.*
deviceIoT sensor, automated systemStandard — device key verifiedsensor.*, automation.*, trade.donate

17.2 target.type — Routing Scope

Defines where the message should go and how far it propagates.

TypeValue field containsPropagationUse case
cellCell ID (int64 as string)Node serves all participants in that cell + neighbors at configured radiusGeographic broadcast — trade.ask, presence.announce
nodeNode ID stringRouted directly to the named node onlyFederation — node.sync, node.ping
broadcast"*"All reachable nodes (TTL-limited)Network-wide events — node.announce
directRecipient DIDRouted to the specific DID's home nodePrivate messages — trade.accept, query.response

17.3 mode — Message Intent

ModeBehaviorResponse expectedExamples
eventFire-and-forget broadcast. Node stores and propagates.Notrade.ask, presence.announce, sensor.report
requestExpects a response message referencing this message's id via context.parentId.Yesquery.cell, node.sync, node.ping
responseReply to a prior request. Must set context.parentId to the original message id.Noquery.response, node.sync reply

17.4 Actor Flow Examples

📱 Client → Node (trade broadcast)
"from.role": "client"   "target.type": "cell"   "mode": "event"
"type": "trade.ask"
→ Node stores + propagates to cell neighbors. No response required.
🌐 Node → Node (federation sync)
"from.role": "node"     "target.type": "node"   "mode": "request"
"type": "node.sync"
→ Target node processes + sends back mode:"response" with context.parentId set.
🌐 Node → Client (query result)
"from.role": "node"     "target.type": "direct"  "mode": "response"
"type": "query.response"
→ Client receives result. context.parentId links to original query.cell request.
🤖 Device → Node (IoT harvest signal)
"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.

1
Structural Validation
Verify the message is valid JSON. Check all required envelope fields are present and correctly typed. Reject if version is not supported by this node.
2
Semantic Validation
Verify: 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.
3
Cryptographic Validation
Recompute 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.
4
Payload Validation
Validate 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.
5
Processing
Execute the type-specific handler: store presence records, match trades, update radar aggregates, respond to queries. Handler logic is implementation-defined; the protocol specifies only the expected outcomes (e.g. a query.cell request MUST produce a query.response).
6
Propagation
Decrement 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.
🌱 Design intent: The pipeline is deliberately simple — validate, process, forward. Complexity lives in payload handlers and federation routing, not in the envelope processing itself. A node that gets steps 1–3 right can safely participate in the network even before implementing all payload types.

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.

Core insight: A farmer's name, a product's photo, and a category label never change during a trade. Only the price and availability do. By separating static data (PAD) from live data (delta), DIFP reduces live message payload size by 99% compared to transmitting full item records.

19.1 The Fundamental Split

LayerNameContentsWhen transmittedTypical size
StaticPAD (difp_pad)Full item metadata — name, brand, description, category, image, unitOnce, at app installation or update~5–15 MB compressed per country
LiveDelta recordItem ID + availability + price onlyEvery 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 formatExampleCoverage
pad_{countryCode}_{typeCode}pad_dz_fAlgeria — Farmer catalog (~6,000 items)
pad_in_sIndia — Store catalog (~6,000 items)
pad_fr_rFrance — Restaurant catalog (~6,000 items)
pad_ng_wNigeria — Wholesaler catalog (~6,000 items)
Target size: Each PAD package SHOULD contain approximately 6,000 items and MUST compress to under 15MB. Images are referenced by asset ID — they are NOT embedded in the PAD file. The PAD ships with a reference implementation app; third-party implementations must host or bundle their own PAD assets.

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
The 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.

CategoryCodeExamples
Vegetablesvegetablestomatoes, potatoes, onions, peppers, leafy greens
Fruitsfruitscitrus, stone fruits, melons, berries, tropical
Grains & Cerealsgrainswheat, rice, barley, corn, semolina, flour
Legumeslegumeslentils, chickpeas, beans, peas, soybeans
Meat & Poultrymeatbeef, lamb, chicken, turkey, rabbit
Fish & Seafoodfishfresh fish, frozen fish, shellfish, canned fish
Dairy & Eggsdairymilk, cheese, butter, yogurt, eggs
Oils & Fatsoilsolive oil, sunflower oil, butter, margarine
Spices & Herbsspicescumin, coriander, saffron, mint, harissa
Beveragesbeverageswater, juice, tea, coffee, soft drinks
Packaged & Processedpackagedcanned goods, sauces, condiments, pasta, preserves
Bakerybakerybread, pastries, biscuits, flatbreads
Agricultural Inputsinputsseeds, fertilizers, pesticides (for farmer/seed provider components)
Packaging & Supplysupplycrates, 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
PAD version mismatch: If a delta references an item ID not found in the local PAD, the client MUST still process the trade using the item ID as a fallback identifier. It MUST schedule a PAD update in the background. It MUST NOT block or cancel the trade because of a missing PAD item.

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

ScenarioWithout PADWith 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:

1
Bundled at Installation
The initial PAD for the participant's country and component type is bundled inside the app at build time. This ensures first-time users can browse and coordinate immediately, with no network dependency on launch. The bundled PAD is always the latest version at app build time.
2
Background OTA Update
When a newer PAD version is available, the app downloads it in the background over Wi-Fi. The update is applied atomically — the old PAD remains active until the new one is fully downloaded and verified. Implementations MUST verify the PAD checksum and signature before applying an update.
3
Node-Served PAD
DIFP nodes MAY serve PAD packages directly to clients via a well-known endpoint: 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
🔒 PAD integrity: Clients MUST verify the SHA-256 checksum of 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

M
PAD package for at least one country and one component type, bundled at installation
M
PAD item schema from Section 20.1 — all required fields must be present for every item
M
Live delta format from Section 21.1 — only id, a, and p transmitted in catalog payloads
M
PAD version string in every catalog-bearing message payload (pad_version field)
M
PAD checksum verification before applying any OTA update
M
Graceful handling of PAD version mismatch — trades MUST NOT be blocked due to missing PAD items
M
Item IDs within a PAD package MUST be stable across versions — an item that exists in v1 MUST have the same ID in v2, v3, and all future versions

23.2 SHOULD Implement

S
Background OTA PAD updates with atomic apply (old PAD stays active until new one is verified)
S
PAD well-known endpoint (/.well-known/difp/pad/latest) for node-distributed catalogs
S
Ed25519 signature verification of PAD manifest
S
Multiple PAD packages per app (for multi-country or multi-component-type deployments)

23.3 MUST NOT

M
PAD CSV files MUST NOT contain price or availability data — these fields belong to the live delta only
M
Images MUST NOT be embedded or base64-encoded inside the CSV — only asset reference IDs
M
Item IDs MUST NOT be reused or reassigned across PAD versions within the same country/type scope
M
Delta records MUST NOT include any fields beyond id, a, and p — additional metadata MUST come from the local PAD
Design intent: The PAD system exists to serve participants with the worst connectivity, not the best. Every conformance decision in this section is evaluated against the question: does this work on a 2G connection in a rural area? If the answer is no, it belongs in the PAD, not in the wire protocol.

24 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.

Layer architecture: DIFP now has two deterministic spatial layers. Layer 0 (cells, 500m) handles participant addressing and trade routing. Layer 1 (lobbies, ~20km) handles node discovery and registry routing. Both use the same row-major encoding for consistency.

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
Partial strip: The last row of lobbies (row index 1,024) contains only 16 cell rows instead of 41, due to 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.

LayerUnitEdge lengthTotal countPurpose
Layer 0Cell500m × 500m~3.44 billionParticipant addressing, trade routing
Layer 1Lobby~20.5km × 20.5km~2.05 millionNode 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.

LocationCell ID (Layer 0)Lobby ID (Layer 1)Local (X,Y)
Algiers, Algeria1,711,767,60340,774(23, 3)
Paris, France1,705,129,76140,598(28, 21)
Lagos, Nigeria1,714,879,28140,831(0, 6)
New York, USA991,131,03923,598(10, 39)
Tokyo, Japan2,989,365,74971,175(26, 24)
Sydney, Australia3,097,104,00373,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
v0.4 threshold (informative): Nodes SHOULD only publish a lobby to the registry if they hold data for at least one cell within it. Publishing lobbies for which a node holds no data creates noise. Future versions may introduce a minimum cell count threshold.

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.

Design principle: The v0.4 registry answers exactly one question: "Which nodes claim to serve lobby X?" It does not guarantee liveness, data freshness, or authority. Clients MUST handle unreachable nodes gracefully by trying other entries in the list.

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.

PatternDescriptionUse caseStatus
CentralizedA single registry service receives all LOBBY_ANNOUNCE messages and responds to all queriesSmall networks, bootstrapping, testing✅ v0.4
FederatedMultiple registry services, each receiving announces from regional nodes. Clients query any registry; registries MAY cross-reference each otherProduction networks, regional deployments✅ v0.4
Distributed (DHT)Fully peer-to-peer, Kademlia-styleCensorship-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:

1
Compute owned lobbies
Call computeOwnedLobbies(storedCellIds) to produce the unique set of lobby IDs this node serves.
2
Send LOBBY_ANNOUNCE
POST a 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.
3
Re-announce periodically
Nodes SHOULD re-announce at a regular interval. The exact interval is implementation-defined in v0.4. Nodes MUST re-announce whenever their lobby set changes (new data added or data deleted).

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
Locality benefit: For small discovery radii (r ≤ 2), all nearby cells typically fall within the same lobby or 2–4 adjacent lobbies. The batched registry call returns the relevant nodes in a single round-trip, making discovery very fast even for cold clients with no cached data.

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:

DomainTypeClassDescription
registryregistry.announceEventNode announces its lobby coverage to a registry
registry.queryQueryClient or node queries a registry for nodes serving a lobby
registry.responseSystemRegistry 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)

M
cellIdToLobbyId algorithm with exact constants from Section 24.1
M
computeOwnedLobbies — deduplicated lobby set from stored cell IDs
M
registry.announce message on startup and on lobby set change
M
Announce to at least one known registry endpoint
M
Lobby set MUST be deduplicated — MUST NOT announce duplicate lobby IDs

28.2 MUST Implement (Registries)

M
Accept and store registry.announce messages — update lobby → nodes mapping
M
Respond to GET /.well-known/difp/registry/lobby/{lobbyId} with node list
M
Respond to POST /.well-known/difp/registry/lobby/batch with batched results
M
Node endpoints MUST be stored as a set — no duplicates per lobby

28.3 MUST Implement (Clients)

M
Compute lobbyId client-side from GPS before querying registry
M
Handle empty registry response gracefully — fall back to cached or hardcoded nodes
M
Deduplicate participants across multiple node responses using DID

28.4 SHOULD Implement

S
Periodic re-announcement — nodes SHOULD refresh their registry entry at a regular interval
S
Batch lobby queries — clients SHOULD use the batch endpoint for radius discovery
S
Local registry cache — clients SHOULD cache lobby → node mappings to reduce registry load
S
Federated registry peers — registries SHOULD expose /.well-known/difp/registry/peers

28.5 Explicitly Out of Scope (v0.4)

Node roles (PRIMARY / REPLICA / CACHE) — deferred to v0.5
Health checks and liveness probes — deferred to v0.5
Trust scoring and reputation — deferred to v0.5
DHT / gossip-based registry — deferred to v0.5
gRPC transport profile — deferred to v0.5
🌍 v0.4 goal: Given any GPS location on Earth, a DIFP client can deterministically compute a lobby ID, query a registry, and receive a list of nodes to contact — all without any prior knowledge of the network topology. That is the complete scope of v0.4.

13 Expansion Roadmap

The following table shows the DIFP roadmap. ✅ = shipped. 🔜 = upcoming.

A
Message Signing
Ed25519 signature spec for trade messages, enabling trustless cross-node verification
B
Supply & Demand Radar
How Ask and Donation signals aggregate per cell to produce a real-time scarcity/surplus heat map
C
Map Route Animation
How TA node cell pairs drive visualized food flow lines on a world map
D
IoT Integration
MQTT profile for automated Asks from farm sensors (soil moisture, harvest weight)
E
USSD / SMS Fallback
Minimal protocol profile for participants with no smartphone or data plan
F
Admin Monitoring Layer
Cross-node read access for platform oversight without write permissions
G
Dispute Resolution
Protocol-level mechanism for handling denied or cancelled trades
H
Multi-currency Pricing
How price fields are normalized across currency zones in federated networks
I
Conformance Test Suite
Reference test cases for validating geoToCellNumber, trade lifecycle, and federation handshake
J
Third-Party Registration
How non-Djowda apps register as DIFP participants via open API

14 How to Implement DIFP

A practical guide for developers who want to build a DIFP-conformant node from scratch.

1
Implement the Grid
Port 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.
2
Stand Up a Presence Store
Choose your storage backend (Firebase, PostgreSQL with PostGIS, DynamoDB, etc.). Create a table/collection for PresenceRecords indexed by cell_id and component_type. Expose the well-known endpoints from Section 10.
3
Implement the Trade Engine
Build the four-node write pattern (Section 8.1) adapted to your storage. Enforce the role-based status transitions (Section 7.4). Implement atomic writes for all state changes (Section 9).
4
Add the Catalog
For each country and component type you support, compile a PAD package (~6,000 items). Distribute it with your client apps. Wire up live availability sync for price and availability fields only.
5
Federate
Register your node at the DIFP node registry (coming in v0.2). Implement the federation handshake. Test cross-node trade routing with another DIFP node.
🚀 Quick Start: A minimal DIFP node only requires Steps 1–3. Steps 4 and 5 can be added incrementally. A working node with 10 participants is more valuable than a perfect spec with zero.

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.