Documentation
How to Use offchain-utils

Tutorial: How to Use offchain-utils

This tutorial explains how to integrate offchain-utils (opens in a new tab) into a Substrate-based blockchain to securely fetch external API data.


πŸ“Œ Clone and Set Up the Substrate Parachain Template

  1. Clone the Substrate Node Template (opens in a new tab) repository:
  • πŸ¦€ The template is using the Rust language.

  • πŸ‘‰ Check the Rust installation instructions (opens in a new tab) for your system.

  • πŸ› οΈ Depending on your operating system and Rust version, there might be additional packages required to compile this template - please take note of the Rust compiler output.

Build

πŸ”¨ Use the following command to build the node without launching it:

cargo build --release

Run the node locally:

Current polkadot-omni-node version not working with Offchain worker template. We will wait for the new release.

For now we can clone the Polkadot SDK monorepo (opens in a new tab) and build that project to get the latest polkadot-omni-node.

1. Clone the repo Polkadot SDK monorepo (opens in a new tab)

2. Assuming you've built the binary using:

cargo build --release

you'll find the executable in the target/release directory.

3. Make It Executable

Though Cargo usually sets the execute permission, you can ensure it’s executable by running:

chmod +x target/release/polkadot-omni-node

4. Move the Binary to a Global PATH Directory

For example, copy it to /usr/local/bin (you might need sudo privileges):

sudo cp target/release/polkadot-omni-node /usr/local/bin/

5. Verify Global Installation

After copying, verify that it’s accessible globally by running:

polkadot-omni-node --help

If everything is set up correctly, you should see the help output for the polkadot-omni-node command.

Getting Started

πŸ“Œ Step 1: Install offchain-utils

Add the crate to your Substrate runtime by running:

cargo add offchain-utils

Alternatively, manually add it to your Cargo.toml:

[dependencies]
offchain-utils = "0.1.0"  # Ensure you use the latest published version

πŸ“Œ Step 2: Implement API Key Retrieval in the Pallet

Modify your pallet to use offchain-utils::OffchainApiKey for fetching the API key.

πŸ“ Define API Key Fetcher

use offchain_utils::offchain_api_key::OffchainApiKey;
 
pub struct CustomApiKeyFetcher;
impl OffchainApiKey for CustomApiKeyFetcher {}

πŸ” Retrieve API Key Securely

impl<T: Config> Pallet<T> {
    fn get_api_key() -> Result<String, &'static str> {
        match CustomApiKeyFetcher::fetch_api_key_for_request("key_name") {
            Ok(key) => Ok(key),
            Err(err) => {
                log::error!("Failed to fetch API key: {:?}", err);
                Err("API key not found in keystore")
            }
        }
    }
}

βœ… This method retrieves the API key securely from the keystore, avoiding on-chain exposure.

πŸ“Œ Step 3: Implement Off-Chain Worker

Modify your pallet to include the offchain_worker hook:

 
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
    fn offchain_worker(block_number: BlockNumberFor<T>) {
        log::trace!(target: "logger", "Ping from offchain workers!");
 
        // Retrieve API Key securely
        let api_key = match Self::get_api_key() {
            Ok(key) => key,
            Err(e) => {
                log::error!("Offchain worker error: {:?}", e);
                return;
            }
        };
 
        log::info!("API key retrieved: {}", api_key);
 
        // Fetch external data using the API key
        let request = HttpRequest::new("https://api.example.com/data")
            .add_header("Authorization", &format!("Bearer {}", api_key));
 
        match DefaultOffchainFetcher::fetch_string(request) {
            Ok(response) => log::info!("Received API response: {}", response),
            Err(_) => log::error!("HTTP request failed"),
        }
 
        // Retrieve parent block hash (example usage of on-chain data)
        let parent_hash = <system::Pallet<T>>::block_hash(block_number - 1u32.into());
        log::debug!(
            "Current block: {:?} (parent hash: {:?})",
            block_number,
            parent_hash
        );
    }
}
 

βœ… This worker securely retrieves an API key and fetches external data.

πŸ“Œ Step 4: Submit Data On-Chain (Optional)

If the API response needs to be stored on-chain, use signed transactions:

let signer = Signer::<T, T::AuthorityId>::all_accounts();
let results = signer.send_signed_transaction(|_account| {
    Call::store_api_response { response: api_response.clone() }
});

🎯 Next Steps

Now that you’ve integrated secure API key storage and off-chain worker execution, explore the architecture to understand how it works.

Additionally, consider using Subway (opens in a new tab) as a secure RPC gateway to optimize and protect your Substrate JSON-RPC calls.

β†’ Check out our example Template (opens in a new tab) of offchain-utils

β†’ Learn about the Architecture of offchain-utils

β†’ Secure Your RPC Calls with Subway (opens in a new tab)