Docs / Allocation rules

Choose and extend allocation rules

Use single-seller procurement or split fulfillment, and understand exactly what can be customized.

Lowest-price procurement

The default lowest-price-v1 rule selects one seller. Before selection, the SDK rejects offers that cannot fill the complete pool or exceed any buyer’s total cap. The cheapest eligible unit price wins; ASCII seller ID breaks ties. Charges use the selected seller’s quoted price.

Split fulfillment

import { cheapestCapacity, createRound } from "near-swarmkit";
const round = createRound({ ...spec, mechanismId: cheapestCapacity.id });

cheapest-capacity-v1 orders buyers by budget per unit, tightest first, then draws inventory from sellers in ascending price order. The result must fill all admitted demand. A null result is no sale. This is a greedy heuristic; failure does not prove that no feasible allocation exists.

A custom winner rule

const mostCapacity = {
  id: "capacity-first-v1",
  selectWinner(offers) {
    return [...offers].sort((a, b) => {
      const delta = BigInt(b.capacity) - BigInt(a.capacity);
      return delta < 0n ? -1 : delta > 0n ? 1
        : a.sellerId < b.sellerId ? -1 : a.sellerId > b.sellerId ? 1 : 0;
    })[0]?.sellerId ?? null;
  },
};
const round = createRound({ ...spec, mechanismId: mostCapacity.id }, Date.now, mostCapacity);

A custom split rule

Implement allocate(buyers, offers) instead of selectWinner. Return null or an array of { buyerId, sellerId, quantity }. Each buyer–seller pair must be unique. The SDK computes amounts at the revealed bid prices, verifies every buyer is filled exactly, and rejects over-budget or over-capacity results. It does not accept custom payment prices through this interface.

Keep replay deterministic

Give each implementation a versioned ID and supply the same implementation to createRound, replayRound, resumeRound, settlementPlanFromTranscript and auditSettlement. Never silently change an implementation behind an old ID. Avoid wall clocks, random tie-breaking or network calls inside allocation functions.

Inspect the decision

const decision = explainAllocation(round.snapshot());
console.log(decision.reason);
console.table(decision.buyers); // spend, unused budget, fulfillment lines
console.table(decision.sellers); // allocation or rejection reason

Call this only on a closed round. “Not selected by mechanism” does not imply a bid was invalid. The explanation reports the recorded outcome without pretending to expose the internal reasoning of arbitrary custom plugins.