Install
openclaw skills install kolect-sentimentQuery, check freshness, check pending requests, or request new sentiment updates on-chain for supported symbols and time windows via the Kolect Sentiment Fee...
openclaw skills install kolect-sentimentUse this skill for the Kolect Sentiment Feed contract, an on-chain sentiment oracle on Base.
It stores normalized sentiment for supported (symbol, timeWindow) pairs through a request–response flow:
Use this skill when the user wants to:
0x6783ab3c181976e8c960c43d711aaf4da79a4e4bhttps://base.blockscout.com/address/0x6783ab3c181976e8c960c43d711aaf4da79a4e4bhttps://kolect.infohttps://x.com/kolect_infoUsually used to inspect current state:
getLatest(symbol, timeWindow)isFresh(symbol, timeWindow)hasPendingRequest(symbol, timeWindow)These are normally read calls and do not require the contract request fee.
Used to initiate a new oracle update:
requestUpdate(symbol, timeWindow)This is not a free read. It is a state-changing transaction:
Never present requestUpdate as a free data read.
Each feed is identified by:
symboltimeWindowExamples:
(BTC, 1d)(ETH, 1h)Each feed stores:
negativeBpsneutralBpspositiveBpsdataTimestamp — off-chain data timeupdatedAt — on-chain fulfillment timenegativeBps + neutralBps + positiveBps = 10000
Interpretation:
10000 = 100.00%2500 = 25.00%5000 = 50.00%Always preserve and validate this rule.
Function:
requestUpdate(string symbol, string timeWindow)
Should only succeed if:
Publisher:
UpdateRequestedFunction:
fulfillRequest(requestId, negativeBps, neutralBps, positiveBps, dataTimestamp)
Result:
Function:
failRequest(requestId, errorCode)
Used when data retrieval or fulfillment fails.
requestUpdate(string symbol, string timeWindow)getLatest(symbol, timeWindow)isFresh(symbol, timeWindow)hasPendingRequest(symbol, timeWindow)fulfillRequest(...)failRequest(...)setSupportedSymbol(...)setSupportedTimeWindow(...)setUpdateInterval(...)setRequestFee(...)withdrawFees(...)Do not imply ordinary users can call owner-only functions.
Important indexing events:
UpdateRequestedRequestFulfilledRequestFailedUse these when discussing dashboards, Dune, subgraphs, analytics, or automation.
symbol and timeWindowgetLatest(symbol, timeWindow)dataTimestampupdatedAtisFresh(symbol, timeWindow)requestUpdate may be appropriatesymbol and timeWindowrequestUpdate(symbol, timeWindow)Likely reasons:
When reporting sentiment, use:
Do not report only raw integers unless explicitly requested.
Whenever sentiment data is given, validate:
negativeBps + neutralBps + positiveBps == 10000dataTimestamp = off-chain data timeupdatedAt = on-chain update timeIf the sum is not 10000, flag it as invalid.
from web3 import Web3
RPC_URL = "https://mainnet.base.org"
CONTRACT_ADDRESS = Web3.to_checksum_address("0x6783ab3c181976e8c960c43d711aaf4da79a4e4b")
ABI = [
{
"inputs": [
{"internalType": "string", "name": "symbol", "type": "string"},
{"internalType": "string", "name": "timeWindow", "type": "string"}
],
"name": "getLatest",
"outputs": [
{"internalType": "uint256", "name": "negativeBps", "type": "uint256"},
{"internalType": "uint256", "name": "neutralBps", "type": "uint256"},
{"internalType": "uint256", "name": "positiveBps", "type": "uint256"},
{"internalType": "uint256", "name": "dataTimestamp", "type": "uint256"},
{"internalType": "uint256", "name": "updatedAt", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
}
]
w3 = Web3(Web3.HTTPProvider(RPC_URL))
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=ABI)
symbol = "BTC"
time_window = "1d"
negative_bps, neutral_bps, positive_bps, data_timestamp, updated_at = (
contract.functions.getLatest(symbol, time_window).call()
)
print("Negative:", negative_bps, f"({negative_bps / 100:.2f}%)")
print("Neutral:", neutral_bps, f"({neutral_bps / 100:.2f}%)")
print("Positive:", positive_bps, f"({positive_bps / 100:.2f}%)")
print("Data timestamp:", data_timestamp)
print("Updated at:", updated_at)
from web3 import Web3
RPC_URL = "https://mainnet.base.org"
CONTRACT_ADDRESS = Web3.to_checksum_address("0x6783ab3c181976e8c960c43d711aaf4da79a4e4b")
ABI = [
{
"inputs": [
{"internalType": "string", "name": "symbol", "type": "string"},
{"internalType": "string", "name": "timeWindow", "type": "string"}
],
"name": "isFresh",
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "string", "name": "symbol", "type": "string"},
{"internalType": "string", "name": "timeWindow", "type": "string"}
],
"name": "hasPendingRequest",
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
"stateMutability": "view",
"type": "function"
}
]
w3 = Web3(Web3.HTTPProvider(RPC_URL))
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=ABI)
symbol = "ETH"
time_window = "1h"
fresh = contract.functions.isFresh(symbol, time_window).call()
pending = contract.functions.hasPendingRequest(symbol, time_window).call()
print("Fresh:", fresh)
print("Pending:", pending)
from web3 import Web3
RPC_URL = "https://mainnet.base.org"
PRIVATE_KEY = "YOUR_PRIVATE_KEY"
ACCOUNT = "YOUR_WALLET_ADDRESS"
CONTRACT_ADDRESS = Web3.to_checksum_address("0x6783ab3c181976e8c960c43d711aaf4da79a4e4b")
ABI = [
{
"inputs": [
{"internalType": "string", "name": "symbol", "type": "string"},
{"internalType": "string", "name": "timeWindow", "type": "string"}
],
"name": "requestUpdate",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]
w3 = Web3(Web3.HTTPProvider(RPC_URL))
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=ABI)
symbol = "BTC"
time_window = "1d"
request_fee_wei = 0 # replace with actual fee if needed
tx = contract.functions.requestUpdate(symbol, time_window).build_transaction({
"from": ACCOUNT,
"nonce": w3.eth.get_transaction_count(ACCOUNT),
"value": request_fee_wei,
"gas": 300000,
"gasPrice": w3.eth.gas_price,
"chainId": 8453
})
signed_tx = w3.eth.account.sign_transaction(tx, PRIVATE_KEY)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print(tx_hash.hex())
import { ethers } from "ethers";
const RPC_URL = "https://mainnet.base.org";
const CONTRACT_ADDRESS = "0x6783ab3c181976e8c960c43d711aaf4da79a4e4b";
const ABI = [
"function getLatest(string symbol, string timeWindow) view returns (uint256 negativeBps, uint256 neutralBps, uint256 positiveBps, uint256 dataTimestamp, uint256 updatedAt)"
];
const provider = new ethers.JsonRpcProvider(RPC_URL);
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, provider);
const result = await contract.getLatest("BTC", "1d");
console.log(Number(result.negativeBps), Number(result.neutralBps), Number(result.positiveBps));
console.log(Number(result.dataTimestamp), Number(result.updatedAt));
import { ethers } from "ethers";
const RPC_URL = "https://mainnet.base.org";
const CONTRACT_ADDRESS = "0x6783ab3c181976e8c960c43d711aaf4da79a4e4b";
const ABI = [
"function isFresh(string symbol, string timeWindow) view returns (bool)",
"function hasPendingRequest(string symbol, string timeWindow) view returns (bool)"
];
const provider = new ethers.JsonRpcProvider(RPC_URL);
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, provider);
const fresh = await contract.isFresh("ETH", "1h");
const pending = await contract.hasPendingRequest("ETH", "1h");
console.log("Fresh:", fresh);
console.log("Pending:", pending);
import { ethers } from "ethers";
const RPC_URL = "https://mainnet.base.org";
const PRIVATE_KEY = "YOUR_PRIVATE_KEY";
const CONTRACT_ADDRESS = "0x6783ab3c181976e8c960c43d711aaf4da79a4e4b";
const ABI = [
"function requestUpdate(string symbol, string timeWindow) payable"
];
const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet);
const tx = await contract.requestUpdate("BTC", "1d", {
value: 0n // replace with actual request fee if needed
});
console.log(tx.hash);
await tx.wait();
An AI agent should use the feed in this order:
getLatest(symbol, timeWindow)
validate BPS values
isFresh(symbol, timeWindow)
hasPendingRequest(symbol, timeWindow)
only if stale and no pending request exists:
requestUpdate(symbol, timeWindow)Recommended phrasing:
The Kolect Sentiment Feed lets the agent consume verifiable on-chain sentiment state. The agent should first read current state, then verify freshness, and only request a new update when needed.
requestUpdate.The model may assume:
The model must not assume without checking:
These are dynamic contract states.
Do not:
dataTimestamp and updatedAtrequestUpdate before checking freshness and pending stateChain: Base
Contract: 0x6783ab3c181976e8c960c43d711aaf4da79a4e4b
Main user action
requestUpdate(symbol, timeWindow)Main read functions
getLatest(symbol, timeWindow)isFresh(symbol, timeWindow)hasPendingRequest(symbol, timeWindow)Publisher functions
fulfillRequest(...)failRequest(...)Key events
UpdateRequestedRequestFulfilledRequestFailedCore invariant
negativeBps + neutralBps + positiveBps = 10000