A NEAR smart contract that enables on-chain code to request and receive off-chain HTTP data using NEAR's yield/resume mechanism. When a contract calls the fetcher, the transaction pauses (yields) while an off-chain relayer executes the HTTP request, then resumes execution with the response data.
You can interact with these deployed contracts right now:
- Fetcher:
http-fetcher.testnet - Relayer:
http-relayer.testnet - Weather Example:
weather-example.testnet
Anyone can call these contracts to test the HTTP fetching functionality.
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Weather │ ──────▶ │ HTTP Fetch │ │ Relayer │
│ Contract │ fetch │ Contract │ │ (off-chain)│
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
│ │ (yields promise) │
│ │ │
│ │ ◀───── polls ──────── │
│ │ requests │
│ │ │
│ │ │ ──▶ HTTP GET
│ │ │ (OpenWeather API)
│ │ │
│ │ ◀───── respond ────── │ ◀── Response
│ │ (resume promise) │
│ │ │
│ ◀──── callback ───────┤ │
│ (parsed data) │ │
│ │ │
- A consumer contract (like the weather example) calls
fetch(url, context) - The fetcher contract creates a yielded promise and logs a fetch request
- An off-chain relayer polls for pending requests via
list_requests() - The relayer performs the HTTP GET request
- For large responses, the relayer calls
store_response_chunk()multiple times - The relayer calls
respond()to resume the yielded promise - The fetcher returns a
FetchResultto the caller's callback
Try fetching weather data using the deployed contracts:
# Request weather for a city
near call weather-example.testnet request_weather '{"city": "London"}' \
--accountId your-account.testnet \
--gas 300000000000000
# Wait a few seconds for the relayer to process the request
# Retrieve cached weather data
near view weather-example.testnet get_cached_weather '{"city": "London"}'You can also directly use the HTTP fetcher:
# Make a custom HTTP request
near call http-fetcher.testnet fetch \
'{"url": "https://api.example.com/data", "context": null}' \
--accountId your-account.testnet \
--gas 300000000000000http-fetch/
├── src/lib.rs # Core HTTP fetcher contract
├── examples/
│ └── weather/ # Example: weather data fetching contract
├── relayer/ # Off-chain HTTP relayer (Rust CLI + library)
├── tests/
│ ├── fetcher.rs # Unit tests for fetcher contract
│ └── weather.rs # Integration tests with weather example
├── Cargo.toml # Workspace configuration
└── README.md
- Rust: Install via rustup (this repo uses Rust 1.86)
- cargo-near: For building NEAR contracts
cargo install cargo-near
- NEAR CLI: For deploying and interacting with contracts
npm install -g near-cli-rs@latest
Build all contracts to WASM:
cargo near buildThe output appears in target/near/:
http_fetch.wasm- The main fetcher contractweather.wasm- The weather example contract
Build specific contracts:
cargo near build -p weatherThe test suite uses near-workspaces to spin up local sandbox chains:
cargo test -- --nocaptureKey tests:
fetcher_yield_resume_flow- Tests basic yield/resume mechanismweather_contract_flow- Full integration test with the weather example
Note: The weather test makes real HTTP calls to OpenWeather API, so network access is required.
# Create a new account for the fetcher
near create-account fetcher.your-account.testnet
# Deploy the contract
near deploy fetcher.your-account.testnet \
--wasmFile target/near/http_fetch.wasm
# Initialize with your relayer account
near call fetcher.your-account.testnet new \
'{"trusted_relayer": "relayer.your-account.testnet"}' \
--accountId your-account.testnet# Create account
near create-account weather.your-account.testnet \
--masterAccount your-account.testnet
# Deploy
near deploy weather.your-account.testnet \
--wasmFile target/near/weather.wasm
# Initialize with fetcher account
near call weather.your-account.testnet new \
'{"fetcher_account": "fetcher.your-account.testnet"}' \
--accountId your-account.testnetThe relayer is a Rust CLI that monitors fetch requests and fulfills them.
Create a .env file in the relayer/ directory:
RPC_URL=https://rpc.testnet.near.org
CONTRACT_ID=http-fetcher.testnet
RELAYER_ACCOUNT_ID=http-relayer.testnet
RELAYER_PRIVATE_KEY=ed25519:your_private_key_here
POLL_INTERVAL_SECS=5Or export as environment variables.
cd relayer
cargo runThe relayer will:
- Poll the fetcher contract every
POLL_INTERVAL_SECSseconds - Execute HTTP GET requests for pending items
- Upload large responses in chunks via
store_response_chunk() - Resume the yielded promises via
respond()
You can also use the relayer as a library in your own Rust projects:
use relayer::{RelayerConfig, run_relayer};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = RelayerConfig {
rpc_url: "https://rpc.testnet.near.org".to_string(),
contract_id: "http-fetcher.testnet".parse()?,
relayer_account_id: "relayer.testnet".parse()?,
relayer_private_key: "ed25519:...".to_string(),
poll_interval_secs: 5,
};
run_relayer(config).await
}Here's a minimal example of a contract that uses the HTTP fetcher:
use near_sdk::*;
#[ext_contract(http_fetcher)]
trait HttpFetcher {
fn fetch(&mut self, url: String, context: Option<Vec<u8>>) -> FetchResult;
}
#[near(contract_state)]
pub struct MyContract {
fetcher_account: AccountId,
}
#[near]
impl MyContract {
pub fn fetch_data(&mut self, url: String) -> Promise {
http_fetcher::ext(self.fetcher_account.clone())
.with_static_gas(Gas::from_tgas(40))
.fetch(url, None)
.then(
Self::ext(env::current_account_id())
.with_static_gas(Gas::from_tgas(20))
.on_fetch_result()
)
}
#[private]
pub fn on_fetch_result(
&mut self,
#[callback_result] result: Result<FetchResult, PromiseError>,
) {
match result {
Ok(fetch_result) => {
if let Some(body) = fetch_result.body {
// Process the response body
env::log_str(&format!("Received {} bytes", body.len()));
}
}
Err(_) => env::log_str("Fetch failed"),
}
}
}Initialize the contract with the relayer account that's authorized to fulfill requests.
Request HTTP data from a URL. The context parameter is passed through to your callback for request tracking. This function yields and returns a FetchResult.
Returns all pending fetch requests (used by relayers).
Resume a yielded promise with response data. Only callable by the trusted relayer.
Store response data in chunks (for large payloads). Only callable by the trusted relayer.
pub struct FetchResult {
pub request_id: u64,
pub url: String,
pub status: FetchStatus, // Completed or TimedOut
pub body: Option<Vec<u8>>,
pub context: Option<Vec<u8>>,
pub caller: AccountId,
}- Trusted Relayer: Only the configured relayer account can fulfill requests. Choose this account carefully.
- Gas Limits: Ensure sufficient gas for the full yield/resume cycle (typically 40+ TGas).
- Response Size: Large responses use chunked storage to avoid receipt size limits.
- Validation: Always validate response data in your callback before using it.
- Add more examples: Create consumer contracts under
examples/for different use cases (price feeds, API integrations, etc.) - Multi-language relayers: Implement relayers in other languages using NEAR APIs
- Enhanced relayer logic: Add authentication, rate limiting, caching, or webhook support
- POST/PUT support: Extend the fetcher to support HTTP methods beyond GET
- NEAR Rust SDK Documentation
- Yield/Resume Documentation
- near-workspaces Testing Framework
- cargo-near
- NEAR CLI Documentation
MIT