From 53065e344b5052ed354bea68dd1d6362f4ea3d70 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:36:52 -0500 Subject: [PATCH 1/8] ds-solana-report-verification --- public/changelog.json | 12 + .../images/tutorial-icons/solanaLogoMark.svg | 13 + .../images/tutorial-icons/solidity_logo.svg | 27 + src/components/PageTabs.astro | 362 ++++++++++--- src/config/sidebar.ts | 12 +- .../register-from-eoa-burn-mint-foundry.mdx | 2 - .../register-from-eoa-burn-mint-hardhat.mdx | 2 - .../register-from-eoa-lock-mint-foundry.mdx | 2 - .../register-from-eoa-lock-mint-hardhat.mdx | 2 - .../update-rate-limiters-foundry.mdx | 2 - .../update-rate-limiters-hardhat.mdx | 2 - .../reference/report-schema-v4.mdx | 6 +- .../data-streams/reference/report-schema.mdx | 4 +- .../streams-direct-onchain-verification.mdx | 2 +- ...dx => evm-onchain-report-verification.mdx} | 24 +- .../solana-offchain-report-verification.mdx | 316 +++++++++++ .../solana-onchain-report-verification.mdx | 494 ++++++++++++++++++ .../streams-direct/streams-direct-api-rwa.mdx | 3 +- .../streams-direct/streams-direct-api.mdx | 3 +- .../streams-direct/streams-direct-ws-rwa.mdx | 3 +- .../streams-direct/streams-direct-ws.mdx | 3 +- src/features/feeds/components/FeedList.tsx | 6 +- .../feeds/components/Tables.module.css | 92 ++++ src/features/feeds/components/Tables.tsx | 303 +++++++---- src/features/redirects/redirects.json | 5 + vercel.json | 5 + 26 files changed, 1498 insertions(+), 209 deletions(-) create mode 100644 public/images/tutorial-icons/solanaLogoMark.svg create mode 100644 public/images/tutorial-icons/solidity_logo.svg rename src/content/data-streams/tutorials/streams-direct/{streams-direct-onchain-verification.mdx => evm-onchain-report-verification.mdx} (90%) create mode 100644 src/content/data-streams/tutorials/streams-direct/solana-offchain-report-verification.mdx create mode 100644 src/content/data-streams/tutorials/streams-direct/solana-onchain-report-verification.mdx diff --git a/public/changelog.json b/public/changelog.json index 903c22444eb..9b17ec94981 100644 --- a/public/changelog.json +++ b/public/changelog.json @@ -1,4 +1,16 @@ [ + { + "category": "release", + "changes": [], + "date": "2024-12-17", + "description": "Chainlink Data Streams now supports both onchain and offchain verification of reports on Solana.\n\nChoose between verification methods based on your application's needs while maintaining the same security guarantees:\n- [Verify reports directly within Solana programs using Cross-Program Invocation (CPI)](https://docs.chain.link/data-streams/tutorials/streams-direct/solana-onchain-report-verification)\n- [Verify reports client-side using the Data Streams SDK](https://docs.chain.link/data-streams/tutorials/streams-direct/solana-offchain-report-verification)\n\nVerifier Program IDs and Access Controller Accounts for both Devnet and Mainnet environments are available on the [Stream Addresses](https://docs.chain.link/data-streams/crypto-streams) page.", + "relatedNetworks": ["solana"], + "relatedTokens": [], + "title": "Data Streams Report Verification on Solana", + "topic": "data", + "subTopic": "data-streams", + "urls": [] + }, { "category": "integration", "changes": [], diff --git a/public/images/tutorial-icons/solanaLogoMark.svg b/public/images/tutorial-icons/solanaLogoMark.svg new file mode 100644 index 00000000000..ed6f34d95f7 --- /dev/null +++ b/public/images/tutorial-icons/solanaLogoMark.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/images/tutorial-icons/solidity_logo.svg b/public/images/tutorial-icons/solidity_logo.svg new file mode 100644 index 00000000000..86b9f4995b2 --- /dev/null +++ b/public/images/tutorial-icons/solidity_logo.svg @@ -0,0 +1,27 @@ + + + + +Vector 1 +Created with Sketch. + + + + + + + + + + + + + diff --git a/src/components/PageTabs.astro b/src/components/PageTabs.astro index 12088dbe17e..546fa402330 100644 --- a/src/components/PageTabs.astro +++ b/src/components/PageTabs.astro @@ -1,107 +1,337 @@ --- +/** + * PageTabs Component + * + * A dropdown-based navigation component that allows users to switch between different versions + * or variations of a page. + * + * @component + * + * @example + * ```mdx + * import { PageTabs } from "@components" + * + * + * ``` + * + * @typedef {Object} Page + * @property {string} name - The display name for the page/version + * @property {string} url - The URL path for the page/version + * @property {string} [icon] - Optional path to an icon image (SVG or PNG recommended) + * + * Props: + * @property {Page[]} pages - Array of page objects containing name, url, and optional icon + * @property {boolean} [showHeader=true] - Whether to show the header section + * @property {string} [headerTitle] - Custom title for the header. Defaults to "Guide Versions" + * @property {string} [headerDescription] - Custom description. Defaults to "This guide is available in multiple versions. Choose the one that matches your needs." + * + * Features: + * - Responsive dropdown interface + * - Optional icons for each page/version (SVG or PNG recommended) + * - Customizable header section (can be hidden) + * - Active state indication + * - Keyboard accessible + * - Click-outside to close + * + * Styling: + * - Responsive design with mobile breakpoints + * + * Default Values: + * - showHeader = true + * - headerTitle = "Guide Versions" + * - headerDescription = "This guide is available in multiple versions. Choose the one that matches your needs." + */ + export interface Props { pages: Array<{ name: string url: string - icon?: string // optional - width?: string // optional + icon?: string }> - tabWidth?: string // optional + showHeader?: boolean + headerTitle?: string + headerDescription?: string } -const { pages, tabWidth } = Astro.props as Props +const { pages, showHeader = true } = Astro.props as Props +const { + headerTitle = "Guide Versions", + headerDescription = "This guide is available in multiple versions. Choose the one that matches your needs.", +} = Astro.props as Props const currentPath = Astro.url.pathname -const normalizeUrl = (url: string) => { - return url.trim().replace(/\/$/, "") -} - -const isActive = (pageUrl: string) => { - const normalizedCurrentPath = normalizeUrl(currentPath) - const normalizedPageUrl = normalizeUrl(pageUrl) - return normalizedCurrentPath === normalizedPageUrl -} +const normalizeUrl = (url: string) => url.trim().replace(/\/$/, "") +const isActive = (pageUrl: string) => normalizeUrl(currentPath) === normalizeUrl(pageUrl) -const pageDetails = pages.filter( - (page): page is { name: string; url: string; icon?: string; width?: string } => page.url !== undefined -) +// Add current page info for mobile dropdown +const currentPage = pages.find((page) => isActive(page.url)) || pages[0] --- -
+
{ - pageDetails.map((page) => ( - - {page.icon && {`${page.name}} -
{page.name}
-
- )) + showHeader && ( +
+

{headerTitle}

+

{headerDescription}

+
+ ) } + +
- .page-column { - width: 100% !important; /* Override any inline widths */ - padding: 9px 10px; - flex-direction: column; - } + diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 3329eca2e17..6fe50e21596 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -284,7 +284,7 @@ export const SIDEBAR: Partial> = { ], }, { - title: "Streams Direct SDK", + title: "Streams Direct", url: "data-streams/tutorials/streams-direct/", children: [ { @@ -298,8 +298,12 @@ export const SIDEBAR: Partial> = { highlightAsCurrent: ["data-streams/tutorials/streams-direct/streams-direct-ws-rwa"], }, { - title: "Verify report data onchain", - url: "data-streams/tutorials/streams-direct/streams-direct-onchain-verification", + title: "Verify report data", + url: "data-streams/tutorials/streams-direct/evm-onchain-report-verification", + highlightAsCurrent: [ + "data-streams/tutorials/streams-direct/solana-onchain-report-verification", + "data-streams/tutorials/streams-direct/solana-offchain-report-verification", + ], }, ], }, @@ -347,7 +351,7 @@ export const SIDEBAR: Partial> = { url: "data-streams/reference/streams-direct/streams-direct-go-sdk", }, { - title: "Onchain report data verification", + title: "Onchain report data verification (EVM chains)", url: "data-streams/reference/streams-direct/streams-direct-onchain-verification", }, ], diff --git a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry.mdx b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry.mdx index fef0b97acec..ca7f9862f46 100644 --- a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry.mdx +++ b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry.mdx @@ -13,13 +13,11 @@ import CcipCommon from "@features/ccip/CcipCommon.astro" name: "Hardhat (Burn & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat", icon: "/images/tutorial-icons/hardhat-icn.png", - width: "270px", }, { name: "Foundry (Burn & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry", icon: "/images/tutorial-icons/foundry-icn.png", - width: "270px", }, ]} /> diff --git a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat.mdx b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat.mdx index 793999a2494..8cf273944d5 100644 --- a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat.mdx +++ b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat.mdx @@ -13,13 +13,11 @@ import CcipCommon from "@features/ccip/CcipCommon.astro" name: "Hardhat (Burn & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat", icon: "/images/tutorial-icons/hardhat-icn.png", - width: "270px", }, { name: "Foundry (Burn & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry", icon: "/images/tutorial-icons/foundry-icn.png", - width: "270px", }, ]} /> diff --git a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry.mdx b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry.mdx index 418b8110ada..0cd78b6a064 100644 --- a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry.mdx +++ b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry.mdx @@ -13,13 +13,11 @@ import CcipCommon from "@features/ccip/CcipCommon.astro" name: "Hardhat (Lock & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat", icon: "/images/tutorial-icons/hardhat-icn.png", - width: "270px", }, { name: "Foundry (Lock & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry", icon: "/images/tutorial-icons/foundry-icn.png", - width: "270px", }, ]} /> diff --git a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat.mdx b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat.mdx index 937bea196dd..dd244726d3c 100644 --- a/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat.mdx +++ b/src/content/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat.mdx @@ -13,13 +13,11 @@ import CcipCommon from "@features/ccip/CcipCommon.astro" name: "Hardhat (Lock & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat", icon: "/images/tutorial-icons/hardhat-icn.png", - width: "270px", }, { name: "Foundry (Lock & Mint)", url: "/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry", icon: "/images/tutorial-icons/foundry-icn.png", - width: "270px", }, ]} /> diff --git a/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry.mdx b/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry.mdx index bf0ac20c111..f59b09bb730 100644 --- a/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry.mdx +++ b/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry.mdx @@ -13,13 +13,11 @@ import CcipCommon from "@features/ccip/CcipCommon.astro" name: "Hardhat", url: "/ccip/tutorials/cross-chain-tokens/update-rate-limiters-hardhat", icon: "/images/tutorial-icons/hardhat-icn.png", - width: "270px", }, { name: "Foundry", url: "/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry", icon: "/images/tutorial-icons/foundry-icn.png", - width: "270px", }, ]} /> diff --git a/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-hardhat.mdx b/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-hardhat.mdx index 1e46d7ca540..ab805f9c788 100644 --- a/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-hardhat.mdx +++ b/src/content/ccip/tutorials/cross-chain-tokens/update-rate-limiters-hardhat.mdx @@ -13,13 +13,11 @@ import CcipCommon from "@features/ccip/CcipCommon.astro" name: "Hardhat", url: "/ccip/tutorials/cross-chain-tokens/update-rate-limiters-hardhat", icon: "/images/tutorial-icons/hardhat-icn.png", - width: "270px", }, { name: "Foundry", url: "/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry", icon: "/images/tutorial-icons/foundry-icn.png", - width: "270px", }, ]} /> diff --git a/src/content/data-streams/reference/report-schema-v4.mdx b/src/content/data-streams/reference/report-schema-v4.mdx index 61b2a6bd9ab..2488ad8ff98 100644 --- a/src/content/data-streams/reference/report-schema-v4.mdx +++ b/src/content/data-streams/reference/report-schema-v4.mdx @@ -14,14 +14,14 @@ import { PageTabs } from "@components" { name: "Crypto Schema (v3)", url: "/data-streams/reference/report-schema", - width: "230px", }, { name: "Real World Asset (RWA) Schema (v4)", url: "/data-streams/reference/report-schema-v4", - width: "350px", }, ]} + headerTitle="Available Schemas" + headerDescription="Choose the schema version you want to explore." /> Real World Asset (RWA) streams adhere to the report schema outlined below: @@ -34,7 +34,7 @@ Real World Asset (RWA) streams adhere to the report schema outlined below: | `nativeFee` | `uint192` | The cost to verify this report onchain when paying with the blockchain's native token | | `linkFee` | `uint192` | The cost to verify this report onchain when paying with LINK | | `expiresAt` | `uint32` | The expiration date of this report | -| `benchmarkPrice` | `int192` | The DON's consensus median price for this report carried to 18 decimal places | +| `price` | `int192` | The DON's consensus median price for this report carried to 18 decimal places | | `marketStatus` | `uint32` | The DON's consensus on whether the market is currently open. Possible values: `0` (`Unknown`), `1` (`Closed`), `2` (`Open`). | **Notes**: diff --git a/src/content/data-streams/reference/report-schema.mdx b/src/content/data-streams/reference/report-schema.mdx index 156169281f1..351d0abf017 100644 --- a/src/content/data-streams/reference/report-schema.mdx +++ b/src/content/data-streams/reference/report-schema.mdx @@ -14,14 +14,14 @@ import { PageTabs } from "@components" { name: "Crypto Schema (v3)", url: "/data-streams/reference/report-schema", - width: "230px", }, { name: "Real World Asset (RWA) Schema (v4)", url: "/data-streams/reference/report-schema-v4", - width: "350px", }, ]} + headerTitle="Available Schemas" + headerDescription="Choose the schema version you want to explore." /> Crypto streams adhere to the report schema outlined below: diff --git a/src/content/data-streams/reference/streams-direct/streams-direct-onchain-verification.mdx b/src/content/data-streams/reference/streams-direct/streams-direct-onchain-verification.mdx index fb013b79051..8a1f61a750d 100644 --- a/src/content/data-streams/reference/streams-direct/streams-direct-onchain-verification.mdx +++ b/src/content/data-streams/reference/streams-direct/streams-direct-onchain-verification.mdx @@ -17,7 +17,7 @@ import DataStreams from "@features/data-streams/common/DataStreams.astro" ## Verify reports onchain -To verify data onchain, Streams Direct requires several interfaces. +To verify data onchain on EVM chains, Streams Direct requires several Solidity interfaces. The primary onchain interaction occurs between the `IVerifierProxy` interface and your protocol's client contract. Find the Verifier proxy address for each stream on the [Stream Addresses](/data-streams/crypto-streams) page. diff --git a/src/content/data-streams/tutorials/streams-direct/streams-direct-onchain-verification.mdx b/src/content/data-streams/tutorials/streams-direct/evm-onchain-report-verification.mdx similarity index 90% rename from src/content/data-streams/tutorials/streams-direct/streams-direct-onchain-verification.mdx rename to src/content/data-streams/tutorials/streams-direct/evm-onchain-report-verification.mdx index d55d27a771e..9522f7f4ce0 100644 --- a/src/content/data-streams/tutorials/streams-direct/streams-direct-onchain-verification.mdx +++ b/src/content/data-streams/tutorials/streams-direct/evm-onchain-report-verification.mdx @@ -5,13 +5,33 @@ title: "Verify report data onchain" whatsnext: { "Find the list of available Stream IDs": "/data-streams/crypto-streams" } --- -import { CodeSample, CopyText, ClickToZoom } from "@components" +import { CodeSample, CopyText, ClickToZoom, PageTabs } from "@components" import { Tabs } from "@components/Tabs" import DataStreams from "@features/data-streams/common/DataStreams.astro" + + -In this tutorial, you'll learn how to verify onchain the integrity of reports by confirming their authenticity as signed by the Decentralized Oracle Network (DON). You'll use a verifier contract to verify the data onchain and pay the verification fee in LINK tokens. +In this guide, you'll learn how to verify onchain the integrity of reports by confirming their authenticity as signed by the Decentralized Oracle Network (DON). You'll use a verifier contract to verify the data onchain and pay the verification fee in LINK tokens. 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 new file mode 100644 index 00000000000..7fca427457d --- /dev/null +++ b/src/content/data-streams/tutorials/streams-direct/solana-offchain-report-verification.mdx @@ -0,0 +1,316 @@ +--- +section: dataStreams +date: Last Modified +title: "Verify report data offchain (Solana)" +whatsnext: + { + "Find the list of available Stream IDs": "/data-streams/crypto-streams", + "Learn how to verify reports on Solana onchain": "/data-streams/tutorials/streams-direct/solana-onchain-report-verification", + } +--- + +import { CodeSample, CopyText, ClickToZoom, PageTabs, Aside } from "@components" +import { Tabs } from "@components/Tabs" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + + + +To verify a Data Streams report, you must confirm the report integrity signed by the Decentralized Oracle Network (DON). + +You have two options to verify Data Streams reports on Solana: + +1. **Onchain verification**: Verify reports directly within your Solana program using [Cross-Program Invocation (CPI)](https://solana.com/docs/core/cpi) to the verifier program. Learn more about this method in the [onchain verification guide](/data-streams/tutorials/streams-direct/solana-onchain-report-verification). + +1. **Offchain verification**: Verify reports client-side using an SDK. You'll learn how to implement this method in this guide. + +Both methods use the same underlying verification logic and security guarantees, differing only in where the verification occurs. + +## Offchain verification + +Offchain verification allows you to verify the authenticity of Data Streams reports from your client-side application. While this method requires sending a transaction to the verifier program, the verification logic and processing of results happens in your client application rather than in a Solana program. + +In this guide, you'll learn how to: + +- Set up an Anchor project for offchain verification +- Configure the necessary dependencies +- Create a command-line tool to verify reports +- Process and display the verified report data + + + +### Prerequisites + +Before you begin, you should have: + +- Familiarity with [Rust](https://www.rust-lang.org/learn) programming +- Understanding of [Solana](https://solana.com/docs) concepts: + - [RPC Clients](https://solana.com/docs/rpc) and network interaction + - [Transactions](https://solana.com/docs/core/transactions) and signing + - [Keypairs](https://solana.com/docs/intro/wallets#keypair) and account management +- An allowlisted account in the Data Streams Access Controller ([contact us](https://chainlinkcommunity.typeform.com/datastreams?typeform-source=docs.chain.link#ref_id=docs) to get started with Early Access) + +### Requirements + +To complete this guide, you'll need: + +- **Rust and Cargo**: Install the latest version using [rustup](https://rustup.rs/). 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. + +- **Anchor Framework**: Follow the [official installation guide](https://www.anchor-lang.com/docs/installation). Run to verify your installation. + +- **Devnet SOL**: You'll need devnet SOL for transaction fees. 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. Check your balance with . + +- **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 implement offchain 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 + +#### 1. Create a new Anchor project + +1. Create a new Anchor project: + + ```bash + anchor init example_verify + cd example_verify + ``` + +1. Create a binary target for the verification tool: + ```bash + mkdir -p programs/example_verify/src/bin + touch programs/example_verify/src/bin/main.rs + ``` + +#### 2. Configure your project's dependencies + +Update your program's manifest file (`programs/example_verify/Cargo.toml`): + +```toml +[package] +name = "example_verify" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[[bin]] +name = "example_verify" +path = "src/bin/main.rs" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +data-streams-solana-verifier-sdk = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "data-streams-solana-verifier-sdk", submodules = false } +sdk-off-chain = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "sdk-off-chain", submodules = false } +data-streams-report = { git = "https://github.com/smartcontractkit/data-streams-sdk.git" } + +# Additional required dependencies +solana-sdk = "1.18.26" +solana-client = "1.18.26" +hex = "0.4.3" +``` + +#### 3. Implement the verification library + +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 solana_client::rpc_client::RpcClient; +use solana_sdk::{ + commitment_config::CommitmentConfig, + pubkey::Pubkey, + signature::read_keypair_file, + signer::Signer, +}; +use std::path::PathBuf; +use std::str::FromStr; + +pub fn default_keypair_path() -> String { + let mut path = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string())); + path.push(".config/solana/id.json"); + path.to_str().unwrap().to_string() +} + +pub fn verify_report(signed_report: &[u8], program_id: &str, access_controller: &str) -> Result> { + // Initialize RPC client with confirmed commitment level + let rpc_client = RpcClient::new_with_commitment( + "https://api.devnet.solana.com", + CommitmentConfig::confirmed() + ); + + // Load the keypair that will pay for and sign verification transactions + let payer = read_keypair_file(default_keypair_path())?; + println!("Using keypair: {}", payer.try_pubkey()?); + + // Set verifier program and access controller addresses + let program_id = Pubkey::from_str(program_id)?; + let access_controller = Pubkey::from_str(access_controller)?; + println!("Program ID: {}", program_id); + println!("Access Controller: {}", access_controller); + + // Create verification client + let client = VerificationClient::new(program_id, access_controller, rpc_client, payer); + + // 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 report = ReportDataV3::decode(&result.return_data.ok_or("No return data")?)?; + Ok(report) +} +``` + +> 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 + +Create `programs/example_verify/src/bin/main.rs`: + +```rust +use example_verify::verify_report; +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + if args.len() != 4 { + eprintln!( + "Usage: {} ", + args[0] + ); + std::process::exit(1); + } + + let program_id = &args[1]; + let access_controller = &args[2]; + let hex_report = &args[3]; + + // Decode the hex string + let signed_report = match hex::decode(hex_report) { + Ok(bytes) => bytes, + Err(e) => { + eprintln!("Failed to decode hex string: {}", e); + std::process::exit(1); + } + }; + + // Verify the report + match verify_report(&signed_report, program_id, access_controller) { + Ok(report) => { + println!("FeedId: {}", report.feed_id); + println!("Valid from timestamp: {}", report.valid_from_timestamp); + println!("Observations timestamp: {}", report.observations_timestamp); + println!("Native fee: {}", report.native_fee); + println!("Link fee: {}", report.link_fee); + println!("Expires at: {}", report.expires_at); + println!("Price: {}", report.benchmark_price); + println!("Bid: {}", report.bid); + println!("Ask: {}", report.ask); + } + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } +} +``` + +#### 5. Build and run the verifier + +1. Build the project: + + ```bash + cargo build + ``` + +1. Make sure you are connected to Devnet with . + +1. Run the verifier with your report: + + ```bash + cargo run -- + ``` + + Replace the placeholders with: + + - ``: The Verifier Program ID (find it on the [Stream Addresses](/data-streams/crypto-streams) page) + - ``: The Access Controller Account (find it on the [Stream Addresses](/data-streams/crypto-streams) page) + - ``: Your hex-encoded signed report (without the '0x' prefix) + + Example: + + ```bash + cargo run -- Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c 2k3DsgwBoqrnvXKVvd7jX7aptNxdcRBdcd5HkYsGgbrb f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578... + ``` + + Expect the output to be similar to the following: + + ```bash + Using keypair: + Program ID: Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c + Access Controller: 2k3DsgwBoqrnvXKVvd7jX7aptNxdcRBdcd5HkYsGgbrb + Verifying report of 736 bytes... + FeedId: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 + Valid from timestamp: 1734124400 + Observations timestamp: 1734124400 + Native fee: 25614677280600 + Link fee: 3574678975954600 + Expires at: 1734210800 + Price: 3904011708000000000000 + Bid: 3903888333211164500000 + Ask: 3904628100124598400000 + ``` + +### Best practices + +When implementing offchain verification in production: + +1. **Error Handling**: + + - Implement robust error handling for network issues + - Add proper logging and monitoring + - Handle report expiration gracefully + +1. **Security**: + + - Securely manage keypairs and never expose them + - Validate all input parameters + - Implement rate limiting for verification requests + +1. **Performance**: + + - Cache verified reports when appropriate + - Implement retry mechanisms with backoff + - Use connection pooling for RPC clients 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 new file mode 100644 index 00000000000..3c096bf665e --- /dev/null +++ b/src/content/data-streams/tutorials/streams-direct/solana-onchain-report-verification.mdx @@ -0,0 +1,494 @@ +--- +section: dataStreams +date: Last Modified +title: "Verify report data onchain (Solana)" +whatsnext: + { + "Find the list of available Stream IDs": "/data-streams/crypto-streams", + "Learn how to verify reports on Solana offchain": "/data-streams/tutorials/streams-direct/solana-offchain-report-verification", + } +--- + +import { CodeSample, CopyText, ClickToZoom, PageTabs, Aside } from "@components" +import { Tabs } from "@components/Tabs" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + + + +To verify a Data Streams report, you must confirm the report integrity signed by the Decentralized Oracle Network (DON). + +You have two options to verify Data Streams reports on Solana: + +1. **Onchain verification**: Verify reports directly within your Solana program using [Cross-Program Invocation (CPI)](https://solana.com/docs/core/cpi) to the Verifier program. You'll learn how to implement this method in this guide. + +1. **Offchain verification**: Verify reports client-side using an SDK. Learn more about this method in the [offchain verification guide](/data-streams/tutorials/streams-direct/solana-offchain-report-verification). + +Both methods use the same underlying verification logic and security guarantees, differing only in where the verification occurs. + +## Onchain verification + +You can verify Data Streams reports directly within your Solana program using onchain verification. This method ensures atomic verification and processing of report data. + +In this guide, you'll learn how to: + +- Integrate with the Chainlink Data Streams Verifier program +- Create and invoke the verification instruction +- Retrieve the verified report data + + + +### Prerequisites + +Before you begin, you should have: + +- Familiarity with [Rust](https://www.rust-lang.org/learn) programming +- Understanding of [Solana](https://solana.com/docs) concepts: + - [Accounts](https://solana.com/docs/core/accounts) + - [Instructions](https://solana.com/docs/core/transactions#instruction) + - [Program Derived Addresses (PDAs)](https://solana.com/docs/core/pda) +- Knowledge of the [Anchor](https://www.anchor-lang.com/) framework +- An allowlisted account in the Data Streams Access Controller. ([Contact us](https://chainlinkcommunity.typeform.com/datastreams?typeform-source=docs.chain.link#ref_id=docs) to get started with Early Access). + +### Requirements + +To complete this guide, you'll need: + +- **Rust and Cargo**: Install the latest version using [rustup](https://rustup.rs/). 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. + +- **Anchor Framework**: 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 . + +- **ts-node**: 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. + +> **Note**: While this guide uses the Anchor framework for project structure, you can implement onchain 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 + +#### 1. Create a new Anchor project + +1. Open your terminal and run the following command to create a new Anchor project: + + ```bash + anchor init example_verify + ``` + + This command creates a new directory named `example_verify` with the basic structure of an Anchor project. + +1. Navigate to the project directory: + + ```bash + cd example_verify + ``` + +#### 2. Configure your project for devnet + +Open your `Anchor.toml` file at the root of your project and update it to use devnet: + +```toml +[features] +seeds = false +skip-lint = false + +[programs.devnet] +# Replace with your program ID +example_verify = "" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "devnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" +``` + +Replace `` with your program ID. You can run to get your program ID. + +#### 3. Set up your program's dependencies + +In your program's manifest file (`programs/example_verify/Cargo.toml`), add the verifier SDK and the report crate as dependencies: + +```toml +[dependencies] +data-streams-solana-verifier-sdk = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "data-streams-solana-verifier-sdk" } +data-streams-report = { git = "https://github.com/smartcontractkit/data-streams-sdk.git", subdir = "rust/crates/report" } + +# Additional required dependencies +anchor-lang = "0.29.0" +``` + +#### 4. Write the program + +Navigate to your program main file (`programs/example_verify/src/lib.rs`). This is where you'll write your program logic. Replace the contents of `lib.rs` with the following example code: + +```rust +// Import required dependencies for Anchor, Solana, and Data Streams +use anchor_lang::prelude::borsh::{BorshDeserialize}; +use anchor_lang::prelude::*; +use anchor_lang::solana_program::program::{get_return_data, invoke}; +use data_streams_report::report::{v3::ReportDataV3}; +use data_streams_solana_verifier_sdk::VerifierInstructions; + +declare_id!(""); + +#[program] +pub mod example_verify { + use super::*; + + /// Verifies a Data Streams report using Cross-Program Invocation to the Verifier program + /// Returns the decoded report data if verification succeeds + pub fn verify(ctx: Context, signed_report: Vec) -> Result<()> { + // Create verification instruction with required accounts and signed report + let verify_ix = VerifierInstructions::verify( + &ctx.accounts.verifier_program_id.key(), + &ctx.accounts.verifier_account.key(), + &ctx.accounts.access_controller.key(), + &ctx.accounts.user.key(), + &ctx.accounts.config_account.key(), + signed_report + ); + + // Invoke the Verifier program with required account infos + invoke( + &verify_ix, + &[ + ctx.accounts.verifier_account.to_account_info(), + ctx.accounts.access_controller.to_account_info(), + ctx.accounts.user.to_account_info(), + ctx.accounts.config_account.to_account_info(), + ], + )?; + + // Decode and log the verified report data + if let Some((_program_id, return_data)) = get_return_data() { + msg!("Report data found"); + let report = ReportDataV3::decode(&return_data).unwrap(); + + // Log report fields + msg!("FeedId: {}", report.feed_id); + msg!("valid from timestamp: {}", report.valid_from_timestamp); + msg!("Observations Timestamp: {}", report.observations_timestamp); + msg!("Native Fee: {}", report.native_fee); + msg!("Link Fee: {}", report.link_fee); + msg!("Expires At: {}", report.expires_at); + msg!("Benchmark Price: {}", report.benchmark_price); + msg!("Bid: {}", report.bid); + msg!("Ask: {}", report.ask); + } else { + msg!("No report data found"); + } + + Ok(()) + } +} + +#[derive(Accounts)] +pub struct ExampleProgramContext<'info> { + /// The Verifier Account stores the DON's public keys and other verification parameters. + /// This account must match the PDA derived from the verifier program. + /// CHECK: The account structure is validated by the verifier program. + pub verifier_account: AccountInfo<'info>, + /// The Access Controller Account manages permissions for report verification. + /// This account must match the PDA derived from the verifier program. + /// CHECK: The account structure is validated by the verifier program. + pub access_controller: AccountInfo<'info>, + /// The account that signs the transaction. This account must be allowlisted + /// in the access controller to verify reports. + pub user: Signer<'info>, + /// The Config Account contains feed-specific settings and constraints. + /// CHECK: The account structure is validated by the verifier program. + pub config_account: UncheckedAccount<'info>, + /// The Verifier Program ID specifies the target Chainlink Data Streams Verifier Program. + /// This ID differs between devnet and mainnet environments. + /// CHECK: The program ID is validated by the verifier program. + pub verifier_program_id: AccountInfo<'info>, +} +``` + +Replace `` with your program ID in the `declare_id!` macro. You can run to get your program ID. + +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 + +1. Run the following command to build your program: + + ```bash + 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 + anchor deploy + ``` + + Expect an output similar to the following: + + ```bash + Deploying cluster: https://api.devnet.solana.com + Upgrade authority: ~/.config/solana/id.json + Deploying program "example_verify"... + Program path: ~/example_verify/target/deploy/example_verify.so... + Program Id: 8XcUbDgY2UaUYNHkirKsWqXJtzPXezBSyj5Yh87dXums + + Signature: 3ky6VkpebDGq7x1n8JB32daybmjvbRBsD4yR2uCCussSWhokaEESTXuSa5s8NMvKTz2NZjoq9aoQ9pvuw9bYoibt + + Deploy success + ``` + +#### 6. Interact with the Verifier Program + +In this section, you'll write a client script to interact with your deployed program, which will use [Cross-Program Invocation (CPI)](https://solana.com/docs/core/cpi) to verify reports through the Chainlink Data Streams Verifier Program. + +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. + + ```typescript + import * as anchor from "@coral-xyz/anchor" + import { Program } from "@coral-xyz/anchor" + import { PublicKey } from "@solana/web3.js" + import { ExampleVerify } from "../target/types/example_verify" + import * as snappy from "snappy" + + // Data Streams Verifier Program ID on Devnet + const VERIFIER_PROGRAM_ID = new PublicKey("Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c") + + async function main() { + // Setup connection and provider + 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 + ) + + // Convert the hex string to a Uint8Array + // This is an example report payload for a crypto stream + const hexString = + "0x00064f2cd1be62b7496ad4897b984db99243e0921906f66ded15149d993ef42c000000000000000000000000000000000000000000000000000000000103c90c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200003684ea93c43ed7bd00ab3bb189bb62f880436589f1ca58b599cd97d6007fb0000000000000000000000000000000000000000000000000000000067570fa40000000000000000000000000000000000000000000000000000000067570fa400000000000000000000000000000000000000000000000000004c6ac85bf854000000000000000000000000000000000000000000000000002e1bf13b772a9c0000000000000000000000000000000000000000000000000000000067586124000000000000000000000000000000000000000000000000002bb4cf7662949c000000000000000000000000000000000000000000000000002bae04e2661000000000000000000000000000000000000000000000000000002bb6a26c3fbeb80000000000000000000000000000000000000000000000000000000000000002af5e1b45dd8c84b12b4b58651ff4173ad7ca3f5d7f5374f077f71cce020fca787124749ce727634833d6ca67724fd912535c5da0f42fa525f46942492458f2c2000000000000000000000000000000000000000000000000000000000000000204e0bfa6e82373ae7dff01a305b72f1debe0b1f942a3af01bad18e0dc78a599f10bc40c2474b4059d43a591b75bdfdd80aafeffddfd66d0395cca2fdeba1673d" + + // Remove the '0x' prefix if present + const cleanHexString = hexString.startsWith("0x") ? hexString.slice(2) : hexString + + // Validate hex string format + if (!/^[0-9a-fA-F]+$/.test(cleanHexString)) { + throw new Error("Invalid hex string format") + } + + // Convert hex to Uint8Array + const signedReport = new Uint8Array(cleanHexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))) + + // Compress the report using Snappy + const compressedReport = await snappy.compress(Buffer.from(signedReport)) + + // Derive necessary PDAs using the SDK's helper functions + const verifierAccount = await PublicKey.findProgramAddressSync([Buffer.from("verifier")], VERIFIER_PROGRAM_ID) + + const configAccount = await PublicKey.findProgramAddressSync([signedReport.slice(0, 32)], VERIFIER_PROGRAM_ID) + + // The Data Streams access controller on devnet + const accessController = new PublicKey("2k3DsgwBoqrnvXKVvd7jX7aptNxdcRBdcd5HkYsGgbrb") + + try { + console.log("\nšŸ“ Transaction Details") + console.log("ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”") + console.log("šŸ”‘ Signer:", provider.wallet.publicKey.toString()) + + const tx = await program.methods + .verify(compressedReport) + .accounts({ + verifierAccount: verifierAccount[0], + accessController: accessController, + user: provider.wallet.publicKey, + configAccount: configAccount[0], + verifierProgramId: VERIFIER_PROGRAM_ID, + }) + .rpc({ commitment: "confirmed" }) + + console.log("āœ… Transaction successful!") + console.log("šŸ”— Signature:", tx) + + // Fetch and display logs + const txDetails = await provider.connection.getTransaction(tx, { + commitment: "confirmed", + maxSupportedTransactionVersion: 0, + }) + + console.log("\nšŸ“‹ Program Logs") + console.log("ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”") + + let indentLevel = 0 + let currentProgramId = "" + txDetails.meta.logMessages.forEach((log) => { + // Handle indentation for inner program calls + if (log.includes("Program invoke")) { + const programIdMatch = log.match(/Program (.*?) invoke/) + if (programIdMatch) { + currentProgramId = programIdMatch[1] + // Remove "Unknown Program" prefix if present + currentProgramId = currentProgramId.replace("Unknown Program ", "") + // Remove parentheses if present + currentProgramId = currentProgramId.replace(/[()]/g, "") + } + console.log(" ".repeat(indentLevel) + "šŸ”„", log.trim()) + indentLevel++ + return + } + if (log.includes("Program return") || log.includes("Program consumed")) { + indentLevel = Math.max(0, indentLevel - 1) + } + + // Add indentation to all logs + const indent = " ".repeat(indentLevel) + + if (log.includes("Program log:")) { + const logMessage = log.replace("Program log:", "").trim() + if (log.includes("Program log:")) { + console.log(indent + "šŸ“", logMessage) + } else if (log.includes("Program data:")) { + console.log(indent + "šŸ“Š", log.replace("Program data:", "").trim()) + } + } + }) + console.log("ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”\n") + } catch (error) { + console.log("\nāŒ Transaction Failed") + console.log("ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”") + console.error("Error:", error) + console.log("ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”\n") + } + } + + main() + ``` + + **Note**: The Program IDs and Access Controller Accounts are available on the [Stream Addresses](/data-streams/crypto-streams) page. + +1. Add the `snappy` dependency to your project: + + ```bash + yarn add snappy + ``` + + `snappy` is a compression library that is used to compress the report data. + +1. Execute the test script to interact with your program: + + ```bash + ANCHOR_PROVIDER_URL="https://api.devnet.solana.com" ANCHOR_WALLET="~/.config/solana/id.json" ts-node tests/verify_test.ts + ``` + + Replace `~/.config/solana/id.json` with the path to your Solana wallet (e.g., `/Users/username/.config/solana/id.json`). + +1. Verify the output logs to ensure the report data is processed correctly. Expect to see the decoded report fields logged to the console: + + ```bash + šŸ“ Transaction Details + ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā” + šŸ”‘ Signer: 1BZZU8cJsrMSBaQQGUxTE4LQYX2SU2jjs97pkrz7rHD + āœ… Transaction successful! + šŸ”— Signature: 2CTZ7kgAxTogvMgb7QFDJUAq9xFBUVTEvyjf7UuhoVrHDhYKtHpQmd8hEy9XvLhfgWMdVTpCRvdf18r1ixgtncUc + + šŸ“‹ Program Logs + ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā” + šŸ“ Instruction: Verify + šŸ“ Instruction: Verify + šŸ“ Report data found + šŸ“ FeedId: 0x0003684ea93c43ed7bd00ab3bb189bb62f880436589f1ca58b599cd97d6007fb + šŸ“ valid from timestamp: 1733758884 + šŸ“ Observations Timestamp: 1733758884 + šŸ“ Native Fee: 84021511714900 + šŸ“ Link Fee: 12978571827423900 + šŸ“ Expires At: 1733845284 + šŸ“ Benchmark Price: 12302227135960220 + šŸ“ Bid: 12294760000000000 + šŸ“ Ask: 12304232715632312 + ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā”ā” + ``` + +### Learn more + +#### Program Derived Addresses (PDAs) + +The verification process relies on two important PDAs that are handled automatically by the [verifier SDK](https://github.com/smartcontractkit/data-streams-solana-integration/tree/main/verify-sdk): + +- **Verifier config account PDA**: + + - Derived using the verifier program ID as a seed + - Stores verification-specific configuration + - Used to ensure consistent verification parameters across all reports + +- **Report config account PDA**: + - Derived using the feed ID (first 32 bytes of the uncompressed signed report) as a seed + - Contains feed-specific configuration and constraints + - Ensures that each feed's verification follows its designated rules + +The SDK's `VerifierInstructions::verify` helper method performs these steps: + +1. Extracts the necessary seeds +2. Computes the PDAs using `Pubkey::find_program_derived_address` +3. Includes these derived addresses in the instruction data + +#### Best practices + +This guide provides a basic implementation of onchain verification. When you implement onchain verification, consider the following best practices: + +- Implement robust error handling: + - Handle verification failures and invalid reports comprehensively + - Implement proper error reporting and logging for debugging + - Add custom error types for different failure scenarios +- Add appropriate validations: + - Price threshold checks to prevent processing extreme values + - Timestamp validations to ensure data freshness + - Custom feed-specific validations based on your use case diff --git a/src/content/data-streams/tutorials/streams-direct/streams-direct-api-rwa.mdx b/src/content/data-streams/tutorials/streams-direct/streams-direct-api-rwa.mdx index c583c55e721..b0a1f3c1b6f 100644 --- a/src/content/data-streams/tutorials/streams-direct/streams-direct-api-rwa.mdx +++ b/src/content/data-streams/tutorials/streams-direct/streams-direct-api-rwa.mdx @@ -16,14 +16,13 @@ import DataStreams from "@features/data-streams/common/DataStreams.astro" - - + + diff --git a/src/features/feeds/components/Tables.module.css b/src/features/feeds/components/Tables.module.css index 8b72af8b9b0..b88cc445ffe 100644 --- a/src/features/feeds/components/Tables.module.css +++ b/src/features/feeds/components/Tables.module.css @@ -145,3 +145,95 @@ width: 100%; } } + +.networksContainer { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + max-width: 1200px; + margin: 0 auto; +} + +.networkCard { + border: 1px solid var(--border-color); + border-radius: 8px; + overflow: hidden; +} + +.networkHeader { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 1rem; + background: var(--background-color); + border: none; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.networkHeader:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +.networkHeader.active { + background: var(--active-background); +} + +.networkInfo { + display: flex; + align-items: center; + gap: 1rem; +} + +.networkInfo img { + border-radius: 50%; +} + +.expandIcon { + font-size: 1.5rem; + font-weight: bold; +} + +.networkDetails { + background-color: #f5f8ff; + padding: 1rem; + border-radius: 0 0 8px 8px; + transition: background-color 0.2s ease; +} + +.networkEnvironment { + margin-bottom: 1.5rem; +} + +.networkEnvironment h4 { + margin-bottom: 0.75rem; + color: var(--heading-color); +} + +.addressContainer { + display: flex; + align-items: center; + gap: 0.5rem; + font-family: monospace; + background: var(--code-background); + padding: 0.5rem; + border-radius: 4px; + margin-top: 0.25rem; +} + +.networkStatus { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--border-color); +} + +.networkStatus a { + color: var(--link-color); + text-decoration: none; +} + +.networkStatus a:hover { + text-decoration: underline; +} diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 4aada8bff7e..849b322172e 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -1,4 +1,5 @@ /** @jsxImportSource preact */ +import { useState } from "preact/hooks" import feedList from "./FeedList.module.css" import { clsx } from "../../../lib" import { ChainNetwork } from "~/features/data/chains" @@ -431,118 +432,204 @@ const StreamsNetworksData = [ { network: "Solana", logoUrl: "/assets/chains/solana.svg", - contactUs: true, - message: "to talk to an expert about integrating Chainlink Data Streams on Solana.", + networkStatus: "https://status.solana.com/", + isSolana: true, + mainnet: { + label: "Solana Mainnet", + verifierProgramId: "Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c", + accessController: "7mSn5MoBjyRLKoJShgkep8J17ueGG8rYioVAiSg5YWMF", + explorerUrl: "https://explorer.solana.com/address/%s", + }, + testnet: { + label: "Solana Devnet", + verifierProgramId: "Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c", + accessController: "2k3DsgwBoqrnvXKVvd7jX7aptNxdcRBdcd5HkYsGgbrb", + explorerUrl: "https://explorer.solana.com/address/%s?cluster=devnet", + }, }, ] -export const StreamsVerifierProxyTable = () => { +type NetworkDetails = { + verifierProxy?: string + verifierProgramId?: string + accessController?: string + explorerUrl: string + label: string +} + +type NetworkData = { + network: string + logoUrl: string + networkStatus?: string + mainnet?: NetworkDetails + testnet?: NetworkDetails + message?: string + isSolana?: boolean +} + +export const StreamsNetworkAddressesTable = () => { + const [activeNetwork, setActiveNetwork] = useState(null) + + const toggleNetwork = (network: string) => { + setActiveNetwork(activeNetwork === network ? null : network) + } + return ( - - - - - - - - - {StreamsNetworksData.map((network) => ( - - - - - ))} - -
NetworkVerifier proxy address
- {`${network.network} -
{network.network}
-
- {network.contactUs ? ( -
- - Contact us - {" "} - {network.message} -
- ) : ( - <> - {network.mainnet && ( -
- {network.mainnet.label}: - - {network.mainnet.verifierProxy} - - -
- )} - - {network.testnet && ( -
- {network.testnet.label}: - - {network.testnet.verifierProxy} - - -
- )} - - {network.networkStatus && ( -
- - Track the status of this network at{" "} - - {network.networkStatus} - - -
- )} - - )} -
+
+ {StreamsNetworksData.map((network: NetworkData) => ( +
+ + + {activeNetwork === network.network && ( +
+ <> + {network.mainnet && ( +
+

{network.mainnet.label}

+ {network.isSolana ? ( + <> +
+ Verifier Program ID: + +
+
+ Access Controller Account: + +
+ + ) : ( +
+ Verifier Proxy Address: + +
+ )} +
+ )} + + {network.testnet && ( +
+

{network.testnet.label}

+ {network.isSolana ? ( + <> +
+ Verifier Program ID: + +
+
+ Access Controller Account: + +
+ + ) : ( +
+ Verifier Proxy Address: + +
+ )} +
+ )} + + {network.networkStatus && ( + + )} + +
+ )} +
+ ))} +
+ ) +} + +const CopyableAddress = ({ + address, + explorerUrl, + network, + environment, + type, +}: { + address?: string + explorerUrl: string + network: NetworkData + environment: string + type: string +}) => { + if (!address) return null + + return ( +
+ + {address} + + +
) } diff --git a/src/features/redirects/redirects.json b/src/features/redirects/redirects.json index ae1770cdaaa..baf4779fb36 100644 --- a/src/features/redirects/redirects.json +++ b/src/features/redirects/redirects.json @@ -1984,6 +1984,11 @@ "source": "data-feeds/proof-of-reserve/addresses", "destination": "data-feeds/smartdata/addresses", "statuscode": 301 + }, + { + "source": "data-streams/tutorials/streams-direct/streams-direct-onchain-verification", + "destination": "data-streams/tutorials/streams-direct/evm-onchain-report-verification", + "statuscode": 301 } ] } diff --git a/vercel.json b/vercel.json index 2be4a76acf6..a2620fb0e30 100644 --- a/vercel.json +++ b/vercel.json @@ -431,6 +431,11 @@ "source": "data-feeds/proof-of-reserve/addresses", "destination": "data-feeds/smartdata/addresses", "statusCode": 301 + }, + { + "source": "data-streams/tutorials/streams-direct/streams-direct-onchain-verification", + "destination": "data-streams/tutorials/streams-direct/evm-onchain-report-verification", + "statusCode": 301 } ] } \ No newline at end of file From 2a6bf2e5e779d6b8795d1fa58b1f7b91ea414c26 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:25:36 -0500 Subject: [PATCH 2/8] update --- .../evm-onchain-report-verification.mdx | 4 +- .../solana-offchain-report-verification.mdx | 4 +- .../solana-onchain-report-verification.mdx | 77 +++++++++++-------- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/content/data-streams/tutorials/streams-direct/evm-onchain-report-verification.mdx b/src/content/data-streams/tutorials/streams-direct/evm-onchain-report-verification.mdx index 9522f7f4ce0..0d8ce5147f1 100644 --- a/src/content/data-streams/tutorials/streams-direct/evm-onchain-report-verification.mdx +++ b/src/content/data-streams/tutorials/streams-direct/evm-onchain-report-verification.mdx @@ -17,12 +17,12 @@ import DataStreams from "@features/data-streams/common/DataStreams.astro" icon: "/images/tutorial-icons/solidity_logo.svg", }, { - name: "Solana (Rust) / onchain verification", + name: "Solana (Rust) / onchain integration", url: "/data-streams/tutorials/streams-direct/solana-onchain-report-verification", icon: "/images/tutorial-icons/solanaLogoMark.svg", }, { - name: "Solana (Rust) / offchain verification", + name: "Solana (Rust) / offchain integration", url: "/data-streams/tutorials/streams-direct/solana-offchain-report-verification", icon: "/images/tutorial-icons/solanaLogoMark.svg", }, 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 7fca427457d..c7251e8f381 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 @@ -21,12 +21,12 @@ import DataStreams from "@features/data-streams/common/DataStreams.astro" icon: "/images/tutorial-icons/solidity_logo.svg", }, { - name: "Solana (Rust) - Onchain verification", + name: "Solana (Rust) - Onchain integration", url: "/data-streams/tutorials/streams-direct/solana-onchain-report-verification", icon: "/images/tutorial-icons/solanaLogoMark.svg", }, { - name: "Solana (Rust) - Offchain verification", + name: "Solana (Rust) - Offchain integration", url: "/data-streams/tutorials/streams-direct/solana-offchain-report-verification", icon: "/images/tutorial-icons/solanaLogoMark.svg", }, 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 3c096bf665e..f7f18663fd3 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 @@ -21,12 +21,12 @@ import DataStreams from "@features/data-streams/common/DataStreams.astro" icon: "/images/tutorial-icons/solidity_logo.svg", }, { - name: "Solana (Rust) - Onchain verification", + name: "Solana (Rust) - Onchain integration", url: "/data-streams/tutorials/streams-direct/solana-onchain-report-verification", icon: "/images/tutorial-icons/solanaLogoMark.svg", }, { - name: "Solana (Rust) - Offchain verification", + name: "Solana (Rust) - Offchain integration", url: "/data-streams/tutorials/streams-direct/solana-offchain-report-verification", icon: "/images/tutorial-icons/solanaLogoMark.svg", }, @@ -133,11 +133,11 @@ Replace `` with your program ID. You can run "); + +declare_id!("Ens8sA3fwQWFMxzbU6Zf6tMe234Taf3ADnqdBsfL9RiR"); #[program] pub mod example_verify { @@ -165,19 +169,25 @@ pub mod example_verify { /// Verifies a Data Streams report using Cross-Program Invocation to the Verifier program /// Returns the decoded report data if verification succeeds pub fn verify(ctx: Context, signed_report: Vec) -> Result<()> { - // Create verification instruction with required accounts and signed report - let verify_ix = VerifierInstructions::verify( - &ctx.accounts.verifier_program_id.key(), - &ctx.accounts.verifier_account.key(), - &ctx.accounts.access_controller.key(), - &ctx.accounts.user.key(), - &ctx.accounts.config_account.key(), - signed_report + let program_id = ctx.accounts.verifier_program_id.key(); + let verifier_account = ctx.accounts.verifier_account.key(); + let access_controller = ctx.accounts.access_controller.key(); + let user = ctx.accounts.user.key(); + let config_account = ctx.accounts.config_account.key(); + + // Create verification instruction + let chainlink_ix: Instruction = VerifierInstructions::verify( + &program_id, + &verifier_account, + &access_controller, + &user, + &config_account, + signed_report, ); - // Invoke the Verifier program with required account infos + // Invoke the Verifier program invoke( - &verify_ix, + &chainlink_ix, &[ ctx.accounts.verifier_account.to_account_info(), ctx.accounts.access_controller.to_account_info(), @@ -189,11 +199,12 @@ pub mod example_verify { // Decode and log the verified report data if let Some((_program_id, return_data)) = get_return_data() { msg!("Report data found"); - let report = ReportDataV3::decode(&return_data).unwrap(); + let report = ReportDataV3::decode(&return_data) + .map_err(|_| error!(CustomError::InvalidReportData))?; // Log report fields msg!("FeedId: {}", report.feed_id); - msg!("valid from timestamp: {}", report.valid_from_timestamp); + msg!("Valid from timestamp: {}", report.valid_from_timestamp); msg!("Observations Timestamp: {}", report.observations_timestamp); msg!("Native Fee: {}", report.native_fee); msg!("Link Fee: {}", report.link_fee); @@ -203,30 +214,36 @@ pub mod example_verify { msg!("Ask: {}", report.ask); } else { msg!("No report data found"); + return Err(error!(CustomError::NoReportData)); } Ok(()) } } +#[error_code] +pub enum CustomError { + #[msg("No valid report data found")] + NoReportData, + #[msg("Invalid report data format")] + InvalidReportData, +} + #[derive(Accounts)] pub struct ExampleProgramContext<'info> { /// The Verifier Account stores the DON's public keys and other verification parameters. /// This account must match the PDA derived from the verifier program. - /// CHECK: The account structure is validated by the verifier program. + /// CHECK: The account is validated by the verifier program. pub verifier_account: AccountInfo<'info>, - /// The Access Controller Account manages permissions for report verification. - /// This account must match the PDA derived from the verifier program. + /// The Access Controller Account /// CHECK: The account structure is validated by the verifier program. pub access_controller: AccountInfo<'info>, - /// The account that signs the transaction. This account must be allowlisted - /// in the access controller to verify reports. + /// The account that signs the transaction. pub user: Signer<'info>, - /// The Config Account contains feed-specific settings and constraints. - /// CHECK: The account structure is validated by the verifier program. + /// The Config Account is a PDA derived from a signed report + /// CHECK: The account is validated by the verifier program. pub config_account: UncheckedAccount<'info>, /// The Verifier Program ID specifies the target Chainlink Data Streams Verifier Program. - /// This ID differs between devnet and mainnet environments. /// CHECK: The program ID is validated by the verifier program. pub verifier_program_id: AccountInfo<'info>, } @@ -461,7 +478,7 @@ In this section, you'll write a client script to interact with your deployed pro #### Program Derived Addresses (PDAs) -The verification process relies on two important PDAs that are handled automatically by the [verifier SDK](https://github.com/smartcontractkit/data-streams-solana-integration/tree/main/verify-sdk): +The verification process relies on two important PDAs that are handled automatically by the [Chainlink Data Streams Client for Solana](https://github.com/smartcontractkit/chainlink-solana/tree/develop/contracts/crates/chainlink-solana-data-streams): - **Verifier config account PDA**: From e7d4ccba5d5cb5c45b4adfa6ec9946e14d8d226a Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:42:41 -0500 Subject: [PATCH 3/8] update --- .../solana-offchain-report-verification.mdx | 67 ++++++++++++------- .../solana-onchain-report-verification.mdx | 4 +- 2 files changed, 45 insertions(+), 26 deletions(-) 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 c7251e8f381..d658b4f9509 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 @@ -1,7 +1,7 @@ --- section: dataStreams date: Last Modified -title: "Verify report data offchain (Solana)" +title: "Verify report data - Offchain integration (Solana)" whatsnext: { "Find the list of available Stream IDs": "/data-streams/crypto-streams", @@ -128,14 +128,14 @@ cpi = ["no-entrypoint"] default = [] [dependencies] -data-streams-solana-verifier-sdk = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "data-streams-solana-verifier-sdk", submodules = false } -sdk-off-chain = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "sdk-off-chain", submodules = false } +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" } +sdk-off-chain = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "sdk-off-chain"} -# Additional required dependencies solana-sdk = "1.18.26" solana-client = "1.18.26" hex = "0.4.3" +borsh = "0.10.3" ``` #### 3. Implement the verification library @@ -152,8 +152,7 @@ use solana_sdk::{ signature::read_keypair_file, signer::Signer, }; -use std::path::PathBuf; -use std::str::FromStr; +use std::{path::PathBuf, str::FromStr}; pub fn default_keypair_path() -> String { let mut path = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string())); @@ -161,7 +160,11 @@ pub fn default_keypair_path() -> String { path.to_str().unwrap().to_string() } -pub fn verify_report(signed_report: &[u8], program_id: &str, access_controller: &str) -> Result> { +pub fn verify_report( + signed_report: &[u8], + program_id: &str, + access_controller: &str +) -> Result> { // Initialize RPC client with confirmed commitment level let rpc_client = RpcClient::new_with_commitment( "https://api.devnet.solana.com", @@ -170,16 +173,16 @@ pub fn verify_report(signed_report: &[u8], program_id: &str, access_controller: // Load the keypair that will pay for and sign verification transactions let payer = read_keypair_file(default_keypair_path())?; - println!("Using keypair: {}", payer.try_pubkey()?); + println!("Using keypair: {}", payer.pubkey()); - // Set verifier program and access controller addresses - let program_id = Pubkey::from_str(program_id)?; - let access_controller = Pubkey::from_str(access_controller)?; - println!("Program ID: {}", program_id); - println!("Access Controller: {}", access_controller); + // Convert to Pubkey + let program_pubkey = Pubkey::from_str(program_id)?; + let access_controller_pubkey = Pubkey::from_str(access_controller)?; + println!("Program ID: {}", program_pubkey); + println!("Access Controller: {}", access_controller_pubkey); - // Create verification client - let client = VerificationClient::new(program_id, access_controller, rpc_client, payer); + // Create a verification client instance + let client = VerificationClient::new(program_pubkey, access_controller_pubkey, rpc_client, payer); // Verify the report println!("Verifying report of {} bytes...", signed_report.len()); @@ -188,7 +191,9 @@ pub fn verify_report(signed_report: &[u8], program_id: &str, access_controller: e })?; - let report = ReportDataV3::decode(&result.return_data.ok_or("No return data")?)?; + // Decode the returned data into a ReportDataV3 struct + let return_data = result.return_data.ok_or("No return data")?; + let report = ReportDataV3::decode(&return_data)?; Ok(report) } ``` @@ -202,6 +207,9 @@ Create `programs/example_verify/src/bin/main.rs`: ```rust use example_verify::verify_report; use std::env; +use std::str::FromStr; +use hex; +use solana_sdk::pubkey::Pubkey; fn main() { let args: Vec = env::args().collect(); @@ -213,11 +221,21 @@ fn main() { std::process::exit(1); } - let program_id = &args[1]; - let access_controller = &args[2]; + let program_id_str = &args[1]; + let access_controller_str = &args[2]; let hex_report = &args[3]; - // Decode the hex string + // Validate program_id and access_controller + if Pubkey::from_str(program_id_str).is_err() { + eprintln!("Invalid program ID provided"); + std::process::exit(1); + } + if Pubkey::from_str(access_controller_str).is_err() { + eprintln!("Invalid access controller address provided"); + std::process::exit(1); + } + + // Decode the hex string for the signed report let signed_report = match hex::decode(hex_report) { Ok(bytes) => bytes, Err(e) => { @@ -226,21 +244,22 @@ fn main() { } }; - // Verify the report - match verify_report(&signed_report, program_id, access_controller) { + // Perform verification off-chain + match verify_report(&signed_report, program_id_str, access_controller_str) { Ok(report) => { - println!("FeedId: {}", report.feed_id); + println!("\nVerified Report Data:"); + println!("Feed ID: {}", report.feed_id); println!("Valid from timestamp: {}", report.valid_from_timestamp); println!("Observations timestamp: {}", report.observations_timestamp); println!("Native fee: {}", report.native_fee); println!("Link fee: {}", report.link_fee); println!("Expires at: {}", report.expires_at); - println!("Price: {}", report.benchmark_price); + println!("Benchmark price: {}", report.benchmark_price); println!("Bid: {}", report.bid); println!("Ask: {}", report.ask); } Err(e) => { - eprintln!("Error: {}", e); + eprintln!("Verification failed: {}", e); std::process::exit(1); } } 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 f7f18663fd3..1576284fb2d 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 @@ -1,7 +1,7 @@ --- section: dataStreams date: Last Modified -title: "Verify report data onchain (Solana)" +title: "Verify report data - Onchain integration (Solana)" whatsnext: { "Find the list of available Stream IDs": "/data-streams/crypto-streams", @@ -160,7 +160,7 @@ use data_streams_report::report::v3::ReportDataV3; use chainlink_solana_data_streams::VerifierInstructions; -declare_id!("Ens8sA3fwQWFMxzbU6Zf6tMe234Taf3ADnqdBsfL9RiR"); +declare_id!(""); #[program] pub mod example_verify { From 57e15ebbc777fc15a9de7cc2db56e1b8764fb7e0 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:44:38 -0500 Subject: [PATCH 4/8] update --- .../streams-direct/solana-onchain-report-verification.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1576284fb2d..b27446206f4 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 @@ -478,7 +478,7 @@ In this section, you'll write a client script to interact with your deployed pro #### Program Derived Addresses (PDAs) -The verification process relies on two important PDAs that are handled automatically by the [Chainlink Data Streams Client for Solana](https://github.com/smartcontractkit/chainlink-solana/tree/develop/contracts/crates/chainlink-solana-data-streams): +The verification process relies on two important PDAs that are handled automatically by the [Chainlink Data Streams Solana SDK](https://github.com/smartcontractkit/chainlink-solana/tree/develop/contracts/crates/chainlink-solana-data-streams): - **Verifier config account PDA**: From a4d4149aad872a55c9adddcbaef19cc2dc9ac034 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:06:03 -0500 Subject: [PATCH 5/8] update --- public/changelog.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/changelog.json b/public/changelog.json index 9b17ec94981..2538a2474b2 100644 --- a/public/changelog.json +++ b/public/changelog.json @@ -3,10 +3,10 @@ "category": "release", "changes": [], "date": "2024-12-17", - "description": "Chainlink Data Streams now supports both onchain and offchain verification of reports on Solana.\n\nChoose between verification methods based on your application's needs while maintaining the same security guarantees:\n- [Verify reports directly within Solana programs using Cross-Program Invocation (CPI)](https://docs.chain.link/data-streams/tutorials/streams-direct/solana-onchain-report-verification)\n- [Verify reports client-side using the Data Streams SDK](https://docs.chain.link/data-streams/tutorials/streams-direct/solana-offchain-report-verification)\n\nVerifier Program IDs and Access Controller Accounts for both Devnet and Mainnet environments are available on the [Stream Addresses](https://docs.chain.link/data-streams/crypto-streams) page.", + "description": "Onchain Verifier Programs are now available on Solana, providing both onchain and offchain integration approaches while maintaining strong security guarantees.\n\nVerifier Program IDs for both Devnet and Mainnet environments are available on the [Stream Addresses](https://docs.chain.link/data-streams/crypto-streams) page.", "relatedNetworks": ["solana"], "relatedTokens": [], - "title": "Data Streams Report Verification on Solana", + "title": "Data Streams Verifier Programs on Solana", "topic": "data", "subTopic": "data-streams", "urls": [] From 6c7e76e32eb12bdd40859d0493db26a4901f861e Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:36:06 -0500 Subject: [PATCH 6/8] update --- .../streams-direct/solana-offchain-report-verification.mdx | 1 - 1 file changed, 1 deletion(-) 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 d658b4f9509..6eb5fe98dec 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 @@ -128,7 +128,6 @@ cpi = ["no-entrypoint"] default = [] [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" } sdk-off-chain = { git = "https://github.com/smartcontractkit/smart-contract-examples.git", branch = "data-streams-solana-integration", package = "sdk-off-chain"} From 7c235e81a015e3afdbe877a03147440d6c3921e8 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:49:51 -0500 Subject: [PATCH 7/8] update --- .../solana-offchain-report-verification.mdx | 14 +++++++------- .../solana-onchain-report-verification.mdx | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) 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 6eb5fe98dec..f63526bf5eb 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 @@ -39,19 +39,19 @@ To verify a Data Streams report, you must confirm the report integrity signed by You have two options to verify Data Streams reports on Solana: -1. **Onchain verification**: Verify reports directly within your Solana program using [Cross-Program Invocation (CPI)](https://solana.com/docs/core/cpi) to the verifier program. Learn more about this method in the [onchain verification guide](/data-streams/tutorials/streams-direct/solana-onchain-report-verification). +1. **Onchain integration**: Verify reports directly within your Solana program using [Cross-Program Invocation (CPI)](https://solana.com/docs/core/cpi) to the verifier program. Learn more about this method in the [onchain verification guide](/data-streams/tutorials/streams-direct/solana-onchain-report-verification). -1. **Offchain verification**: Verify reports client-side using an SDK. You'll learn how to implement this method in this guide. +1. **Offchain integration**: Verify reports client-side using an SDK. You'll learn how to implement this method in this guide. Both methods use the same underlying verification logic and security guarantees, differing only in where the verification occurs. -## Offchain verification +## Offchain integration -Offchain verification allows you to verify the authenticity of Data Streams reports from your client-side application. While this method requires sending a transaction to the verifier program, the verification logic and processing of results happens in your client application rather than in a Solana program. +The offchain integration allows you to verify the authenticity of Data Streams reports from your client-side application. While this method requires sending a transaction to the verifier program, the verification logic and processing of results happens in your client application rather than in a Solana program. In this guide, you'll learn how to: -- Set up an Anchor project for offchain verification +- Set up an Anchor project - Configure the necessary dependencies - Create a command-line tool to verify reports - Process and display the verified report data @@ -83,7 +83,7 @@ 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 implement offchain 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. +> **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 @@ -313,7 +313,7 @@ fn main() { ### Best practices -When implementing offchain verification in production: +When implementing verification in production: 1. **Error Handling**: 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 b27446206f4..6af253889c1 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 @@ -39,15 +39,15 @@ To verify a Data Streams report, you must confirm the report integrity signed by You have two options to verify Data Streams reports on Solana: -1. **Onchain verification**: Verify reports directly within your Solana program using [Cross-Program Invocation (CPI)](https://solana.com/docs/core/cpi) to the Verifier program. You'll learn how to implement this method in this guide. +1. **Onchain integration**: Verify reports directly within your Solana program using [Cross-Program Invocation (CPI)](https://solana.com/docs/core/cpi) to the Verifier program. You'll learn how to implement this method in this guide. -1. **Offchain verification**: Verify reports client-side using an SDK. Learn more about this method in the [offchain verification guide](/data-streams/tutorials/streams-direct/solana-offchain-report-verification). +1. **Offchain integration**: Verify reports client-side using an SDK. Learn more about this method in the [offchain verification guide](/data-streams/tutorials/streams-direct/solana-offchain-report-verification). Both methods use the same underlying verification logic and security guarantees, differing only in where the verification occurs. -## Onchain verification +## Onchain integration -You can verify Data Streams reports directly within your Solana program using onchain verification. This method ensures atomic verification and processing of report data. +You can verify Data Streams reports directly within your Solana program using this integration. This method ensures atomic verification and processing of report data. In this guide, you'll learn how to: @@ -85,7 +85,7 @@ To complete this guide, you'll need: - **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. -> **Note**: While this guide uses the Anchor framework for project structure, you can implement onchain 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. +> **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 @@ -499,7 +499,7 @@ The SDK's `VerifierInstructions::verify` helper method performs these steps: #### Best practices -This guide provides a basic implementation of onchain verification. When you implement onchain verification, consider the following best practices: +This guide provides a basic example on how to verify reports. When you implement reports verification, consider the following best practices: - Implement robust error handling: - Handle verification failures and invalid reports comprehensively From bb03ce62ae4fb43236a53791d85886e5bffb142f Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:12:10 -0500 Subject: [PATCH 8/8] update --- .../solana-offchain-report-verification.mdx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 f63526bf5eb..f171ecce2a9 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 @@ -131,8 +131,9 @@ default = [] 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"} -solana-sdk = "1.18.26" -solana-client = "1.18.26" +solana-program = "2.1.6" +solana-sdk = "2.1.6" +solana-client = "2.1.6" hex = "0.4.3" borsh = "0.10.3" ``` @@ -151,7 +152,7 @@ use solana_sdk::{ signature::read_keypair_file, signer::Signer, }; -use std::{path::PathBuf, str::FromStr}; +use std::{ path::PathBuf, str::FromStr }; pub fn default_keypair_path() -> String { let mut path = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string())); @@ -181,7 +182,12 @@ pub fn verify_report( println!("Access Controller: {}", access_controller_pubkey); // Create a verification client instance - let client = VerificationClient::new(program_pubkey, access_controller_pubkey, rpc_client, payer); + let client = VerificationClient::new( + program_pubkey, + access_controller_pubkey, + rpc_client, + payer + ); // Verify the report println!("Verifying report of {} bytes...", signed_report.len()); @@ -290,7 +296,7 @@ fn main() { Example: ```bash - cargo run -- Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c 2k3DsgwBoqrnvXKVvd7jX7aptNxdcRBdcd5HkYsGgbrb f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578... + cargo run -- Gt9S41PtjR58CbG9JhJ3J6vxesqrNAswbWYbLNTMZA3c 2k3DsgwBoqrnvXKVvd7jX7aptNxdcRBdcd5HkYsGgbrb 0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000004f56930f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba78200000000000000000000000000000000000000000000000000000000675ca37000000000000000000000000000000000000000000000000000000000675ca3700000000000000000000000000000000000000000000000000000174be1bd8758000000000000000000000000000000000000000000000000000cb326ce8c3ea800000000000000000000000000000000000000000000000000000000675df4f00000000000000000000000000000000000000000000000d3a30bcc15e207c0000000000000000000000000000000000000000000000000d3a1557b5e634060200000000000000000000000000000000000000000000000d3ab99a974ff10f400000000000000000000000000000000000000000000000000000000000000000292bdd75612560e46ed9b0c2437898f81eb0e18b6b902a161b9708e9177175cf3b8ef2b279f230f766fb29306250ee90856516ee349ca42b2d7fb141deb006745000000000000000000000000000000000000000000000000000000000000000221c156e80276827e1bfeb6542ab064dfa958f5be955f516fb62b1c93437472c31cc65fcaba68c9d661701190bc32025a0690af0eefe027ac218fd15c588dd4d5 ``` Expect the output to be similar to the following: