Using Data Feeds On-Chain (Solana)

Chainlink Data Feeds are the quickest way to connect your smart contracts to the real-world market prices of assets. This guide demonstrates how to deploy a program to the Solana Devnet cluster and access Data Feeds on-chain using the Chainlink Solana Starter Kit. To learn how to read price feed data using off-chain applications, see the Using Data Feeds Off-Chain guide.

To get the full list of available Chainlink Data Feeds on Solana, see the Solana Feeds page. View the program that owns the Chainlink Data Feeds in the Solana Devnet Explorer, or the Solana Mainnet Explorer.

🚧 Select quality data feeds

Be aware of the quality of the data that you use. Learn more about making responsible data quality decisions.

The program that owns the data feeds on both Devnet and Mainnet is HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny. This is the program ID that you use to retrieve Chainlink Price Data on-chain in your program. The source code for this program is available in the smartcontractkit/chainlink-solana repository on GitHub.

You can add data feeds to an existing project or use the Solana Starter Kit.

Adding Data Feeds On-Chain In An Existing Project

You can read Chainlink Data Feed data on-chain in your existing project using the Chainlink Solana Crate.

Import the Chainlink Solana Crate into your project and use the code sample to make function calls.

  1. Add the Chainlink Solana Crate as an entry in your Cargo.toml file dependencies section, as shown in the starter kit Cargo.toml example.

    [dependencies]
    chainlink_solana = "1.0.0"
  2. Use the following code sample to query price data. Each function call to the Chainlink Solana library takes two parameters:

The code sample has the following components:

  • latest_round_data: Returns the latest round information for the specified price pair including the latest price
  • description: Returns a price pair description such as SOL/USD
  • decimals: Returns the precision of the price, as in how many numbers the price is padded out to
  • Display: A helper function that formats the padded out price data into a human-readable price
