Leg Lifecycle
From bid to settlement: how a Leg row and its on-chain PDA come into existence, travel through the protocol, and unlock payment.
The short version: the shipper never picks a courier. Agents bid, the coordinator's route optimizer picks the winning chain, legs are created atomically from the winning bids, and mirrored on-chain.
1. Shipper lists a package
- Shipper's wallet signs
list_packagevia the dashboard. - The instruction creates two on-chain PDAs:
PackageAccount+VaultAccount(holds escrowed SOL up tomaxBudgetLamports). - API persists the
Packagerow withonChainPackage+onChainVault+listSignature. Status:listed. - WebSocket broadcasts
PACKAGE_LISTED.
No legs yet. No swarm yet.
2. Agents bid
Every agent daemon polls GET /packages every 10s. For each package with status: listed:
computeOptimalLeg— picks the segment of the agent's pre-declared itinerary that best overlaps the package's origin→dest. Returns pickup/dropoff coords, distance, estimated duration, and detour delta.detourExceedsLimit— hard gate onmaxDetourKm/maxDetourMinutesfrombidSettings.computeCost— fuel × distance + time × hourly rate, EUR → SOL. Canonical cost model; the API reuses it.reasonAboutBid— LLM call togpt-oss-120breasoning about whether to bid. Always has a rule-based fallback if the LLM is unavailable.- If
shouldBid, the agent POSTs to/bidswith ed25519-signed headers (X-Pubkey,X-Nonce,X-Signature).
A bid is a proposed leg. It is not yet a leg — the coordinator still has to pick winners.
3. Coordinator evaluates — evaluateSwarmFormation
Triggered on every accepted bid. Threshold: MIN_BIDS_FOR_EVALUATION = 1 (as soon as the first bid lands, the coordinator checks if a swarm can form). Inside a serializable Postgres transaction:
- Re-fetch the package. Bail if it's not
listedor if a swarm already exists (idempotency against races). - Pull all non-expired bids for the package.
- Pull reputation rows for every unique bidder →
repMap: Map<pubkey, score/100>. findOptimalRelayChain— picks either the single best bid or a multi-hop chain whose waypoints form a valid origin→dest relay, total cost ≤maxBudgetSol, and whose reputation-weighted score is best. Bounded γ=0.08 nudge toward higher-rep chains prevents cartels.allocateReputationWeightedPayments— splits the payout pool across the winning bids: α=0.7 of each payment is proportional to the bid itself; 1−α is weighted by reputation. First-meeting Sybil ceiling at 0.6.tx.swarm.createwith nestedlegs.create— oneLegrow per bid in the chain.- Package status flips to
swarm_forming. Transaction commits.
Legs now exist in Postgres. Still no on-chain accounts.
4. On-chain commit — coordinatorFormAndAssignSwarm
After the Postgres transaction commits, the coordinator keypair executes one Solana transaction that bundles:
form_swarm(totalLegs, totalLamports)— createsSwarmAccountPDA at[b"swarm", package]. Reserves the total payout from the vault.- N ×
assign_leg(legIndex, courierPubkey, paymentLamports)— createsLegAccountPDAs at[b"leg", swarm, [legIndex]]for each leg. Each assign bumps the courier's on-chain reputationlegs_acceptedcounter.
Post-confirm, the API updates swarm.onChainSwarm, derives and writes each leg.onChainLeg, and upserts AgentReputation rows in Postgres. WebSocket broadcasts SWARM_FORMED.
If on-chain fails, the Swarm row is marked failed and the package reverts to listed so a retry can form a fresh swarm.
5. Delivery + confirmation
Confirmation model (multi-leg aware). Legs confirm in strict index order. The program enforces leg_index == swarm.completed_legs, so leg i+1 cannot confirm until leg i has.
- Final leg (
leg_index == total_legs - 1): the shipper signsconfirm_leg.next_leg_accountmust beNone. - Intermediate leg (
leg_index < total_legs - 1): the next-hop courier signsconfirm_leg— their signature is the handoff attestation. - Errors:
LegOutOfOrder,MissingNextLeg,UnexpectedNextLeg,UnauthorizedRecipient.
On-chain, confirm_leg transfers leg.agreedPaymentSol directly from the vault to the courier's wallet via a PDA-signed system transfer. Reputation legs_completed bumps. The off-chain mirror then applies the Bayesian ramp: new_score = old + (1 − old) × gain_factor × 0.05 (diminishing returns toward 1.0). The on-chain reliability_score stores the result scaled to 0–100.
State machine summary
Package: listed → swarm_forming → in_transit → delivered
↘ failed (on-chain error, retry path)
Swarm: forming → active → settled
↘ failed
Leg: pending → completed (in strict legIndex order)
Key invariants
- A package has at most one active swarm (unique constraint + serializable tx).
- A leg's
agreedPaymentSolis locked at creation time — it does NOT recompute on settle. - The vault's lamport balance at
confirm_legtime must coverleg.payment_lamports. confirm_legmarks the leg complete before the system transfer (CEI pattern).- Legs confirm in strict
legIndexorder. Out-of-order confirms are rejected on-chain withLegOutOfOrder.
Why this architecture
- Shipper doesn't pick couriers. The market picks; the coordinator executes allocation. Shippers can't prefer bad actors who offer side payments.
- Legs are created from bids, not quotes. Every winning leg has a committed, signed bid behind it — no phantom couriers.
- Single serializable tx creates the whole swarm. Prevents partial swarms under concurrent bid arrivals.
- On-chain reputation lives in a PDA; Postgres mirrors it. Fast dashboard reads, durable on-chain source of truth. Reputation changes only via protocol actions (
assign_leg,confirm_leg) — no standaloneupdate_reputationinstruction exists. - Shipper signs
confirm_leg. Only the recipient can honestly attest "I received the goods." Courier self-attestation is a trust hole.