Pull Oracle
A pull implementation for Tellor users
Using Tellor Pull on EVM: library, flow, and sample borrow app
Tellor Pull is for builders who need oracle data inside the same transaction as the user action—borrow, liquidate, resolve a market, and so on. Instead of relying on a stored on-chain value that someone else pushed earlier, you pull the latest attested report and proofs from Tellor Layer, attach them to the call, and your contract verifies via Tellor’s DataBridge and uses the value in one shot. That gives you fresher, user-driven reads without a separate “update oracle” step, and it maps cleanly to lending, stablecoins, and prediction markets where price or outcome must be atomic with the protocol logic.
This article combines the practical material from two places: the using-tellor-pull npm package from the UsingTellorPull repository, and the SampleTellorUserPull repo & reference app (React + Solidity borrow on Sepolia). Read it top-down for context, then use the sections as checklists for your own integration.
Package contents
API — Fetch and encoding helpers, plus explicit pull modes (consensus-first, consensus-only, optimistic-only). Build output lives in
dist/when you consume the published package.
End-to-end flow
Your app uses the library to fetch attestation, validator set, and signatures from Tellor Layer and encode them for the EVM.
If the DataBridge’s validator set is stale, you use
getValsetUpdatePayloadsto buildupdateValidatorSettransactions (skip relay) before oracle verification will succeed.You pass
attestData,validators, andsigsinto your contract (for example as ABI-encoded calldata).Your contract calls
dataBridge.verifyOracleData(...)and uses the value in the same transaction.
Assumption: You deploy on an EVM chain where Tellor’s DataBridge is deployed.
Install
npm install usingtellorpull ethersQuick start (fetch and encode)
Production integrations should bind the pull to the destination DataBridge before submitting calldata. Read validatorTimestamp(), powerThreshold(), and lastValidatorSetCheckpoint(), then use getVerifiedPullPayload (or a mode helper with bridgeState). getPullPayload remains a raw fetch helper for tests and inspection—it does not check destination bridge checkpoint or recovered signature power by itself.
Explicit pull modes
Use mode helpers when you want the library to enforce acceptance policy before you submit calldata (recommended for production UX).
Mode behavior:
getConsensusFirstPayload— Matches SampleTellorUserPull semantics (consensus first, controlled optimistic fallback).getConsensusOnlyPayload— Requires consensus report plus freshness checks.getOptimisticOnlyPayload— Requires optimistic report plus freshness, dispute delay, and optimistic power gate.
All mode helpers return a structured PullModeResult with status: "ok" | "unsatisfied" | "error" and reason (for example checkpoint_mismatch or insufficient_signature_power when bridgeState is supplied).
Validator set skip relay
If the DataBridge’s on-chain validator set is behind Tellor Layer, attestations will fail verification until the bridge is updated. Use getValsetUpdatePayloads and submit updateValidatorSet in a loop until caught up:
Solidity
Your contract verifies attestations via Tellor’s DataBridge. Use the DataBridge interface (structs and verifyOracleData) from Tellor’s DataBridge repository. Off-chain production flow: getVerifiedPullPayload or a mode helper with bridgeState → encodeBridgeCallArgsFromPayload → pass attestData, validators, sigs. The sample repo below shows a full consumer contract.
API (npm package)
getReport(queryIdHex, apiBase, options?)
Current aggregate value and timestamp for the query ID.
getAttestationBundle(queryIdHex, reportTimestamp, apiBase, options?)
Validator set and signatures for that report.
getPullPayload(queryIdHex, apiBase, options?)
Raw report plus attestation assembly. With reportTimestampMs, skips current aggregate and loads that report time from Layer. Does not check destination DataBridge 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)
Returns { attestData, validators, sigs } for the bridge.
encodeBridgeCallArgsFromPayload(payload)
Same encoding from a pull payload.
spotPriceQueryId(asset, currency)
Query ID for SpotPrice (for example "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 current.
Types include BridgeState, ValsetUpdatePayload, PullPayload, PullModeResult, PullModeOptions, VerifiedPullPayloadOptions, AttestationBundle, BridgeCallArgsEncodable, and others.
Constants include TELLOR_LAYER_API (for example https://mainnet.tellorlayer.com), LAYER_MAINNET_API, and LAYER_TESTNET_API.
Fetch options and reportTimestampMs
reportTimestampMsOptional last parameter for getReport, getPullPayload, getAttestationBundle: { timeoutMs?: number, maxRetries?: number, reportTimestampMs?: number }. Defaults are typically a 30s timeout and retries on 5xx or network errors.
When reportTimestampMs is set on getPullPayload (or in PullModeOptions for mode helpers), the client does not call the current aggregate endpoint; it loads the attestation bundle for that report time. Use this with a real apiBase after you discover a report timestamp (for example via npm run discover-report) so optimistic-only rules can run against Layer-backed signatures.
Mode helper options (PullModeOptions)
PullModeOptions)Fetch options (including optional reportTimestampMs), optional bridgeState, plus 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 match the destination DataBridge checkpoint or recovered signature power is below the configured threshold.
Optimistic-only example
Optional Sepolia fork: Point Anvil or Hardhat at Sepolia, use Layer API with discover-report / getOptimisticOnlyPayload and reportTimestampMs, then submit encoded args to your contract. Environment-specific; not part of default CI.
Sample application: Borrow TRB (Tellor Pull)
The SampleTellorUserPull repo demonstrates one use case: borrow TRB against Sepolia ETH collateral, then repay TRB to reclaim collateral. The frontend pulls ETH/USD and TRB/USD using getConsensusFirstPayload(...), updates the DataBridge validator set when needed (skip relay, multiple hops capped in the UI), and passes attestations into borrow. The contract verifies through Tellor’s DataBridge and applies an 80% LTV.
How it looks
Connect wallet (Sepolia).
Enter collateral (ETH) and borrow amount (TRB).
Borrow — Check bridge validator set → pull oracle data → encode for EVM →
borrow(amount, ethAttest, trbAttest, validators, ethSigs, trbSigs).Repay — Approve TRB spend →
repay(amount)→ collateral returned proportionally (partial or full repay).
Sample flow: Borrow
User clicks Borrow in the frontend.
Frontend reads the DataBridge’s
validatorTimestamp(),powerThreshold(), andlastValidatorSetCheckpoint(), then loops (up to 10 hops): fetch skip-relay payloads viagetValsetUpdatePayloadsand submitdataBridge.updateValidatorSet(...)until caught up.After sync, frontend builds
bridgeStatefrom the bridge and requests ETH/USD and TRB/USD viagetConsensusFirstPayload(queryId, TELLOR_LAYER_API, { bridgeState, filterOptions: { powerThreshold } }), thenencodeBridgeCallArgsFromPayload(payload)for each feed (with retries if signatures cannot be recovered).Frontend sends:
contract.borrow(amountTrbWei, ethAttestData, trbAttestData, validators, ethSigs, trbSigs, { value: collateralWei }).Contract verifies both attestations via
dataBridge.verifyOracleData(...), decodes prices, checks LTV, updates position, and transfers TRB.
Sample flow: Repay
User enters repay amount and confirms Repay.
Frontend approves the borrow contract to spend TRB if allowance is insufficient.
Frontend sends
contract.repay(amountTrbWei).Contract returns ETH collateral proportionally.
Validator set skip relay in the sample
The frontend is responsible for keeping the DataBridge validator set current. If the bridge lags multiple rotations, skip relay:
Read the bridge’s trusted state (timestamp, power threshold, checkpoint).
Try to jump directly to the latest validator set.
If not enough signing power from the bridge’s current trusted set, walk backward to the furthest reachable target.
Repeat until caught up (the sample caps hops, for example 10).
The sample imports pull and relay helpers through frontend/src/tellorPull.js, which re-exports using-tellor-pull and sets TELLOR_LAYER_API (Vite dev proxy vs production URL). Implementation lives in UsingTellorPull (src/valsetRelay.ts, src/attestation.ts).
Sample repo layout
Run the sample
1. Contracts (Sepolia or local)
Deploy to Sepolia (set hardhat/.env: SEPOLIA_PRIVATE_KEY, SEPOLIA_RPC_URL, ETHERSCAN_API_KEY, optional TRB_ADDRESS):
The script deploys, waits for confirmations, and can auto-verify on Etherscan. Fund the borrow contract with TRB after deploy so it can lend (approve + fundTrb from the deployer wallet).
2. Frontend
Set frontend/.env:
Open the dev URL, connect on Sepolia, and exercise borrow and repay. Local dev often uses a Vite proxy so browser calls avoid CORS; production builds use VITE_TELLOR_LAYER_API directly.
Testing the library (developers)
Hardhat tests in the sample include optimistic-shaped oracle data so on-chain filter rules are covered without Live Layer or a fork in the default test run.
Secure integrations
For production security guidance, use the Tellor documentation and review how your protocol uses decoded values (liquidations, LTV, resolution rules). The DataBridge’s VALIDATOR_SET_HASH_DOMAIN_SEPARATOR is chain-specific (keccak256(abi.encode("checkpoint", CHAIN_ID))). Skip relay behavior is implemented in using-tellor-pull (src/valsetRelay.ts in UsingTellorPull); relay builders validate the bridge’s on-chain checkpoint and power threshold against Tellor Layer before proposing hops.
Repositories and links
Library: tellor-io/UsingTellorPull (npm using-tellor-pull)
Sample: tellor-io/SampleTellorUserPull
Related structure: sample-tellor-layer-pull
Maintainers and community: see the GitHub repos for issues; Tellor Discord for contributions. Tellor Inc.
Last updated
Was this helpful?

