At a glance#
| Number | Value | What it drives |
|---|---|---|
| Transactions, FY 2025–26 | 218M · ~7 a second on average | orders are not the load; correctness is |
| Cumulative | 500M on 25 July 2026 | includes mobility, tickets, finance |
| Retail sellers live | 1.16 lakh+ in 630+ cities (Dec 2025) | catalogue size; search fan-out |
| Design target (assumed) | ~200 orders / s · ~25 signed calls each | ~5,000 signed calls / s network-wide |
| Live search fan-out (assumed) | ~150 seller apps per domain and city | cache catalogues; don't broadcast per query |
Registry /lookup limit (recommended) |
7,600 a minute (~127 / s) | cache public keys by subscriber and key id |
| Settlement | daily cycle | two-sided /settle, matched and netted |
| Complaints | 48 h to respond · 30 days to resolve | IGM deadlines and escalation |
Architecture#
DiagramParties talk directly; shared services stay thin
%% title: Parties talk directly; shared services stay thin
flowchart TD
BUY@{ icon: "sd:users", label: "Buyers<br/>any app they already use" }
BAP["Buyer apps · BAP<br/>catalogue cache · cart · collector"]:::core
GW@{ icon: "sd:gateway", label: "Gateway<br/>multicasts search only" }
REG@{ icon: "sd:id", label: "Registry<br/>subscribers · keys · domains" }
BPP["Seller apps · BPP<br/>catalogues · quotes · orders"]:::hot
IGM@{ icon: "sd:chat", label: "Issues · IGM<br/>48 h · 30 days · ODR" }
LSP@{ icon: "sd:delivery", label: "Logistics apps<br/>bought by the seller app" }
SOR@{ icon: "sd:store", label: "Sellers on record<br/>shops like Rehana's" }
SA@{ icon: "sd:bank", label: "Settlement agency<br/>match · net · RTGS batch" }
OBS@{ icon: "sd:dashboard", label: "Network observability<br/>masked logs · scorecards" }
BUY ==> BAP
BAP ==>|"search"| GW
BAP ==>|"select · init · confirm"| BPP
BAP --> IGM
GW ==>|"by domain and city"| BPP
GW -.->|"keys, cached"| REG
BPP ==>|"cascaded logistics"| LSP
BPP --> SOR
BPP -->|"settle"| SA
LSP -.->|"every NP pushes logs"| OBS
class BUY client
class GW,REG edge
class IGM service
class LSP external
class SOR store
class SA store
class OBS service- Only search touches a gateway. Seller apps answer
on_searchdirectly; select through status go peer to peer, signed (Ed25519 over a BLAKE-512 digest) and verified against cached registry keys. - Search is served from the buyer app's own cache, filled by full catalogue refreshes and 1–30 minute incremental pushes, ordered by the seller's timestamps.
- The seller app orchestrates delivery as a logistics buyer inside the retail order.
DiagramHold, then release: one confirm at the seller app
%% title: Hold, then release: one confirm at the seller app
flowchart TD
CONF["/confirm O-77<br/>network-unique order id"]:::service
DUP{"Order id seen before?"}
VAL{"Matches quote and terms?"}
NACK["NACK 31002<br/>buyer app cancels 999"]:::bad
HOLD@{ icon: "sd:queue", label: "Order HELD<br/>on_confirm in outbox" }
ACKED{"Buyer app ACKs<br/>on_confirm in window?"}
REL["Released to the shop<br/>Accepted"]:::core
C998["Cancel 998<br/>push on_cancel"]:::bad
CONF ==> DUP
DUP -->|"yes: same answer"| HOLD
DUP ==>|"no"| VAL
VAL -->|"no"| NACK
VAL ==>|"yes, ACK"| HOLD
HOLD ==> ACKED
ACKED ==>|"yes"| REL
ACKED -->|"no"| C998
class HOLD queueKey flows#
DiagramThe Pune thali, from cached search to pickup
%% title: The Pune thali, from cached search to pickup sequenceDiagram autonumber participant B as Buyer app participant S as Seller app participant L as Logistics app Note over B: search answered from its catalogue cache B->>S: select thali S-->>B: ACK S->>L: logistics search Moradabad to Pune L->>S: on_search · options S->>B: on_select · item + delivery charge B->>S: init · billing, address, payment terms S->>B: on_init · cancellation and settlement terms B->>S: confirm O-77 · buyer has paid S-->>B: ACK · order held S->>L: logistics confirm S->>B: on_confirm Accepted B-->>S: ACK · released to the shop L->>S: on_status Order-picked-up S->>B: on_status Order-picked-up
DiagramA silent confirm ends the same way on both sides
%% title: A silent confirm ends the same way on both sides
sequenceDiagram
autonumber
participant B as Buyer app
participant S as Seller app
B->>S: confirm O-77
alt no ACK
B->>S: confirm O-77 again · same id, capped retries
Note over B: still nothing · cancel with 999, refund
else ACK but no on_confirm reaches the buyer app
S-->>B: ACK · order held, not sent to shop
S->>B: on_confirm · retried from the outbox
Note over S: no ACK in the window · cancel with 998, push on_cancel
Note over B: no on_confirm in its window · cancel with 999, refund
else lost ACK of on_confirm
S->>B: on_confirm O-77 resent
B-->>S: ACK again · same order, nothing new
endDiagramMoney moves only when both books match
%% title: Money moves only when both books match
sequenceDiagram
autonumber
participant C as Buyer app · collector
participant R as Seller app · receiver
participant A as Settlement agency
C->>A: settle · inter-NP 1,000, collector 50, self 50
R->>A: settle · inter-NP 1,000, collector 50, self 200, provider 800
A->>A: dedupe, validate, match, balance check
alt amounts match
A->>C: on_settle SETTLED
A->>R: on_settle SETTLED · net batch to RTGS, 800 to the seller
else mismatch
A->>R: on_settle NOT_SETTLED
R->>C: recon · breakup
C->>R: on_recon · agree or difference
Note over C,R: agreed · settle next cycle · else an IGM issue
endData model#
DiagramCore entities, each held by its own participant
%% title: Core entities, each held by its own participant
classDiagram
class Participant {
+subscriberId : string · domain name
+role : buyer app, seller app, both
+domains : list
+cities : list
}
class SigningKey {
+uniqueKeyId : string
+publicKey : Ed25519
+validUntil : time
}
class Order {
+networkOrderId : string · set by the buyer app
+transactionId : string
+state : Created, Accepted, In-progress, Completed, Cancelled
+quote : items + delivery + fees
}
class Fulfillment {
+id : string
+type : delivery, return, RTO
+state : Pending to Order-delivered
}
class LogisticsOrder {
+id : string
+provider : string
+pickupCode : string
+deliveryCode : string
}
class SettlementLine {
+orderId : string
+interParticipant : amount
+collector : amount
+self : amount
+provider : amount
+status : SETTLED or NOT_SETTLED
}
class Issue {
+id : string
+type : issue, grievance, dispute
+actions : append-only list
+expectedResolution : time
}
Participant "1" --> "many" SigningKey : publishes
Order "1" --> "many" Fulfillment : tracks
Fulfillment "1" --> "0..1" LogisticsOrder : bought as
Order "1" --> "many" SettlementLine : settled by
Order "1" --> "many" Issue : complained about in- There is no shared copy: the buyer app and the seller app each hold their own
Orderkeyed by the network order id; logistics and settlement lines link back to it. - Every request and callback pair is identified by
transaction_idplusmessage_id; retries carry the same ids, and older duplicates are NACKed as stale.
Lifecycle#
DiagramAn order on both sides, or on neither
%% title: An order on both sides, or on neither stateDiagram-v2 [*] --> CONFIRM_SENT: buyer app names the order CONFIRM_SENT --> HELD_AT_SELLER: seller ACKs CONFIRM_SENT --> CANCELLED_999: NACK or silence after retries HELD_AT_SELLER --> ACCEPTED: on_confirm ACKed, released HELD_AT_SELLER --> CANCELLED_998: on_confirm NACKed or unanswered ACCEPTED --> IN_PROGRESS: packed, picked up IN_PROGRESS --> COMPLETED: delivered ACCEPTED --> CANCELLED: cancel or force cancel IN_PROGRESS --> CANCELLED: RTO initiated COMPLETED --> [*] CANCELLED_999 --> [*] CANCELLED_998 --> [*] CANCELLED --> [*] class COMPLETED ok class CANCELLED_999 bad class CANCELLED_998 bad class HELD_AT_SELLER queue
Decisions#
| Decision | Chose | Over | Because |
|---|---|---|---|
| Discovery | Cached catalogues, full + incremental refresh | Live broadcast per search | fan-out per query waits for the slowest of ~150 |
| Order identity | Buyer app names it, network-unique | Seller app assigns ids | retries collide on one id |
| Release to shop | After on_confirm is ACKed |
On /confirm |
the shop never packs an order the buyer app lacks |
| Failure outcome | Fixed codes, 999 and 998 | Ad hoc retries | both sides reach "cancelled" without talking |
| Courier cancels | Re-procure, then cascade cancel | Cancel the order at once | most orders survive one courier |
| Settlement | Two-sided /settle, agency matches |
Collector pays on its own say | neither side trusts the other's books |
| Settlement timing | After terminal state + return window | At delivery | refunds become reductions, not clawbacks |
When it breaks#
| Failure | What users see | Contain it with |
|---|---|---|
| Seller app down at peak | its items unavailable | per-counterparty breaker; keep cached catalogue; 999 refunds |
ACKs confirm, never sends on_confirm |
"confirming…", then refund | hold-then-release; outbox-age alerts; network attempt-to-confirm rate |
| Signing key leaks | nothing, if revoked fast | new key id; short key-cache TTL; refresh on failure |
| Clock skew | callbacks rejected as stale | NTP; ±10 s rule; skew metric per counterparty |
| Amounts disagree | seller paid late | NOT_SETTLED; recon and on_recon; IGM if unresolved |
| Collector account short | seller paid late | balance check fails; orders wait until funded |
Say it in the interview#
- "The protocol is the transaction manager": no shared database, so consistency comes from ids, idempotent request and callback pairs, and failure rules both sides apply alone.
- Hold, then release: the seller app sends an order to the shop only after the buyer app acknowledges
on_confirm; silence becomes a refund, never a phantom delivery. - Pay the broadcast once: search fans out to every seller app, so buyer apps cache catalogues and order refreshes by the seller's timestamps; absence isn't deletion.
- Every edge is signed: Ed25519 over a BLAKE-512 digest of the raw bytes, keys from the registry, cached because lookups are rate-limited.
- The seller app orchestrates delivery: logistics is another network transaction inside the retail one; re-procure first, cascade cancels and RTO second.
- Money moves on a match: collector and receiver both send
/settle; the agency nets matched orders into one batch and never decides who's right. - Watch the edges, not the boxes: callback latency, NACKs by code and attempt-to-confirm rate from masked logs find a silent participant in minutes.