usingtellorpull
v0.1.0
Published
Tellor Layer → EVM pull oracle: fetch attested reports from Tellor Layer for use on Ethereum and other EVM chains
Readme
UsingTellorPull
Pull oracle: Tellor Layer → EVM. Fetch attested reports from Tellor Layer API, re-encode for EVM, include in your tx. Your contract verifies via Tellor's DataBridge and uses the value in one tx.
Use this package with your own npm project. See the SampleTellorUserPull repo for a complete usage example.
Contents
- API – Fetch/encoding helpers + explicit pull modes (consensus-first, consensus-only, optimistic-only). Build output in
dist/.
Flow
- Caller uses this library to fetch attestation + validator set + signatures from Tellor Layer and encode them for EVM.
- If the DataBridge's validator set is stale, caller uses
getValsetUpdatePayloadsto build update transactions (skip relay). - Caller passes
attestData,validators,sigsinto your contract (e.g. as calldata). - Your contract verifies via
dataBridge.verifyOracleData(...)and uses the value in the same tx.
Assumption: User deploys on an EVM chain where Tellor's DataBridge is deployed.
Install
npm install usingtellorpull ethersQuick start
Production integrations should bind the pull payload to the destination DataBridge state before submitting calldata. Your contract still verifies via DataBridge and must enforce application policy (expected queryId, freshness, power, and bounds) before using the value.
import {
getVerifiedPullPayload,
encodeBridgeCallArgsFromPayload,
spotPriceQueryId,
TELLOR_LAYER_API,
type BridgeState,
} from "using-tellor-pull";
const queryIdHex = spotPriceQueryId("eth", "usd");
const bridgeState: BridgeState = {
timestamp: (await dataBridge.validatorTimestamp()).toString(),
powerThreshold: (await dataBridge.powerThreshold()).toString(),
checkpoint: await dataBridge.lastValidatorSetCheckpoint(),
};
const payload = await getVerifiedPullPayload(queryIdHex, TELLOR_LAYER_API, bridgeState);
const { attestData, validators, sigs } = encodeBridgeCallArgsFromPayload(payload);
// Pass attestData, validators, sigs to your contract; it calls dataBridge.verifyOracleData(...)getPullPayload is still available as a raw fetch/assembly helper, but production code should
prefer getVerifiedPullPayload or the mode helpers with bridgeState so checkpoint and recovered
signature power are checked before gas is spent.
Explicit pull modes
Use mode helpers when you want the library to enforce acceptance policy before you submit calldata.
import {
getConsensusFirstPayload,
getConsensusOnlyPayload,
getOptimisticOnlyPayload,
encodeBridgeCallArgsFromPayload,
spotPriceQueryId,
TELLOR_LAYER_API,
} from "using-tellor-pull";
import type { BridgeState } from "using-tellor-pull";
const queryId = spotPriceQueryId("eth", "usd");
const bridgeState: BridgeState = {
timestamp: (await dataBridge.validatorTimestamp()).toString(),
powerThreshold: (await dataBridge.powerThreshold()).toString(),
checkpoint: await dataBridge.lastValidatorSetCheckpoint(),
};
const powerThreshold = Number(await dataBridge.powerThreshold());
const result = await getConsensusFirstPayload(queryId, TELLOR_LAYER_API, {
bridgeState,
filterOptions: { powerThreshold },
});
if (result.status !== "ok" || !result.payload) {
console.log(result.status, result.reason, result.message);
return;
}
const { attestData, validators, sigs } = encodeBridgeCallArgsFromPayload(result.payload);Mode behavior:
getConsensusFirstPayload: matchesSampleTellorUserPullsemantics (consensus first, controlled optimistic fallback).getConsensusOnlyPayload: requires consensus report + freshness checks.getOptimisticOnlyPayload: requires optimistic report + freshness + dispute delay + optimistic power gate.
All mode helpers return a structured PullModeResult with status: "ok" | "unsatisfied" | "error" and reason.
Validator set skip relay
If the DataBridge's on-chain validator set is behind Layer, oracle attestations will fail verification. Use getValsetUpdatePayloads to update it before submitting oracle data:
import { getValsetUpdatePayloads, TELLOR_LAYER_API } from "using-tellor-pull";
import type { BridgeState } from "using-tellor-pull";
// Read the bridge's current state (ethers.js example)
const bridgeState: BridgeState = {
timestamp: (await dataBridge.validatorTimestamp()).toString(),
powerThreshold: (await dataBridge.powerThreshold()).toString(),
checkpoint: await dataBridge.lastValidatorSetCheckpoint(),
};
// Loop until the bridge is caught up
while (true) {
const payloads = await getValsetUpdatePayloads(bridgeState, TELLOR_LAYER_API);
if (payloads.length === 0) break; // bridge is current
for (const p of payloads) {
const tx = await dataBridge.updateValidatorSet(
p.newValsetHash, p.newPowerThreshold, p.newTimestamp,
p.currentValidators, p.sigs
);
await tx.wait();
}
// Re-read bridge state for next iteration
bridgeState.timestamp = (await dataBridge.validatorTimestamp()).toString();
bridgeState.powerThreshold = (await dataBridge.powerThreshold()).toString();
bridgeState.checkpoint = await dataBridge.lastValidatorSetCheckpoint();
}Solidity
Your contract verifies attestations via Tellor's DataBridge. Get the DataBridge interface (structs + verifyOracleData) from Tellor's DataBridge repository. Off-chain production flow: getVerifiedPullPayload or a mode helper with bridgeState → encodeBridgeCallArgsFromPayload → pass attestData, validators, sigs to your contract. See SampleTellorUserPull for a full consumer example.
API (npm package)
| Function | Description |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| getReport(queryIdHex, apiBase, options?) | Current aggregate value + timestamp for queryId. |
| getAttestationBundle(queryIdHex, reportTimestamp, apiBase, options?) | Validator set + signatures for that report. |
| getPullPayload(queryIdHex, apiBase, options?) | Raw report + attestation assembly. Validates returned queryId/timestamp, but does not check destination bridge state. |
| getVerifiedPullPayload(queryIdHex, apiBase, bridgeState, options?) | Production helper: raw payload plus destination DataBridge checkpoint and recovered signature power checks. |
| getConsensusFirstPayload(queryIdHex, apiBase, options?) | Consensus-first with controlled optimistic fallback (Sample-compatible defaults). |
| getConsensusOnlyPayload(queryIdHex, apiBase, options?) | Consensus-only with freshness checks. |
| getOptimisticOnlyPayload(queryIdHex, apiBase, options?) | Optimistic-only with freshness, delay, and power checks. |
| encodeBridgeCallArgs(attestation) | { attestData, validators, sigs } for useOracleData. |
| encodeBridgeCallArgsFromPayload(payload) | Same from PullPayload. |
| spotPriceQueryId(asset, currency) | QueryId for SpotPrice (e.g. "btc", "usd"). |
| spotPriceQueryIdBytes32(asset, currency) | Same as bytes32 (0x-prefixed) for Solidity. |
| generateWithdrawalQueryId(withdrawalId) | TRBBridge withdrawal (Layer → Ethereum). |
| generateDepositQueryId(depositId) | TRBBridge deposit (Ethereum → Layer). |
| queryIdFromData(queryData) | Generic: keccak256(queryData). |
| getValsetUpdatePayloads(bridgeState, apiBase, options?) | Skip relay: build updateValidatorSet payloads to bring the DataBridge up to date. |
Types: BridgeState, ValsetUpdatePayload, PullPayload, PullModeResult, PullModeOptions, VerifiedPullPayloadOptions, AttestationBundle, BridgeCallArgsEncodable, etc.
Constants: TELLOR_LAYER_API (https://mainnet.tellorlayer.com), LAYER_MAINNET_API, LAYER_TESTNET_API.
Fetch options (optional last param for getReport, getPullPayload, getAttestationBundle): { timeoutMs?: number, maxRetries?: number, reportTimestampMs?: number }. Default: 30s timeout, 2 retries with exponential backoff on 5xx/network errors.
When **reportTimestampMs** is set on getPullPayload (or on PullModeOptions passed to mode helpers), the client does not call get_current_aggregate_report; it only loads the attestation bundle for that report time. Use this with a real apiBase after you discover a report timestamp T (e.g. via npm run discover-report) so getOptimisticOnlyPayload can evaluate optimistic-only rules against Layer-backed signatures instead of the latest consensus aggregate.
import {
getOptimisticOnlyPayload,
encodeBridgeCallArgsFromPayload,
spotPriceQueryId,
TELLOR_LAYER_API,
} from "using-tellor-pull";
import type { BridgeState } from "using-tellor-pull";
const queryId = spotPriceQueryId("eth", "usd");
const reportTimestampMs = 1730000000000; // ms — must exist on Layer for this queryId
const bridgeState: BridgeState = {
timestamp: (await dataBridge.validatorTimestamp()).toString(),
powerThreshold: (await dataBridge.powerThreshold()).toString(),
checkpoint: await dataBridge.lastValidatorSetCheckpoint(),
};
const powerThreshold = Number(await dataBridge.powerThreshold());
const result = await getOptimisticOnlyPayload(queryId, TELLOR_LAYER_API, {
bridgeState,
reportTimestampMs,
filterOptions: { powerThreshold },
});
if (result.status !== "ok" || !result.payload) {
console.log(result.status, result.reason, result.message);
return;
}
const { attestData, validators, sigs } = encodeBridgeCallArgsFromPayload(result.payload);Manual Sepolia fork (optional): point an Anvil/Hardhat fork at Sepolia, use Layer TELLOR_API with discover-report / getOptimisticOnlyPayload + reportTimestampMs, then submit encoded args to your contract. Environment-specific; not part of default CI.
Mode helper options: PullModeOptions = fetch options (including optional reportTimestampMs) + optional bridgeState + filterOptions:
nowSec(optional test override)maxDataAgeSec(default 24h)maxAttestationAgeSec(default 10m)optimisticDelaySec(default 12h)powerThreshold(required for optimistic checks)requiredSignaturePower(optional; defaults tobridgeState.powerThresholdwhenbridgeStateis supplied)
When bridgeState is supplied, mode helpers reject status: "ok" if the attestation checkpoint
does not equal the destination DataBridge checkpoint or recovered signature power is below the
configured threshold.
Raw vs production helpers
- Use
getVerifiedPullPayloador mode helpers withbridgeStatefor production calldata. - Use
getPullPayloadfor inspection, tests, or custom flows that perform equivalent bridge-state checks elsewhere. - Use
encodeBridgeCallArgsFromPayloadonly after the payload has passed your desired policy checks.
DataBridge verifies validator signatures and current validator-set quorum. It does not know the consumer's intended feed or business rules, so consuming contracts should also check:
attestData.queryIdis the expected queryId.attestData.report.timestampandattestData.attestationTimestampmeet freshness requirements.attestData.report.aggregatePowermeets the integration's consensus/optimistic policy.- The decoded value is within application-specific bounds before state changes are made.
Testing this repo
This library is validated by unit tests (mocked fetch for deterministic mode logic), plus live API checks. For a full flow including contracts and e2e tests, see SampleTellorUserPull.
npm install
npm test # deterministic unit tests
npm run test:smoke # pull ETH/USD from mainnet API (requires network)
npm run live:modes # live mode helper checks (requires network)
npm run discover-report -- <queryIdHex> <reportTimestampMs> # inspect Layer bundle for T (requires network)Secure Integrations
For secure integrations, refer to the Tellor docs. Also, see example integrations in the SampleTellorUserPull repo.
Repository
- Library (this repo): tellor-io/UsingTellorPull
- Sample: tellor-io/SampleTellorUserPull
Maintainers
@themandalore @brendaloya
How to Contribute
Check out our issues log here on Github or in our Discord.
Contributors
This repository is maintained by the Tellor team - www.tellor.io
Copyright
Tellor Inc. 2026
