diff --git a/src/content/data-streams/tutorials/streams-direct/solana-offchain-report-verification.mdx b/src/content/data-streams/tutorials/streams-direct/solana-offchain-report-verification.mdx
index a1011df53a0..1b2b6b3bf24 100644
--- a/src/content/data-streams/tutorials/streams-direct/solana-offchain-report-verification.mdx
+++ b/src/content/data-streams/tutorials/streams-direct/solana-offchain-report-verification.mdx
@@ -83,7 +83,11 @@ To complete this guide, you'll need:
- **Allowlisted Account**: Your account must be allowlisted in the Data Streams Access Controller.
-> **Note**: While this guide uses the Anchor framework for project structure, you can integrate the verification using any Rust-based Solana project setup. The verifier SDK and client libraries are written in Rust, but you can integrate them into your preferred Rust project structure.
+
### Implementation guide
@@ -128,14 +132,17 @@ cpi = ["no-entrypoint"]
default = []
[dependencies]
-data-streams-report = { git = "https://github.com/smartcontractkit/data-streams-sdk.git" }
-sdk-off-chain = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "sdk-off-chain"}
+chainlink-data-streams-report = "1.0.0"
+sdk-off-chain = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "sdk-off-chain" }
solana-program = "1.18.26"
solana-sdk = "1.18.26"
solana-client = "1.18.26"
+solana-transaction-status = "1.18.26"
hex = "0.4.3"
borsh = "0.10.3"
+base64 = "0.22.1"
+snap = "1.1.1"
```
#### 3. Implement the verification library
@@ -143,16 +150,16 @@ borsh = "0.10.3"
Create `programs/example_verify/src/lib.rs` with the verification function:
```rust
-use data_streams_report::report::v3::ReportDataV3;
-use sdk_off_chain::VerificationClient;
+use chainlink_data_streams_report::report::v3::ReportDataV3;
+use sdk_off_chain::{ VerificationClient, VerificationResult };
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
pubkey::Pubkey,
- signature::read_keypair_file,
+ signature::{ read_keypair_file, Keypair },
signer::Signer,
};
-use std::{ path::PathBuf, str::FromStr };
+use std::{ path::PathBuf, str::FromStr, fs::File, io::Read };
pub fn default_keypair_path() -> String {
let mut path = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string()));
@@ -160,19 +167,37 @@ pub fn default_keypair_path() -> String {
path.to_str().unwrap().to_string()
}
+pub fn load_keypair(keypair_path: Option<&str>) -> Result> {
+ let path = keypair_path.map(|p| p.to_string()).unwrap_or_else(default_keypair_path);
+
+ // Try reading as a file-based keypair first
+ match read_keypair_file(&path) {
+ Ok(keypair) => Ok(keypair),
+ Err(_) => {
+ // If file read fails, try as a JSON string
+ let mut file = File::open(&path)?;
+ let mut contents = String::new();
+ file.read_to_string(&mut contents)?;
+
+ let keypair = Keypair::from_base58_string(&contents);
+ Ok(keypair)
+ }
+ }
+}
+
pub fn verify_report(
signed_report: &[u8],
program_id: &str,
- access_controller: &str
+ access_controller: &str,
+ keypair_path: Option<&str>,
+ rpc_url: Option<&str>
) -> Result> {
// Initialize RPC client with confirmed commitment level
- let rpc_client = RpcClient::new_with_commitment(
- "https://api.devnet.solana.com",
- CommitmentConfig::confirmed()
- );
+ let rpc_url = rpc_url.unwrap_or("https://api.devnet.solana.com");
+ let rpc_client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
// Load the keypair that will pay for and sign verification transactions
- let payer = read_keypair_file(default_keypair_path())?;
+ let payer = load_keypair(keypair_path)?;
println!("Using keypair: {}", payer.pubkey());
// Convert to Pubkey
@@ -191,19 +216,40 @@ pub fn verify_report(
// Verify the report
println!("Verifying report of {} bytes...", signed_report.len());
- let result = client.verify(signed_report.to_vec()).map_err(|e| {
- println!("Verification error: {:?}", e);
- e
- })?;
+ let result = verify_and_handle_errors(&client, signed_report)?;
// Decode the returned data into a ReportDataV3 struct
- let return_data = result.return_data.ok_or("No return data")?;
+ let return_data = result.return_data.ok_or_else(|| {
+ Box::::from("No return data from verification")
+ })?;
+
let report = ReportDataV3::decode(&return_data)?;
Ok(report)
}
+
+// Helper function to handle the verification process and errors
+fn verify_and_handle_errors(
+ client: &VerificationClient,
+ signed_report: &[u8]
+) -> Result> {
+ match client.verify(signed_report.to_vec()) {
+ Ok(result) => Ok(result),
+ Err(e) => {
+ println!("Verification error: {:?}", e);
+
+ // Convert ClientError to a boxed Error
+ Err(Box::new(e))
+ }
+ }
+}
```
-> This example uses the [V3 schema](/data-streams/reference/report-schema) for [crypto streams](/data-streams/crypto-streams) to decode the report. If you verify reports for [RWA streams](/data-streams/rwa-streams), import and use the [V4 schema](/data-streams/reference/report-schema-v4) from the [report crate](https://github.com/smartcontractkit/data-streams-sdk/tree/main/rust/crates/report) instead.
+
#### 4. Create the command-line interface
@@ -218,11 +264,16 @@ use solana_sdk::pubkey::Pubkey;
fn main() {
let args: Vec = env::args().collect();
- if args.len() != 4 {
- eprintln!(
- "Usage: {} ",
- args[0]
- );
+
+ // Check if help flag is passed
+ if args.len() > 1 && (args[1] == "-h" || args[1] == "--help") {
+ print_usage(&args[0]);
+ return;
+ }
+
+ if args.len() < 4 {
+ eprintln!("Error: Not enough arguments provided.");
+ print_usage(&args[0]);
std::process::exit(1);
}
@@ -230,6 +281,10 @@ fn main() {
let access_controller_str = &args[2];
let hex_report = &args[3];
+ // Optional arguments
+ let keypair_path = args.get(4).map(|s| s.as_str());
+ let rpc_url = args.get(5).map(|s| s.as_str());
+
// Validate program_id and access_controller
if Pubkey::from_str(program_id_str).is_err() {
eprintln!("Invalid program ID provided");
@@ -250,7 +305,9 @@ fn main() {
};
// Perform verification off-chain
- match verify_report(&signed_report, program_id_str, access_controller_str) {
+ match
+ verify_report(&signed_report, program_id_str, access_controller_str, keypair_path, rpc_url)
+ {
Ok(report) => {
println!("\nVerified Report Data:");
println!("Feed ID: {}", report.feed_id);
@@ -269,6 +326,20 @@ fn main() {
}
}
}
+
+fn print_usage(program_name: &str) {
+ println!("Usage: {} [keypair-path] [rpc-url]", program_name);
+ println!("Arguments:");
+ println!(" program-id The Solana program ID for the verification");
+ println!(" access-controller The access controller public key");
+ println!(" hex-encoded-signed-report The signed report data in hex format");
+ println!(
+ " keypair-path Optional: Path to the keypair file (default: ~/.config/solana/id.json)"
+ );
+ println!(
+ " rpc-url Optional: URL for the Solana RPC endpoint (default: https://api.devnet.solana.com)"
+ );
+}
```
#### 5. Build and run the verifier
@@ -306,15 +377,16 @@ fn main() {
Program ID: Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c
Access Controller: 2k3DsgwBoqrnvXKVvd7jX7aptNxdcRBdcd5HkYsGgbrb
Verifying report of 736 bytes...
- FeedId: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782
+
+ Verified Report Data:
+ Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782
Valid from timestamp: 1734124400
Observations timestamp: 1734124400
Native fee: 25614677280600
Link fee: 3574678975954600
Expires at: 1734210800
- Price: 3904011708000000000000
+ Benchmark price: 3904011708000000000000
Bid: 3903888333211164500000
- Ask: 3904628100124598400000
```
### Best practices
diff --git a/src/content/data-streams/tutorials/streams-direct/solana-onchain-report-verification.mdx b/src/content/data-streams/tutorials/streams-direct/solana-onchain-report-verification.mdx
index 2c09402a5bd..a21b987dc20 100644
--- a/src/content/data-streams/tutorials/streams-direct/solana-onchain-report-verification.mdx
+++ b/src/content/data-streams/tutorials/streams-direct/solana-onchain-report-verification.mdx
@@ -73,19 +73,23 @@ Before you begin, you should have:
To complete this guide, you'll need:
-- **Rust and Cargo**: Install the latest version using [rustup](https://rustup.rs/). Run to verify your installation.
+- **Rust tool-chain ([rustup](https://rustup.rs/))**: Use `rustc` and `cargo` ≥ 1.73 (the version Anchor 0.31 requires). Run to verify your installation.
-- **Solana CLI tools**: Install the latest version following the [official guide](https://docs.solana.com/cli/install-solana-cli-tools). Run to verify your installation.
+- **Solana CLI tools**: Use 2.x (Anchor 0.31 is built against the 2.x SDK). Install an appropriate version following the [official guide](https://docs.solana.com/cli/install-solana-cli-tools). Run to verify your installation.
-- **Anchor Framework**: Follow the [official installation guide](https://www.anchor-lang.com/docs/installation). Run to verify your installation.
+- **Anchor Framework**: Use Anchor ≥ 0.31.0 (older 0.29/0.30 will not work with Solana 2.x.). Follow the [official installation guide](https://www.anchor-lang.com/docs/installation). Run to verify your installation.
-- **Node.js and npm**: [Install Node.js 20 or later](https://nodejs.org/). Verify your installation with .
+- **Node.js and npm**: [Install Node.js ≥ 20](https://nodejs.org/). Verify your installation with .
-- **ts-node**: Install globally using npm: . Verify your installation with .
+- **ts-node**: Use ts-node ≥ 10. Install globally using npm: . Verify your installation with .
-- **Devnet SOL**: You'll need devnet SOL for deployment and testing. Use the [Solana CLI](https://docs.solana.com/cli/transfer-tokens#airdrop-some-tokens-to-get-started) or the [Solana Faucet](https://faucet.solana.com/) to get devnet SOL.
+- **Devnet SOL**: You'll need ~3-5 SOL in your devnet wallet for deployment and testing. Use the [Solana CLI](https://docs.solana.com/cli/transfer-tokens#airdrop-some-tokens-to-get-started) or the [Solana Faucet](https://faucet.solana.com/) to get devnet SOL.
-> **Note**: While this guide uses the Anchor framework for project structure, you can integrate the verification using any Rust-based Solana program framework. The verifier SDK is written in Rust, but you can integrate it into your preferred Rust program structure.
+
### Implementation guide
@@ -110,12 +114,14 @@ To complete this guide, you'll need:
Open your `Anchor.toml` file at the root of your project and update it to use devnet:
```toml
+[toolchain]
+solana_version = "2.1.0"
+
[features]
-seeds = false
+resolution = true
skip-lint = false
[programs.devnet]
-# Replace with your program ID
example_verify = ""
[registry]
@@ -137,11 +143,11 @@ In your program's manifest file (`programs/example_verify/Cargo.toml`), add the
```toml
[dependencies]
-chainlink_solana_data_streams = { git = "https://github.com/smartcontractkit/chainlink-solana", branch = "develop", subdir = "contracts/crates/chainlink-solana-data-streams" }
-data-streams-report = { git = "https://github.com/smartcontractkit/data-streams-sdk.git", subdir = "rust/crates/report" }
+chainlink_solana_data_streams = { git = "https://github.com/smartcontractkit/chainlink-data-streams-solana", subdir = "crates/chainlink-solana-data-streams", tag = "v1.1.0" }
+chainlink-data-streams-report = "1.0.0"
# Additional required dependencies
-anchor-lang = "0.29.0"
+anchor-lang = { version = "0.31.0", features = ["idl-build"] }
```
#### 4. Write the program
@@ -152,14 +158,13 @@ Navigate to your program main file (`programs/example_verify/src/lib.rs`). This
// Import required dependencies for Anchor, Solana, and Data Streams
use anchor_lang::prelude::*;
use anchor_lang::solana_program::{
+ instruction::Instruction,
program::{get_return_data, invoke},
pubkey::Pubkey,
- instruction::Instruction,
};
-use data_streams_report::report::v3::ReportDataV3;
+use chainlink_data_streams_report::report::v3::ReportDataV3;
use chainlink_solana_data_streams::VerifierInstructions;
-
declare_id!("");
#[program]
@@ -253,7 +258,12 @@ Replace `` with your program ID in the `declare_id!` macro. You
Note how the `VerifierInstructions::verify` helper method automatically handles the PDA computations internally. Refer to the [Program Derived Addresses (PDAs)](#program-derived-addresses-pdas) section for more information.
-> This example uses the [V3 schema](/data-streams/reference/report-schema) for [crypto streams](/data-streams/crypto-streams) to decode the report. If you verify reports for [RWA streams](/data-streams/rwa-streams), import and use the [V4 schema](/data-streams/reference/report-schema-v4) from the [report crate](https://github.com/smartcontractkit/data-streams-sdk/tree/main/rust/crates/report) instead.
+
#### 5. Deploy your program
@@ -263,20 +273,6 @@ Note how the `VerifierInstructions::verify` helper method automatically handles
anchor build
```
- **Note**: If you run into this error, set the `version` field at the top of your `cargo.lock` file to `3`.
-
- ```bash
- warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
- note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
- note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
- note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
- warning: .../example_verify/programs/example_verify/Cargo.toml: unused manifest key: dependencies.data-streams-report.subdir
- error: failed to parse lock file at: .../example_verify/Cargo.lock
-
- Caused by:
- lock file version 4 requires `-Znext-lockfile-bump`
- ```
-
1. Deploy your program to a Solana cluster (devnet in this example) using:
```bash
@@ -290,9 +286,9 @@ Note how the `VerifierInstructions::verify` helper method automatically handles
Upgrade authority: ~/.config/solana/id.json
Deploying program "example_verify"...
Program path: ~/example_verify/target/deploy/example_verify.so...
- Program Id: 8XcUbDgY2UaUYNHkirKsWqXJtzPXezBSyj5Yh87dXums
+ Program Id: ET4wvk16BozbiAyja89uHPGZxXzosyBFWcY6E6koCHxF
- Signature: 3ky6VkpebDGq7x1n8JB32daybmjvbRBsD4yR2uCCussSWhokaEESTXuSa5s8NMvKTz2NZjoq9aoQ9pvuw9bYoibt
+ Signature: FoFxJyWEYzwiA8k8W4mh4TC8EJicbvXKWjha2SGpD6EggXJWBRpKoHhiAtsuWJgN9pPDfyrfa92qCKmmPkvt9Ub
Deploy success
```
@@ -303,10 +299,7 @@ In this section, you'll write a client script to interact with your deployed pro
1. In the `tests` directory, create a new file to interact with your deployed program.
-1. Populate your `verify_tests.ts` file with the example client script below.
-
- - Replace `` with your program ID.
- - This example provides a report payload. To use your own report payload, update the `hexString` variable.
+1. Populate your `verify_test.ts` file with the example client script below. This example provides a report payload. To use your own report payload, update the `hexString` variable.
```typescript
import * as anchor from "@coral-xyz/anchor"
@@ -323,12 +316,9 @@ In this section, you'll write a client script to interact with your deployed pro
const provider = anchor.AnchorProvider.env()
anchor.setProvider(provider)
- // Initialize your program using the IDL and your program ID
- const program = new Program(
- require("../target/idl/example_verify.json"),
- "",
- provider
- )
+ // Initialize the program using Anchor workspace - this automatically
+ // loads the IDL and program ID from lib.rs (declare_id!)
+ const program = anchor.workspace.ExampleVerify as Program
// Convert the hex string to a Uint8Array
// This is an example report payload for a crypto stream
@@ -455,7 +445,7 @@ In this section, you'll write a client script to interact with your deployed pro
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔑 Signer: 1BZZU8cJsrMSBaQQGUxTE4LQYX2SU2jjs97pkrz7rHD
✅ Transaction successful!
- 🔗 Signature: 2CTZ7kgAxTogvMgb7QFDJUAq9xFBUVTEvyjf7UuhoVrHDhYKtHpQmd8hEy9XvLhfgWMdVTpCRvdf18r1ixgtncUc
+ 🔗 Signature: 5wVCWAQUs7gZyCzbJDGs7UGHjbr3Z9YwyiV3PcpiJyKwZUVaUBf7BUAqX5CFGGfGWbBRu3PdfBRXCndeoeNPh4Ca
📋 Program Logs
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -463,7 +453,7 @@ In this section, you'll write a client script to interact with your deployed pro
📍 Instruction: Verify
📍 Report data found
📍 FeedId: 0x0003684ea93c43ed7bd00ab3bb189bb62f880436589f1ca58b599cd97d6007fb
- 📍 valid from timestamp: 1733758884
+ 📍 Valid from timestamp: 1733758884
📍 Observations Timestamp: 1733758884
📍 Native Fee: 84021511714900
📍 Link Fee: 12978571827423900