Integrations

Karatum agent scores as Chainlink-style data feeds (testnets)

Testnet pilotSource: docs/integrations/chainlink-feeds.md

Status: testnet pilot. Seven testnets: Ethereum Sepolia, Base Sepolia, OP Sepolia, Arbitrum Sepolia, Linea Sepolia, Unichain Sepolia and Soneium Minato. Nothing here is on a mainnet. This is not a Chainlink product and involves no Chainlink nodes or partnership: the contracts implement Chainlink's public AggregatorV3Interface so that code written for Chainlink price feeds can read a Karatum score without changes. Addresses and transactions are recorded in contracts/deployments/feeds.json (deployments for the feed, chainlink for the adapters).

What it is

The Karatum Reputation Feed (KaratumReputationFeed, see contracts/src/feeds/IKaratumReputation.sol) stores each agent's Karatum standing on-chain: certified task classes and tiers, K-rating (karats), Karatum Credit Score (KCS), shield tier, honesty band, recall flag and updatedAt. It has its own read interface (getStanding, getStandingFresh, ...).

Many protocols already know how to consume one kind of oracle: a Chainlink feed. They call latestRoundData(), check updatedAt against a max age, and use answer. The adapter gives every agent a feed of that shape:

consumer ──latestRoundData()──▶ KaratumScoreAggregator (one per agent + metric)
                                   │ getStanding(agent), paused()
                                   ▼
                          KaratumReputationFeed ◀── pushBatch / submitReport (Karatum updater, signed pull reports)
ContractSourceRole
KaratumScoreAggregatorcontracts/src/feeds/chainlink/KaratumScoreAggregator.solAggregatorV3Interface for one (agent, metric). Immutable, no owner, no storage, reads the feed live.
KaratumAggregatorFactorycontracts/src/feeds/chainlink/KaratumAggregatorFactory.solDeploys aggregators with CREATE2. Permissionless and idempotent: deploy(agent, metric), deployMany(agents, metric), predict(agent, metric), aggregatorOf(agent, metric).
AggregatorV3Interfacecontracts/src/feeds/chainlink/AggregatorV3Interface.solChainlink's interface restated (same signatures), so there is no external dependency.
ChainlinkScoreGatecontracts/src/feeds/examples/ChainlinkScoreGate.solExample consumer: a job board that lets an agent claim jobs only with K >= 18 and data younger than maxAge, using nothing but the standard Chainlink checks.

Metrics

decimals() is 0 for every metric.

metricidanswerNotes
KARATS00..24Highest K-rating among the agent's valid certificates. 0 = no valid certificate.
KCS1300..850Karatum Credit Score. 0 = not scored.
TIER20..3Highest certificate tier: 3 = 22K+, 2 = 18K+ (production grade), 1 = 12K+.

description() reads like Karatum KARATS / 0x376c25e79c4e4c6ba32245a8b56cc3826fa69fae. version() is 1.

Semantics (read before integrating)

  • updatedAt is the time Karatum last confirmed the standing: a push, a signed pull report, or the latest full-sync round (a full sync attests that every known agent is unchanged as of that block). startedAt == updatedAt. Staleness is the consumer's check, exactly as with Chainlink: block.timestamp - updatedAt <= maxAge. The feed's heartbeat is heartbeat() on the aggregator (86400 s on these testnets); the updater runs every 15 minutes and sends a full sync when the heartbeat is due, so data older than one day means the updater has stopped.
  • roundId == answeredInRound == updatedAt (unix seconds as uint80). It never decreases. It is not Chainlink's phase/aggregator round encoding; do not decode it.
  • Unknown agent: reverts NoDataPresent(). We chose the revert over a zero answer because it is how Chainlink's FluxAggregator answers a round without data ("No data present"), and because a consumer that forgets the updatedAt != 0 check cannot then mistake "never seen" for "scored 0". So answer == 0 always means a known agent that has no certificate / no score, or a recalled agent. Use try/catch if you want to treat it as a soft failure (ChainlinkScoreGate.check does).
  • Recalled agent: answer == 0 for every metric, whatever its stored score (fail closed). Where the chain has a Karatum incident log wired into the feed (Ethereum Sepolia and Base Sepolia), the recall is visible in the block the log records it, before any push, and the answer drops to 0 inside the same roundId. This is the one case where a round's answer changes after the fact, on purpose.
  • Paused feed: reverts FeedPaused(). While paused the feed reports every standing as stale; the adapter refuses to answer rather than hand out data its own source has disowned.
  • History is not on-chain. getRoundData(id) answers only when id is the current round id and reverts NoDataPresent() otherwise. Past values are in the feed's StandingUpdated events.
  • Push thresholds. The updater pushes a KCS change only past a deviation of 10 points (and any change of class, tier, karats, bands or recall at once), like a price feed's deviation threshold. Two chains can therefore show a KCS a few points apart; both are within the deviation and their updatedAt says how recent each is.
  • latestStanding() (not part of the Chainlink interface) returns the full standing plus the feed's own staleness verdict and never reverts.

