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.

When a query uses one or more Knowledge Blocks, the protocol distributes the associated fee across three recipients: the protocol itself, the curators of parent KBs in the attribution graph, and the curator of the KB being queried. Settlement is deterministic — given the same msg.value and on-chain state, the same distribution is always produced. All earnings accumulate in a per-address ledger inside the contract, and curators withdraw them on demand (pull-based).

Settlement formula

For a settleQuery call with msg.value as the query payment:
protocolFee     = msg.value * protocolFeesBps / 10000
distributable   = msg.value - protocolFee
parentRoyalty_i = distributable * royaltyShareBps_i / 10000
curatorAmount   = distributable - sum(parentRoyalties)
The reference deployment applies a 2% protocol fee (200 bps). The remaining 98% is the distributable amount. Parent royalties are deducted from the distributable amount in basis points, and the remainder goes to the queried KB’s curator.
The sum of royaltyShareBps across all attribution links in a single settlement call must not exceed 10000 (100%). A sum exceeding 10000 would attempt to distribute more than the available distributable amount and will revert.
Conservation invariant: protocolFee + sum(parentRoyalties) + curatorAmount = msg.value. Every wei sent is accounted for.

Calling settleQuery

import { ethers } from "ethers";
import { REGISTRY_ABI } from "@alx/protocol";

const CONTRACT_ADDRESS = "0xD1F216E872a9ed4b90E364825869c2F377155B29";
const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const registry = new ethers.Contract(CONTRACT_ADDRESS, REGISTRY_ABI, wallet);

const queryId = ethers.id("my-query-session-id"); // bytes32
const kbIds = [
  "0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2",
  // additional KB IDs if multiple KBs were used
];
const queryFee = ethers.parseEther("0.005"); // the fee to distribute

const tx = await registry.settleQuery(
  queryId,    // bytes32 — unique identifier for this query
  kbIds,      // bytes32[] — KBs that served this query
  queryFee,   // uint256 — fee amount in wei
  { value: queryFee }  // msg.value must equal queryFee
);

await tx.wait();
queryFee = 0 is valid and still counts as a usage event for reputation scoring in the reference contract. Passing zero fees lets you record usage without moving value.

Pull-based withdrawals

Earnings do not transfer to curators immediately. Instead, they accumulate in pendingWithdrawals[address] inside the contract. Each curator must call the contract’s withdraw function to claim their balance. This pull-payment pattern protects settlement atomicity — a failed transfer to one recipient cannot cause the entire settlement transaction to revert.
// Check pending balance (example — confirm method name from REGISTRY_ABI)
const pending = await registry.pendingWithdrawals(curatorAddress);
console.log("Unclaimed earnings:", ethers.formatEther(pending), "ETH");

// Withdraw
const withdrawTx = await registry.withdraw();
await withdrawTx.wait();

Economics primitives

The @alx/protocol package exports pure, deterministic functions for computing reputation-weighted payouts. These functions implement the reference economics model and produce identical results across all implementations.

computePayout

Compute the payout for a KB given a base fee, a reputation score, and a freshness multiplier:
import { computePayout } from "@alx/protocol";

// payout = base * clamp(rs) * freshness
// Rounded to 6 decimal places, minimum 0.
const payout = computePayout(
  0.005,   // base fee in ETH (or any consistent unit)
  1.2,     // reputation score (rs)
  0.87,    // freshness multiplier (0–1)
);
// e.g. 0.005 * 1.2 * 0.87 ≈ 0.00522

normalizeOnChainScore

Convert the on-chain integer score (0–1000) produced by ReputationLogic.sol to the floating-point RS value used by economics functions:
import { normalizeOnChainScore } from "@alx/protocol";

// Maps [0, 1000] → [RS_MIN (0.01), RS_MAX (3.0)]
const rs = normalizeOnChainScore(onChainScore);
The on-chain score is derived from:
queryWeight       = min(500, queryVolume * 2)
endorsementWeight = min(100, endorsements * 20)
score             = min(1000, queryWeight + endorsementWeight)

freshnessMultiplier

Compute an exponential freshness decay multiplier based on the KB’s publish date. Returns a value in (0, 1] where 1.0 means just published. The half-life is 30 days.
import { freshnessMultiplier } from "@alx/protocol";

// multiplier = 0.5 ^ (daysAgo / 30)
const freshness = freshnessMultiplier("2026-03-01T00:00:00.000Z");
// A KB published ~45 days ago → ≈ 0.354

meetsTier

Check whether a reputation score meets the minimum threshold for a given access tier:
import { meetsTier } from "@alx/protocol";

meetsTier(rs, tier);

Tier thresholds

Tiers gate access to KBs. A curator sets the tier at publish time; consumers must hold a reputation score that meets the minimum threshold to query the KB.
TierLabelMinimum RS
0Openrs >= 0 (always passes)
1Verifiedrs >= 0.5
2Premiumrs >= 1.0
3Restrictedrs >= 2.0
import { meetsTier, normalizeOnChainScore } from "@alx/protocol";

const rs = normalizeOnChainScore(onChainScore); // e.g. 0.85

console.log(meetsTier(rs, 0)); // true  — open
console.log(meetsTier(rs, 1)); // true  — rs 0.85 >= 0.5
console.log(meetsTier(rs, 2)); // false — rs 0.85 < 1.0
console.log(meetsTier(rs, 3)); // false — rs 0.85 < 2.0

Settlement example with reputation weighting

import {
  computePayout,
  normalizeOnChainScore,
  freshnessMultiplier,
  meetsTier,
} from "@alx/protocol";
import { ethers } from "ethers";
import { REGISTRY_ABI } from "@alx/protocol";

const CONTRACT_ADDRESS = "0xD1F216E872a9ed4b90E364825869c2F377155B29";

async function settleWithReputationWeighting(
  kbId: string,
  onChainScore: number,
  publishedAt: string,
  tier: number,
) {
  // 1. Normalize reputation score and compute freshness
  const rs = normalizeOnChainScore(onChainScore);
  const freshness = freshnessMultiplier(publishedAt);

  // 2. Check tier eligibility
  if (!meetsTier(rs, tier)) {
    throw new Error(`Reputation score ${rs} does not meet tier ${tier} threshold`);
  }

  // 3. Compute expected payout (off-chain estimate)
  const baseFee = 0.005; // ETH
  const estimatedPayout = computePayout(baseFee, rs, freshness);
  console.log(`Estimated payout: ${estimatedPayout} ETH`);

  // 4. Settle on-chain
  const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
  const registry = new ethers.Contract(CONTRACT_ADDRESS, REGISTRY_ABI, wallet);

  const queryFee = ethers.parseEther(baseFee.toString());
  const queryId = ethers.id(`query-${Date.now()}`);

  const tx = await registry.settleQuery(
    queryId,
    [kbId],
    queryFee,
    { value: queryFee },
  );
  await tx.wait();

  console.log("Settlement complete");
}