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).
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.
| Tier | Label | Minimum RS |
|---|
0 | Open | rs >= 0 (always passes) |
1 | Verified | rs >= 0.5 |
2 | Premium | rs >= 1.0 |
3 | Restricted | rs >= 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");
}