Addresses

Same address on every chain listed. The feed and the factory were deployed through the deterministic CREATE2 deployer 0x4e59b44847b379578588920ca78fbf26c0b4956c with identical init code; the factory then deploys each aggregator with CREATE2 from (agent, metric), so an aggregator address is also the same on every chain.

Address
KaratumReputationFeed0x3e98094028cF9879A82B66f62f16edCaEfE7f16b
KaratumAggregatorFactory0x5Bb82Df838A24B6E67a8ab457B6e948d4B7AbACd
ChainchainIdExplorerFeedAdapters
Ethereum Sepolia11155111https://sepolia.etherscan.iosince 2026-10-172026-10-27
Base Sepolia84532https://sepolia.basescan.orgsince 2026-10-172026-10-27
OP Sepolia11155420https://sepolia-optimism.etherscan.iosince 2026-10-172026-10-27
Arbitrum Sepolia421614https://sepolia.arbiscan.iosince 2026-10-172026-10-27
Linea Sepolia59141https://sepolia.lineascan.build2026-10-272026-10-27
Unichain Sepolia1301https://sepolia.uniscan.xyz2026-10-272026-10-27
Soneium Minato1946https://soneium-minato.blockscout.com2026-10-272026-10-27

Scroll Sepolia was planned but skipped: the network has been shut down (Scroll's docs list its RPC and bridge as gone).

Aggregators deployed for the 11 agents the feed currently tracks (the same addresses on all seven chains). Any other agent's aggregator can be deployed by anyone with factory.deploy(agent, metric); factory.predict gives the address in advance.

AgentKARATS aggregatorKCS aggregator
0x220a2fca33365c39ab18fa293c70e1286da794340xF62d920d080d90Be0944877FD7A727FF8CF87D570xedbA610Eb05740ae0D9467BEE509fb900C0226e9
0x376c25e79c4e4c6ba32245a8b56cc3826fa69fae0x643b50CF039caC47833408ce7e5A9cCB4930a68B0xFd7783bBf794b04b8bd82e34858848dbd1086aA5
0x3a29c038bd7470000fc736ed887f2b1babe339bf0x7b1680cbC1E8D577B41C97fF5F234aDF1F4ED8960x028B342C78c43AB960cD750fFbFEBEA44E4A761D
0x3bfdeb284f8f4e39a6b9bff7deb1b44ed76e45470x043830f6B3A88F1644a49222bFBaf5e70bFAbfE00xBC69d334893A799716Fc9281DCE6691B782Aae6C
0x6a9d14170b0057ed9db84d24bf805b949b1e0dca0x642A2F25505A3c56d147509bB9ef4AA08b9667250xfdEaD40d611ba7fC7804461F3B3c5082C6103e9a
0x6d562ea9eb2d581ce065d9bf7ec6aca94115f6370x3709CB7b42A96641A5f1326315411A22E11B24990x68aF6155FC85c3ab88A8d6cf4c2f1c3e5948Fb16
0x7ae23c4fd31d3d84ff63cc0834b5619e9e5c94690xAEb3226D2a2Fc3443E6aAeCB1875231e9EB241520xcc4c82B7bE0F3768326FDB4BB6898A4EC14b26F0
0x863c1499f6501fd08102925b9630a19bc265b10c0x98011535B4207124f1A2a0fCe18BC70839335D950x761b7063671fb8B5da8d6bcc8FAe79485911425e
0x9b32b3d04592a42c7fe02c34fea7be75427e51390x1F88FB715226637d681eb8233eAc31bC4217D2D00x7A208dD6b8da47d710D870d54fD12326FC3a027A
0xc5a800af3695d5e40cdfc4bed8428789be1024280xDa5D1d88d33aCc2B318303bcE3F688579D31112e0x04E779635e3E0a2486c5384EbF9eFbE70E6E684b
0xe651b30ca7399daa3e8325df60751a17825196060xE04319056b08Ec0b78610363b6840a0aD6d76E790x3d692130f2D849b9B991Ac6C2eB2fF9EB9BD8d31

Source verification: Sourcify exact_match for the feed, the factory and all 22 aggregators on all seven chains.

Try it from a shell:

bash
cast call 0x643b50CF039caC47833408ce7e5A9cCB4930a68B "latestRoundData()(uint80,int256,uint256,uint256,uint80)" \
  --rpc-url https://rpc.sepolia.linea.build
cast call 0x643b50CF039caC47833408ce7e5A9cCB4930a68B "description()(string)" --rpc-url https://sepolia.base.org

Consumer snippet

Identical to a Chainlink price-feed consumer. Pass the aggregator address (or look it up in the factory).

solidity
import {AggregatorV3Interface} from "./AggregatorV3Interface.sol"; // or Chainlink's own file

contract AgentGate {
    uint256 public constant MAX_AGE = 1 days;
    int256 public constant MIN_KARATS = 18; // production-grade certificate

    function allowed(AggregatorV3Interface agentKarats) public view returns (bool) {
        try agentKarats.latestRoundData() returns (uint80 roundId, int256 answer, uint256, uint256 updatedAt, uint80 answeredInRound) {
            if (updatedAt == 0 || answeredInRound < roundId) return false;   // no data
            if (block.timestamp - updatedAt > MAX_AGE) return false;          // stale: fail closed
            return answer >= MIN_KARATS;                                       // recalled agents read 0
        } catch {
            return false;                                                      // unknown agent or paused feed
        }
    }
}

To find the aggregator of an arbitrary agent on-chain: KaratumAggregatorFactory(0x5Bb82Df838A24B6E67a8ab457B6e948d4B7AbACd).aggregatorOf(agent, 0 /* KARATS */) (0 address = not deployed yet; anyone can call deploy(agent, 0)).

A complete example with tests is ChainlinkScoreGate (contracts/test/feeds/ChainlinkAdapter.t.sol).

Limits and honesty notes

  • Testnets only. Testnet ETH, testnet deployments. The values are the standings Karatum's pipeline currently computes for the wallets bound in its testnet database (mostly Karatum's own demo and test agents), and nothing here carries economic weight.
  • Not Chainlink. No Chainlink DON signs these values. The interface is Chainlink's; the trust model is Karatum's.
  • Trust is in the feed owner. One owner EOA (0xa4AB…Af43, Ownable2Step) on this pilot controls the updater and the pull-report signer, and therefore what the feed says; it can also pause it. A multisig is planned before any mainnet use. The adapters and the factory have no owner and no admin functions.
  • Freshness depends on one updater service (Karatum's server, every 15 minutes, public RPCs). If it stops, data goes stale after the one-day heartbeat and consumers that check updatedAt stop accepting it. Anyone can still bring a standing in with a signed report from Karatum's API (submitReport), but that also depends on Karatum signing.
  • No on-chain history, see getRoundData above.
  • Same-block edge case. roundId is a timestamp, so two writes for one agent in the same block would share a round id. The updater never does that; a pull report and a push in the same block could.
  • Linea Sepolia, Unichain Sepolia and Soneium Minato have no Karatum incident log wired: a recall there arrives with the next push (within one 15-minute cycle), not live.