/**
 * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

use chainlink_solana as chainlink;

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    pubkey::Pubkey,
};

struct Decimal {
    pub value: i128,
    pub decimals: u32,
}

impl Decimal {
    pub fn new(value: i128, decimals: u32) -> Self {
        Decimal { value, decimals }
    }
}

impl std::fmt::Display for Decimal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut scaled_val = self.value.to_string();
        if scaled_val.len() <= self.decimals as usize {
            scaled_val.insert_str(
                0,
                &vec!["0"; self.decimals as usize - scaled_val.len()].join(""),
            );
            scaled_val.insert_str(0, "0.");
        } else {
            scaled_val.insert(scaled_val.len() - self.decimals as usize, '.');
        }
        f.write_str(&scaled_val)
    }
}

// Declare and export the program's entrypoint
entrypoint!(process_instruction);

// Program entrypoint's implementation
pub fn process_instruction(
    _program_id: &Pubkey, // Ignored
    accounts: &[AccountInfo],
    _instruction_data: &[u8], // Ignored
) -> ProgramResult {
    msg!("Chainlink Price Feed Consumer entrypoint");

    let accounts_iter = &mut accounts.iter();

    // This is the account of the price feed data to read from
    let feed_account = next_account_info(accounts_iter)?;
    // This is the chainlink solana program ID
    let chainlink_program = next_account_info(accounts_iter)?;

    let round = chainlink::latest_round_data(
        chainlink_program.clone(),
        feed_account.clone(),
    )?;

    let description = chainlink::description(
        chainlink_program.clone(),
        feed_account.clone(),
    )?;

    let decimals = chainlink::decimals(
        chainlink_program.clone(),
        feed_account.clone(),
    )?;

    let decimal_print = Decimal::new(round.answer, u32::from(decimals));
    msg!("{} price is {}", description, decimal_print);

    Ok(())
}
/**
 * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

use anchor_lang::prelude::*;
use chainlink_solana as chainlink;

//Program ID required by Anchor. Replace with your unique program ID once you build your project
declare_id!("HPuUpM1bKbaqx7yY2EJ4hGBaA3QsfP5cofHHK99daz85");

#[account]
pub struct Decimal {
    pub value: i128,
    pub decimals: u32,
}

impl Decimal {
    pub fn new(value: i128, decimals: u32) -> Self {
        Decimal { value, decimals }
    }
}

impl std::fmt::Display for Decimal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut scaled_val = self.value.to_string();
        if scaled_val.len() <= self.decimals as usize {
            scaled_val.insert_str(
                0,
                &vec!["0"; self.decimals as usize - scaled_val.len()].join(""),
            );
            scaled_val.insert_str(0, "0.");
        } else {
            scaled_val.insert(scaled_val.len() - self.decimals as usize, '.');
        }
        f.write_str(&scaled_val)
    }
}

#[program]
pub mod chainlink_solana_demo {
    use super::*;
        pub fn execute(ctx: Context<Execute>) -> Result<()>  {
        let round = chainlink::latest_round_data(
            ctx.accounts.chainlink_program.to_account_info(),
            ctx.accounts.chainlink_feed.to_account_info(),
        )?;

        let description = chainlink::description(
            ctx.accounts.chainlink_program.to_account_info(),
            ctx.accounts.chainlink_feed.to_account_info(),
        )?;

        let decimals = chainlink::decimals(
            ctx.accounts.chainlink_program.to_account_info(),
            ctx.accounts.chainlink_feed.to_account_info(),
        )?;
        // write the latest price to the program output
        let decimal_print = Decimal::new(round.answer, u32::from(decimals));
        msg!("{} price is {}", description, decimal_print);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Execute<'info> {
    /// CHECK: We're reading data from this chainlink feed account
    pub chainlink_feed: AccountInfo<'info>,
    /// CHECK: This is the Chainlink program library
    pub chainlink_program: AccountInfo<'info>
}

Program Transaction logs:

> Program logged: "Chainlink Price Feed Consumer entrypoint"
> Program logged: "SOL / USD price is 83.99000000"
> Program consumed: 95953 of 1400000 compute units
> Program return: HNYSbr77Jc9LhHeb9tx53SrWbWfNBnQzQrM4b3BB3PCR CA==

To learn more about Solana and Anchor, see the Solana Documentation and the Anchor Documentation.

Using the Solana Starter Kit

This guide demonstrates the following tasks:

This example shows a full end to end example of using Chainlink Price Feeds on Solana. It includes an on-chain program written in rust, as well as an off-chain client written in JavaScript. The client passes in an account to the program, the program then looks up the latest price of the specified price feed account, and then stores the result in the passed in account. The off-chain client then reads the value stored in the account.

Install the required tools

Before you begin, set up your environment for development on Solana:

  1. Install Git if it is not already configured on your system.

  2. Install Node.js 14 or higher. Run node --version to verify which version you have installed:

    node --version
  3. Install Yarn to simplify package management and run code samples.

  4. Install a C compiler such as the one included in GCC. Some of the dependencies require a C compiler.

  5. Install Rust:

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh &&
    source $HOME/.cargo/env
  6. Install the latest Mainnet version of the Solana CLI and export the path to the CLI:

    sh -c "$(curl -sSfL https://release.solana.com/v1.9.28/install)" &&
    export PATH="~/.local/share/solana/install/active_release/bin:$PATH"

    Run solana --version to make sure the Solana CLI is installed correctly.

    solana --version
  7. Install Anchor. On some operating systems, you might need to build and install Anchor locally. See the Anchor documentation for instructions.

After you install the required tools, build and deploy the example program from the solana-starter-kit repository.

Deploy the example program

This example includes a contract written in Rust. Deploy the contract to the Solana Devnet cluster.

  1. In a terminal, clone the solana-starter-kit repository and change to the solana-starter-kit directory:

    git clone https://github.com/smartcontractkit/solana-starter-kit &&
    cd ./solana-starter-kit

    You can see the complete code for the example on GitHub.

  2. In the ./solana-starter-kit directory, install Node.js dependencies defined in the package.json file:

    yarn install
  3. Create a temporary Solana wallet to use for this example. Use a temporary wallet to isolate development from your other wallets and prevent you from unintentionally using lamports on the Solana Mainnet. Alternatively, if you have an existing wallet that you want to use, locate the path to your keypair file and use it as the keypair for the rest of this guide.

    solana-keygen new --outfile ./id.json

    When you build your production applications and deploy Solana programs to the Mainnet cluster, always follow the security best practices in the Solana Wallet Guide for managing your wallets and keypairs.

  4. Fund your Solana wallet. On Devnet, use solana airdrop to add tokens to your account. The contract requires at least 4 SOL to deploy and the faucet limits each request to 2 SOL, so you must make two requests to get a total of 4 SOL on your wallet:

    solana airdrop 2 --keypair ./id.json --url devnet &&
    solana airdrop 2 --keypair ./id.json --url devnet
    • If the command line faucet does not work, run solana address on the temporary wallet to print the public key value for the wallet and request tokens from SolFaucet:

      solana address -k ./id.json
  5. Run anchor build to build the example program. If you receive the no such subcommand: 'build-bpf' error, restart your terminal session and run anchor build again:

    anchor build
  6. The build process generates the keypair for your program’s account. Before you deploy your program, you must add this public key to the lib.rs file:

    1. Get the keypair from the ./target/deploy/chainlink_solana_demo-keypair.json file that Anchor generated:

      solana address -k ./target/deploy/chainlink_solana_demo-keypair.json
    2. Edit the ./programs/chainlink_solana_demo/src/lib.rs file and replace the keypair in the declare_id!() definition:

      vi ./programs/chainlink_solana_demo/src/lib.rs
      declare_id!("JC16qi56dgcLoaTVe4BvnCoDL6FhH5NtahA7jmWZFdqm");
  7. With the new program ID added, run anchor build again. This recreates the necessary program files with the correct program ID:

    anchor build
  8. Run anchor deploy to deploy the program to the Solana Devnet. Remember to specify the keypair file for your wallet and override the default. This wallet is the account owner (authority) for the program:

    anchor deploy --provider.wallet ./id.json --provider.cluster devnet
  9. To confirm that the program deployed correctly, run solana program show --programs to get a list of deployed programs that your wallet owns. For this example, check the list of deployed programs for the id.json wallet on the Solana Devnet:

    solana program show --programs --keypair ./id.json --url devnet

    The command prints the program ID, slot number, the wallet address that owns the program, and the program balance:

    Program Id                                   | Slot      | Authority                                    | Balance
    GRt21UnJFHZvcaWLbcUrXaTCFMREewDrm1DweDYBak3Z | 110801571 | FsQPnANKDhqpoayxCL3oDHFCBmrhP34NrfbDR34qbQUt | 3.07874904 SOL

    To see additional details of your deployed program, copy the program ID and look it up in the Solana Devnet Explorer.

Now that the program is on-chain, you can call it using the Anchor Web3 module.

Call the deployed program

Use your deployed program to retrieve price data from a Chainlink data feed on Solana Devnet. For this example, call your deployed program using the Anchor Web3 module and the client.js example code.

  1. Set the Anchor environment variables. Anchor uses these to determine which wallet to use and Solana cluster to use.

    export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com &&
    export ANCHOR_WALLET=./id.json
  2. Run the client.js example and pass the program address in using the --program flag:

    node client.js --program $(solana address -k ./target/deploy/chainlink_solana_demo-keypair.json)

    If the script executes correctly, you will see output with the current price of SOL / USD.

    â‹®
    Price Is: 96.79778375
    Success
    â‹®
  3. Each request costs an amount of SOL that is subtracted from the id.json wallet. Run solana balance to check the remaining balance for your temporary wallet on Devnet.

    solana balance --keypair ./id.json --url devnet
  4. To get prices for a different asset pair, run client.js again and add the --feed flag with one of the available Chainlink data feeds. For example, to get the price of BTC / USD on Devnet, use the following command:

    node client.js \
    --program $(solana address -k ./target/deploy/chainlink_solana_demo-keypair.json) \
    --feed CzZQBrJCLqjXRfMjRN3fhbxur2QYHUzkpaRwkWsiPqbz
    Price Is: 12.4215826
    Success

The program that owns the data feeds is HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny, which you can see defined for const CHAINLINK_PROGRAM_ID in the client.js file.

Clean up

After you are done with your deployed contract and no longer need it, it is nice to close the program and withdraw the Devnet SOL tokens for future use. In a production environment, you will want to withdraw unused SOL tokens from any Solana program that you no longer plan to use, so it is good to practice the process when you are done with programs on Devnet.

  1. Run solana program show to see the list of deployed programs that your wallet owns and the balances for each of those programs:

    solana program show --programs --keypair ./id.json --url devnet
    Program Id                                   | Slot      | Authority                                    | Balance
    GRt21UnJFHZvcaWLbcUrXaTCFMREewDrm1DweDYBak3Z | 110801571 | FsQPnANKDhqpoayxCL3oDHFCBmrhP34NrfbDR34qbQUt | 3.07874904 SOL
  2. Run solana program close and specify the program that you want to close:

    solana program close [YOUR_PROGRAM_ID] --keypair ./id.json --url devnet

    The program closes and the remaining SOL is transferred to your temporary wallet.

  3. If you have deployments that failed, they might still be in the buffer holding SOL tokens. Run solana program show again with the --buffers flag:

    solana program show --buffers --keypair ./id.json --url devnet

    If you have open buffers, they will appear in the list.

    Buffer Address                               | Authority                                    | Balance
    CSc9hnBqYJoYtBgsryJAmrjAE6vZ918qaFhL6N6BdEmB | FsQPnANKDhqpoayxCL3oDHFCBmrhP34NrfbDR34qbQUt | 1.28936088 SOL
  4. If you have any buffers that you do not plan to finish deploying, run the same solana program close command to close them and retrieve the unused SOL tokens:

    solana program close [YOUR_PROGRAM_ID] --keypair ./id.json --url devnet
  5. Check the balance on your temporary wallet.

    solana balance --keypair ./id.json --url devnet
  6. If you are done using this wallet for examples and testing, you can use solana transfer to send the remaining SOL tokens to your default wallet or another Solana wallet that you use. For example, if your default wallet keypair is at ~/.config/solana/id.json, you can send ALL of the temporary wallet’s balance with the following command:

    solana transfer ~/.config/solana/id.json ALL --keypair ./id.json --url devnet

    Alternatively, you can send the remaining balance to a web wallet. Specify the public key for your wallet instead of the path the default wallet keypair. Now you can use those Devnet funds for other examples and development.

To learn more about Solana and Anchor, see the Solana Documentation and the Anchor Documentation.

What's next

Stay updated on the latest Chainlink news