Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.xandrlabs.ai/llms.txt

Use this file to discover all available pages before exploring further.

ALX Protocol reputation determines how much weight a Knowledge Block’s on-chain history carries in payout computation and access control. The on-chain contract (ReputationLogic.sol) produces an integer score from 0 to 1000 based on query volume and endorsements. Your SDK translates that integer into a floating-point reputation score (rs) and applies freshness decay before computing a payout. All functions are exported from @alx/protocol/economics.

Constants

ConstantValueDescription
RS_MIN0.01Minimum normalized reputation score. Non-zero so that new KBs with no history remain discoverable and earn a minimal payout.
RS_MAX3Maximum normalized reputation score. The top-tier KB earns 3× the base payout.
HALF_LIFE_DAYS30Freshness half-life in days. A KB published 30 days ago has a freshness multiplier of 0.5.

On-chain reputation formula

The reference contract derives the integer score using ReputationLogic.sol:
queryWeight       = min(500, queryVolume * 2)
endorsementWeight = min(100, endorsements * 20)
score             = min(1000, queryWeight + endorsementWeight)
A KB with 250 queries and 5 endorsements receives min(500, 500) + min(100, 100) = 600. You pass this integer to normalizeOnChainScore to get a protocol-usable rs.

Tier thresholds

Tier access gates which queries a KB is eligible to serve. You check tier eligibility with meetsTier.
Tierrs thresholdAccess
0>= 0Open — all KBs qualify regardless of reputation
1>= 0.5Verified — requires modest query history
2>= 1.0Premium — requires sustained query volume or endorsements
3>= 2.0Restricted — high-reputation KBs only

Functions

normalizeOnChainScore

Converts the raw on-chain reputation integer (0–1000) to a normalized float in [RS_MIN, RS_MAX]. You call this immediately after reading the score from the contract or subgraph.
function normalizeOnChainScore(score: number): number
Formula: RS_MIN + (score / 1000) × (RS_MAX - RS_MIN) Input values outside [0, 1000] are clamped before normalization.
score
number
required
On-chain reputation integer produced by ReputationLogic.sol. Valid range is 01000. Values outside this range are clamped.
return
number
Normalized reputation score in [0.01, 3]. A score of 0 maps to 0.01; a score of 1000 maps to 3.

freshnessMultiplier

Computes the exponential decay multiplier for a KB based on how long ago it was published. More recently published KBs receive a higher multiplier and therefore a higher payout.
function freshnessMultiplier(isoDate: string): number
Formula: 0.5 ^ (daysAgo / HALF_LIFE_DAYS) A KB published today returns 1.0. A KB published 30 days ago returns 0.5. A KB published 60 days ago returns 0.25. The multiplier approaches but never reaches 0 for very old KBs.
If isoDate is in the future (relative to the current clock), the function returns 1.0 rather than a value greater than 1.
isoDate
string
required
ISO-8601 publication date of the Knowledge Block (e.g. "2026-03-01T00:00:00.000Z"). The function parses this with new Date(), so any valid ISO-8601 string is accepted.
return
number
Freshness multiplier in (0, 1]. Returns exactly 1 when daysAgo <= 0.

meetsTier

Returns true if the given reputation score meets or exceeds the threshold for the requested tier. Use this before serving a query to enforce access control.
function meetsTier(rs: number, tier: number): boolean
rs
number
required
Normalized reputation score in [RS_MIN, RS_MAX]. Pass the output of normalizeOnChainScore or clampRS.
tier
number
required
Integer tier level 03. Tier 0 always returns true. An unknown tier (outside 03) returns true because the threshold lookup falls back to 0.
return
boolean
true if rs meets the tier threshold; false otherwise.

Complete example

The following example walks through the full reputation pipeline: read the on-chain score, normalize it, check tier access, compute freshness, and produce a payout.
import {
  normalizeOnChainScore,
  freshnessMultiplier,
  meetsTier,
  computePayout,
  RS_MIN,
  RS_MAX,
  HALF_LIFE_DAYS,
} from "@alx/protocol/economics";

// Step 1: Read on-chain score from ReputationLogic.sol or subgraph
const onChainScore = 600;       // min(500, 250*2) + min(100, 5*20) = 600
const publishedAt  = "2026-02-15T00:00:00.000Z";
const baseFee      = 0.0049;    // distributable share after 2% protocol fee
const requiredTier = 2;

// Step 2: Normalize to protocol rs range [RS_MIN, RS_MAX]
const rs = normalizeOnChainScore(onChainScore);
console.log(`RS: ${rs.toFixed(4)}`);  // → "RS: 1.8370"

// Step 3: Check tier eligibility before serving the query
if (!meetsTier(rs, requiredTier)) {
  throw new Error(`KB does not meet tier ${requiredTier} (rs=${rs.toFixed(4)})`);
}
console.log(`Tier ${requiredTier} access: granted`);

// Step 4: Compute freshness decay
const freshness = freshnessMultiplier(publishedAt);
console.log(`Freshness: ${freshness.toFixed(4)}`);  // decays with age

// Step 5: Compute payout
const payout = computePayout(baseFee, rs, freshness);
console.log(`Payout: ${payout.toFixed(6)} ETH`);
normalizeOnChainScore(1000) always returns exactly RS_MAX (3), and normalizeOnChainScore(0) always returns exactly RS_MIN (0.01). You do not need to clamp the output of normalizeOnChainScore before passing it to computePayoutcomputePayout clamps internally.