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