From 6f455f069c58b4701dac57b7f8a604955ee5eec6 Mon Sep 17 00:00:00 2001 From: devin distefano Date: Tue, 7 Jul 2026 11:26:20 -0500 Subject: [PATCH 01/13] initial pass --- src/content/cre/capabilities/index.mdx | 16 +- src/content/cre/capabilities/solana-write.mdx | 75 ++ .../generating-bindings-go.mdx | 207 ++++ .../generating-bindings-ts.mdx | 212 ++++ .../using-solana-client/onchain-write-go.mdx | 294 ++++++ .../using-solana-client/onchain-write-ts.mdx | 275 +++++ .../using-solana-client/overview-go.mdx | 36 + .../using-solana-client/overview-ts.mdx | 36 + src/content/cre/llms-full-go.txt | 871 +++++++++++++++- src/content/cre/llms-full-ts.txt | 971 +++++++++++++++++- .../cre/reference/sdk/solana-client-go.mdx | 254 +++++ .../cre/reference/sdk/solana-client-ts.mdx | 368 +++++++ src/content/cre/supported-networks-go.mdx | 27 +- src/content/cre/supported-networks-ts.mdx | 27 +- 14 files changed, 3649 insertions(+), 20 deletions(-) create mode 100644 src/content/cre/capabilities/solana-write.mdx create mode 100644 src/content/cre/guides/workflow/using-solana-client/generating-bindings-go.mdx create mode 100644 src/content/cre/guides/workflow/using-solana-client/generating-bindings-ts.mdx create mode 100644 src/content/cre/guides/workflow/using-solana-client/onchain-write-go.mdx create mode 100644 src/content/cre/guides/workflow/using-solana-client/onchain-write-ts.mdx create mode 100644 src/content/cre/guides/workflow/using-solana-client/overview-go.mdx create mode 100644 src/content/cre/guides/workflow/using-solana-client/overview-ts.mdx create mode 100644 src/content/cre/reference/sdk/solana-client-go.mdx create mode 100644 src/content/cre/reference/sdk/solana-client-ts.mdx diff --git a/src/content/cre/capabilities/index.mdx b/src/content/cre/capabilities/index.mdx index bf1c02730c4..2d2d2b4fceb 100644 --- a/src/content/cre/capabilities/index.mdx +++ b/src/content/cre/capabilities/index.mdx @@ -22,7 +22,18 @@ Each capability is powered by its own independent Decentralized Oracle Network ( ## Invoking Capabilities via the SDK -As a developer, you do not interact with these capability DONs directly. Instead, you invoke them through the developer-friendly interfaces provided by the **CRE SDKs** ([Go](/cre/reference/sdk/core-go) and [TypeScript](/cre/reference/sdk/core-ts)), such as the [`evm.Client`](/cre/reference/sdk/evm-client) or the [`http.Client`](/cre/reference/sdk/http-client). The SDK handles the low-level complexity of communicating with the correct DON and processing the consensus-verified result, allowing you to focus on your business logic. +As a developer, you do not interact with these capability DONs directly. Instead, you invoke them through the developer-friendly interfaces provided by the **CRE SDKs** ([Go](/cre/reference/sdk/core-go) and [TypeScript](/cre/reference/sdk/core-ts)), such as the [`evm.Client`](/cre/reference/sdk/evm-client), the [`solana.Client`](/cre/reference/sdk/solana-client), or the [`http.Client`](/cre/reference/sdk/http-client). The SDK handles the low-level complexity of communicating with the correct DON and processing the consensus-verified result, allowing you to focus on your business logic. + +## Chain Families + +CRE supports blockchain capabilities across multiple **chain families** — groups of networks that share the same virtual machine, serialization format, and address encoding. The chain family determines which SDK client you use and how payloads are encoded. + +| Chain Family | Networks | SDK Client | Payload Encoding | +| ------------ | ------------------------------------------------------------------ | -------------------------------- | ------------------------ | +| EVM | Ethereum, Base, Arbitrum, and [many more](/cre/supported-networks) | `EVMClient` / `evm.Client` | ABI (viem / go-ethereum) | +| Solana | Solana Mainnet, Solana Devnet | `SolanaClient` / `solana.Client` | Borsh | + +Additional chain families may be added in future releases. ## Available Capabilities @@ -32,5 +43,6 @@ This section provides a high-level, conceptual overview of the capabilities curr - **[HTTP](/cre/capabilities/http)**: Fetch and post data from external APIs with decentralized consensus. - **[Confidential HTTP](/cre/capabilities/confidential-http)**: Make privacy-preserving API calls with enclave execution, secret injection, and optional response encryption. - **[EVM Read & Write](/cre/capabilities/evm-read-write)**: Interact with smart contracts on EVM-compatible blockchains with decentralized consensus. +- **[Solana Write](/cre/capabilities/solana-write)**: Write verified workflow results to Solana programs via the Keystone Forwarder. -All execution capabilities (HTTP, Confidential HTTP, EVM) automatically use [built-in consensus](/cre/concepts/consensus-computing) to validate results across multiple nodes, ensuring security and reliability. +All execution capabilities automatically use [built-in consensus](/cre/concepts/consensus-computing) to validate results across multiple nodes, ensuring security and reliability. diff --git a/src/content/cre/capabilities/solana-write.mdx b/src/content/cre/capabilities/solana-write.mdx new file mode 100644 index 00000000000..25586dd71b5 --- /dev/null +++ b/src/content/cre/capabilities/solana-write.mdx @@ -0,0 +1,75 @@ +--- +section: cre +title: "The Solana Write Capability" +date: Last Modified +metadata: + description: "CRE Solana Write capability: write verified workflow results to Solana programs with built-in DON consensus and cryptographic verification." + datePublished: "2026-07-06" + lastModified: "2026-07-06" +--- + +import { Aside } from "@components" + +The **Solana Write** capability lets your CRE workflow submit cryptographically signed reports to Solana programs. Your workflow code calls a single method; the underlying Decentralized Oracle Network (DON) handles consensus, report signing, and on-chain submission through the Keystone Forwarder program. + + + +## How it works + +The Solana Write capability shares the same conceptual model as [EVM Write](/cre/capabilities/evm-read-write): your workflow generates a signed report, and the DON submits it to a Keystone Forwarder program on-chain. The forwarder verifies the oracle signatures, then cross-program invokes (CPI) your receiver program's `on_report` instruction. + +### The secure write flow + +1. **Payload encoding**: Your workflow Borsh-encodes the data to deliver and wraps it in a `ForwarderReport` struct containing an account hash. +2. **Report generation**: `runtime.report()` signs the encoded payload using the `solana` encoder (`ecdsa` + `keccak256`). +3. **DON consensus**: Multiple DON nodes independently validate the report before any on-chain submission. +4. **Forwarder submission**: A DON node calls the Keystone Forwarder program with the signed report and the required accounts. +5. **Delivery to your program**: The forwarder verifies the signatures and CPIs into your receiver program's `on_report` instruction. + +### Account layout + +Every Solana write request includes a `remainingAccounts` list. The Keystone Forwarder expects accounts in the following order: + +| Index | Account | Writable | Description | +| ----- | -------------------- | --------- | ------------------------------------------------------------------------------------------------ | +| 0 | `forwarderState` | No | The Keystone Forwarder's on-chain state account | +| 1 | `forwarderAuthority` | No | PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | +| 2+ | Receiver accounts | As needed | Accounts your program's `on_report` instruction requires | + +The public keys of all accounts in this list are concatenated and SHA-256 hashed to produce the `accountHash` field in the report. The forwarder strips indices 0–1 before CPIing into your receiver, so they must be present and in the correct positions. + +### Key differences from EVM Write + +| Aspect | EVM Write | Solana Write | +| ------------------ | ---------------------------------------- | -------------------------------- | +| Payload encoding | ABI (viem) | Borsh | +| Receiver interface | `onReport(bytes metadata, bytes report)` | `on_report` Anchor instruction | +| Address format | 20-byte hex | 32-byte base58 / base64 | +| Report encoder | `evm` | `solana` | +| Consumer setup | Deploy a Solidity contract | Deploy an Anchor program | +| Compute budget | Gas limit | Compute units (default: 290,000) | + +## Key features + +- **DON consensus**: Reports are signed by multiple oracle nodes before submission. +- **Borsh encoding**: Payloads use Borsh, the standard serialization format for Solana programs. +- **Type-safe bindings**: The CLI binding generator creates typed `writeReportFrom()` helpers from your Anchor IDL. +- **Configurable compute budget**: Override the default 290,000 compute unit limit via `ComputeConfig`. +- **Chain selector support**: Target Solana Mainnet or Devnet using Chainlink's chain selector system. + + + +## Learn more + +- **[Solana Chain Interactions Guide](/cre/guides/workflow/using-solana-client/overview)**: Learn how to write data to Solana programs using the `SolanaClient` +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client)**: Detailed API reference for `SolanaClient` and helper functions +- **[Supported Networks](/cre/supported-networks)**: Chain selector values and minimum SDK versions for Solana networks diff --git a/src/content/cre/guides/workflow/using-solana-client/generating-bindings-go.mdx b/src/content/cre/guides/workflow/using-solana-client/generating-bindings-go.mdx new file mode 100644 index 00000000000..7ccfefdd6b2 --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/generating-bindings-go.mdx @@ -0,0 +1,207 @@ +--- +section: cre +title: "Generating Solana Bindings" +sdkLang: "go" +pageId: "guides-workflow-solana-generating-bindings" +date: Last Modified +metadata: + description: "Generate typed Go structs from Anchor IDL files to write data to Solana programs from CRE workflows." + datePublished: "2026-07-06" + lastModified: "2026-07-06" +--- + +import { Aside } from "@components" + +To interact with a Solana program from your Go workflow, you generate **bindings** from the program's [Anchor IDL](https://www.anchor-lang.com/docs/references/idl). Bindings are typed Go structs and methods that handle Borsh encoding, account hashing, and report submission automatically. + +For each Anchor struct that appears in an `on_report` instruction, the generator creates a `WriteReportFrom()` method. Your workflow calls this method with the struct data and the required accounts; the generated code handles everything else. + +{/* TODO: Confirm minimum CLI version once Solana binding generation is cut in a release */} + + + +## The generation process + +### Step 1: Add your Anchor IDL + +Place your program's Anchor IDL file in `contracts/solana/src/idl/`. The IDL is a JSON file that Anchor generates when you build your program: + +```bash +# In your Anchor project +anchor build +# IDL is written to: target/idl/.json +``` + +Copy it into your CRE project: + +```bash +mkdir -p contracts/solana/src/idl +cp /path/to/your-anchor-project/target/idl/my_program.json contracts/solana/src/idl/ +``` + +### Step 2: Generate the bindings + +From your **project root**, run: + +```bash +cre generate-bindings solana +``` + +This reads all `.json` IDL files in `contracts/solana/src/idl/` and generates Go files in `contracts/solana/src/generated/`. For each IDL, two files are generated: + +- `.go` — The main binding with structs and `WriteReportFrom()` methods +- `_mock.go` — A mock for testing without a live network + +### Step 3: Sync dependencies and import + +After generation, run `go mod tidy` to pull in any new dependencies, then import the generated package: + +```go +import mycreproject "github.com/your-org/my-cre-project/contracts/solana/src/generated/myprogram" +``` + +## What the generator creates + +For each Anchor type that appears in an `on_report` instruction, the generator creates: + +- A **Go struct** matching the Anchor struct fields with Borsh tags +- A **`WriteReportFrom(runtime, input, accounts, computeConfig) error`** method on the binding type that: + 1. Borsh-encodes `input` using `github.com/gagliardetto/binary` + 2. Calculates the account hash via `bindings.CalculateAccountsHash(accounts)` + 3. Encodes the forwarder report via `bindings.ForwarderReport{AccountHash: hash, Payload: encoded}.Marshal()` + 4. Calls `runtime.Report()` with the `solana` encoder to generate the signed report + 5. Calls `client.WriteReport()` to submit the report on-chain + +The binding struct holds a `*solana.Client` reference, which you supply at construction: + +```go +import ( + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + myprogram "github.com/your-org/my-project/contracts/solana/src/generated/myprogram" +) + +client := &solana.Client{ChainSelector: solana.SolanaMainnet} +program := myprogram.NewMyProgram(client, receiverProgramIDBytes) +``` + +## Example: Writing a price update + +Given an Anchor program with a `PriceData` struct, the generated binding exposes: + +```go +// contracts/solana/src/generated/priceconsumer/PriceConsumer.go (generated — do not edit) + +type PriceData struct { + Price uint64 + Timestamp int64 +} + +type PriceConsumer struct { + Client *solana.Client + ProgramID []byte +} + +func (c *PriceConsumer) WriteReportFromPriceData( + runtime cre.Runtime, + input PriceData, + accounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { ... } +``` + +Your workflow code calls the generated method: + +```go +// my-workflow/main.go + +package main + +import ( + "encoding/hex" + + "github.com/smartcontractkit/cre-sdk-go/cre" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + priceconsumer "github.com/your-org/my-project/contracts/solana/src/generated/priceconsumer" +) + +type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ReceiverProgramID string `json:"receiverProgramId"` + ForwarderState string `json:"forwarderState"` + ForwarderAuthority string `json:"forwarderAuthority"` + ReportState string `json:"reportState"` +} + +func workflow(runtime cre.Runtime) (string, error) { + config, err := cre.GetConfig[Config](runtime) + if err != nil { + return "", err + } + + client := &solana.Client{ChainSelector: config.ChainSelector} + + receiverID, _ := base58Decode(config.ReceiverProgramID) + program := priceconsumer.NewPriceConsumer(client, receiverID) + + // Build remaining accounts in the required order: + // Index 0: forwarderState (read-only) + // Index 1: forwarderAuthority (read-only) + // Index 2+: receiver-specific accounts + forwarderStateBytes, _ := base58Decode(config.ForwarderState) + forwarderAuthBytes, _ := base58Decode(config.ForwarderAuthority) + reportStateBytes, _ := base58Decode(config.ReportState) + + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStateBytes, IsWritable: false}, + {PublicKey: forwarderAuthBytes, IsWritable: false}, + {PublicKey: reportStateBytes, IsWritable: true}, + } + + result := program.WriteReportFromPriceData(runtime, priceconsumer.PriceData{ + Price: 500000, + Timestamp: time.Now().Unix(), + }, accounts, nil).Result() + + runtime.Log("Write succeeded", "txSignature", hex.EncodeToString(result.TxHash)) + return "ok", nil +} +``` + +See the full step-by-step walkthrough in the [Writing to Solana guide](/cre/guides/workflow/using-solana-client/onchain-write-go). + +## `accounts` layout + +Every Solana write requires an `[]*solana.AccountMeta` slice. The Keystone Forwarder program expects: + +| Index | Account | `IsWritable` | How to obtain | +| ----- | -------------------- | ----------------------- | ------------------------------------------------------------------------------------------------ | +| 0 | `forwarderState` | `false` | Provided by Chainlink for the forwarder deployment | +| 1 | `forwarderAuthority` | `false` | PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | +| 2+ | Receiver accounts | Depends on your program | Defined by your Anchor program's `on_report` instruction | + + + +## `solana.AccountMeta` fields + +| Field | Type | Description | +| ------------ | -------- | -------------------------------------------- | +| `PublicKey` | `[]byte` | 32-byte raw public key (decoded from base58) | +| `IsWritable` | `bool` | Whether the account is writable | + +## Best practices + +- **Regenerate when needed**: Re-run `cre generate-bindings solana` whenever your Anchor IDL changes. Do not edit generated files. +- **Store addresses in config**: Put program IDs, forwarder addresses, and account addresses in `config.staging.json` / `config.production.json`. +- **`chainSelector` as string in config**: Use `json:"chainSelector,string"` on your config struct to avoid precision loss when unmarshalling `uint64` values from JSON. + +## Where to go next + +- [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-go) — Step-by-step tutorial +- [Solana Client SDK Reference](/cre/reference/sdk/solana-client-go) — Full API reference for `solana.Client` and helper functions diff --git a/src/content/cre/guides/workflow/using-solana-client/generating-bindings-ts.mdx b/src/content/cre/guides/workflow/using-solana-client/generating-bindings-ts.mdx new file mode 100644 index 00000000000..74c08ea5a94 --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/generating-bindings-ts.mdx @@ -0,0 +1,212 @@ +--- +section: cre +title: "Generating Solana Bindings" +sdkLang: "ts" +pageId: "guides-workflow-solana-generating-bindings" +date: Last Modified +metadata: + description: "Generate typed TypeScript classes from Anchor IDL files to write data to Solana programs from CRE workflows." + datePublished: "2026-07-06" + lastModified: "2026-07-06" +--- + +import { Aside } from "@components" + +To interact with a Solana program from your TypeScript workflow, you generate **bindings** from the program's [Anchor IDL](https://www.anchor-lang.com/docs/references/idl). Bindings are typed TypeScript classes that handle Borsh encoding, account hashing, and report submission automatically. + +For each Anchor instruction that accepts a report, the generator creates a `writeReportFrom()` method on the binding class. Your workflow calls this method with the struct data and the required accounts; the generated code handles everything else. + +{/* TODO: Confirm minimum CLI version once Solana binding generation is cut in a release */} + + + +## The generation process + +### Step 1: Add your Anchor IDL + +Place your program's Anchor IDL file in `contracts/solana/src/idl/`. The IDL is a JSON file that Anchor generates when you build your program: + +```bash +# In your Anchor project +anchor build +# IDL is written to: target/idl/.json +``` + +Copy or symlink it into your CRE project: + +```bash +mkdir -p contracts/solana/src/idl +cp /path/to/your-anchor-project/target/idl/my_program.json contracts/solana/src/idl/ +``` + +### Step 2: Generate the bindings + +From your **project root**, run: + +```bash +cre generate-bindings solana +``` + +This reads all `.json` IDL files in `contracts/solana/src/idl/` and generates TypeScript files in `contracts/solana/ts/generated/`. For each IDL, three files are generated: + +- `.ts` — The typed binding class with `writeReportFrom()` methods +- `_mock.ts` — A mock for testing without a live network +- `index.ts` — A barrel re-exporting everything + +### Step 3: Import the generated class + +```typescript +import { MyProgram } from "../contracts/solana/ts/generated/MyProgram" +``` + +## What the generator creates + +For each Anchor type that appears in an `on_report` instruction, the generator creates: + +- A **TypeScript type** matching the Anchor struct fields +- A **codec** using `@solana/codecs` for Borsh encoding +- A **`writeReportFrom(runtime, input, remainingAccounts, computeConfig?)`** method on the binding class that: + 1. Borsh-encodes `input` using the generated codec + 2. Calculates the account hash via `calculateAccountsHash(remainingAccounts)` + 3. Encodes the forwarder report via `encodeForwarderReport({ accountHash, payload })` + 4. Calls `runtime.report()` with `prepareSolanaReportRequest(encodedForwarderReport)` to generate the signed report + 5. Calls `this.client.writeReport()` to submit the report on-chain + +The binding class constructor takes a `SolanaClient` and an optional program ID override: + +```typescript +// Program ID defaults to the address baked into the IDL +const myProgram = new MyProgram(client) + +// Or override with a different address (useful for testing) +const myProgram = new MyProgram(client, "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL") +``` + +## Example: Writing a price update + +Given an Anchor program with a `PriceData` struct and an `on_report` instruction that accepts it, the generated binding exposes: + +```typescript +// contracts/solana/ts/generated/PriceConsumer.ts (generated — do not edit) + +export type PriceData = { + price: bigint + timestamp: bigint +} + +export class PriceConsumer { + constructor(private readonly client: SolanaClient, programId?: string | Uint8Array) { ... } + + writeReportFromPriceData( + runtime: Runtime, + input: PriceData, + remainingAccounts: SolanaAccountMeta[], + computeConfig?: SolanaComputeConfig, + ) { ... } +} +``` + +Your workflow code calls the generated method directly: + +```typescript +// my-workflow/main.ts + +import { + SolanaClient, + handler, + CronCapability, + Runner, + type Runtime, + solanaAccountMeta, + solanaAddressToBytes, +} from "@chainlink/cre-sdk" +import { PriceConsumer } from "../contracts/solana/ts/generated/PriceConsumer" +import { PublicKey } from "@solana/web3.js" + +type Config = { + schedule: string + chainSelector: string + receiverProgramId: string + forwarderState: string + forwarderAuthority: string + reportState: string +} + +const onCronTrigger = (runtime: Runtime) => { + const config = runtime.config + + const client = new SolanaClient(BigInt(config.chainSelector)) + const priceConsumer = new PriceConsumer(client, config.receiverProgramId) + + // Build the remaining accounts list in the required order: + // Index 0: forwarderState (read-only) + // Index 1: forwarderAuthority (read-only) + // Index 2+: receiver-specific accounts + const remainingAccounts = [ + solanaAccountMeta(config.forwarderState, false), + solanaAccountMeta(config.forwarderAuthority, false), + solanaAccountMeta(config.reportState, true), + ] + + return priceConsumer.writeReportFromPriceData( + runtime, + { price: 500000n, timestamp: BigInt(Math.floor(Date.now() / 1000)) }, + remainingAccounts + ) +} + +export const initWorkflow = (config: Config) => { + const cron = new CronCapability() + return [handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)] +} + +export async function main() { + const runner = await Runner.newRunner() + await runner.run(initWorkflow) +} +``` + +See the full step-by-step walkthrough in the [Writing to Solana guide](/cre/guides/workflow/using-solana-client/onchain-write-ts). + +## `remainingAccounts` layout + +Every Solana write requires a `remainingAccounts` array. The Keystone Forwarder program expects: + +| Index | Account | Writable | How to obtain | +| ----- | -------------------- | ----------------------- | ------------------------------------------------------------------------------------------------ | +| 0 | `forwarderState` | No | Provided by Chainlink for the forwarder deployment | +| 1 | `forwarderAuthority` | No | PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | +| 2+ | Receiver accounts | Depends on your program | Defined by your Anchor program's `on_report` instruction | + +The `forwarderAuthority` PDA derivation in TypeScript using `@solana/addresses`: + +```typescript +import { getProgramDerivedAddress, getAddressEncoder } from "@solana/addresses" + +const [forwarderAuthority] = await getProgramDerivedAddress({ + programAddress: config.forwarderProgramId, + seeds: [ + Buffer.from("forwarder"), + getAddressEncoder().encode(config.forwarderState), + getAddressEncoder().encode(config.receiverProgramId), + ], +}) +``` + + + +## Best practices + +- **Regenerate when needed**: Re-run `cre generate-bindings solana` whenever your Anchor IDL changes. Do not edit generated files. +- **Store addresses in config**: Put program IDs, forwarder addresses, and account addresses in your `config.staging.json` / `config.production.json` rather than hard-coding them. +- **`chainSelector` as string in config**: Store the chain selector as a stringified number in your JSON config to avoid precision loss, then convert with `BigInt()` in workflow code. + +## Where to go next + +- [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-ts) — Step-by-step tutorial +- [Solana Client SDK Reference](/cre/reference/sdk/solana-client-ts) — Full API reference for `SolanaClient` and helper functions diff --git a/src/content/cre/guides/workflow/using-solana-client/onchain-write-go.mdx b/src/content/cre/guides/workflow/using-solana-client/onchain-write-go.mdx new file mode 100644 index 00000000000..47b0c46ade7 --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/onchain-write-go.mdx @@ -0,0 +1,294 @@ +--- +section: cre +title: "Writing to Solana" +sdkLang: "go" +pageId: "guides-workflow-solana-onchain-write" +date: Last Modified +metadata: + description: "Write verified CRE workflow results to Solana programs using Go: step-by-step guide with generated bindings and solana.Client." + datePublished: "2026-07-06" + lastModified: "2026-07-06" +--- + +import { Aside } from "@components" + +This guide walks through writing data from a CRE workflow to a Solana program using the Go SDK. By the end, you will have a working workflow that writes a Borsh-encoded struct to a Solana program via the Keystone Forwarder. + +## Prerequisites + +- A CRE Go project initialized with `cre init`. See [Part 1: Project Setup](/cre/getting-started/part-1-project-setup-go) if you are starting from scratch. +- An Anchor program deployed to Solana Mainnet or Devnet with an `on_report` instruction. See [What you need: a receiver program](#what-you-need-a-receiver-program) below. +- The following addresses from your Chainlink representative or via `cre workflow supported-chains`: + - Keystone Forwarder program ID + - Forwarder state account address + - Your program's report state account address + +## What you need: a receiver program + +Your Solana program must implement an `on_report` instruction that accepts the payload delivered by the Keystone Forwarder. The forwarder CPIs into your program after verifying the DON signatures. + +A minimal `on_report` instruction in Anchor: + +```rust +// programs/my-consumer/src/lib.rs + +#[program] +pub mod my_consumer { + use super::*; + + pub fn on_report(ctx: Context, _metadata: Vec, payload: Vec) -> Result<()> { + // Deserialize the Borsh-encoded payload delivered by the forwarder + let price_data: PriceData = PriceData::try_from_slice(&payload)?; + ctx.accounts.price_store.price = price_data.price; + ctx.accounts.price_store.timestamp = price_data.timestamp; + Ok(()) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct PriceData { + pub price: u64, + pub timestamp: i64, +} +``` + +{/* TODO: Link to a reference Anchor consumer program repository or deployed address once available */} + + + +## Step 1: Get your program's Anchor IDL + +After building your Anchor program, the IDL is at `target/idl/.json`. Copy it to your CRE project: + +1. Create the IDL directory. + + ```bash + mkdir -p contracts/solana/src/idl + ``` + +1. Copy your IDL file. + + ```bash + cp /path/to/anchor-project/target/idl/my_consumer.json contracts/solana/src/idl/ + ``` + +## Step 2: Generate Go bindings + +1. Generate the bindings from the IDL. + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/src/generated/myconsumer/MyConsumer.go` (and `MyConsumer_mock.go`). + +1. Verify the generated file exists. + + ```bash + ls contracts/solana/src/generated/myconsumer/ + # MyConsumer.go MyConsumer_mock.go + ``` + + The generated file contains a `PriceData` struct and a `WriteReportFromPriceData(runtime, input, accounts, computeConfig)` method. + +1. Sync dependencies. + + ```bash + go mod tidy + ``` + +## Step 3: Configure the workflow + +1. Open (or create) `config.staging.json` and add the Solana parameters. + + {/* TODO: Replace placeholder addresses with real Solana Mainnet / Devnet forwarder addresses once published */} + + ```json + { + "schedule": "0 */1 * * * *", + "chainSelector": "124615329519749607", + "receiverProgramId": "", + "forwarderProgramId": "", + "forwarderState": "", + "forwarderAuthority": "", + "reportState": "" + } + ``` + + | Field | Description | + | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | + | `chainSelector` | Chain selector for Solana Mainnet (`"124615329519749607"`) or Devnet (`"16423721717087811551"`). Store as a string to avoid precision loss. | + | `receiverProgramId` | Base58 program ID of your deployed Anchor program | + | `forwarderProgramId` | Base58 program ID of the Keystone Forwarder — provided by Chainlink | + | `forwarderState` | Base58 address of the forwarder's on-chain state account — provided by Chainlink | + | `forwarderAuthority` | Base58 address of the PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | + | `reportState` | Base58 address of the report state account required by your program | + + + +## Step 4: Write the workflow + +1. Save the following as `my-workflow/main.go`. + + ```go + // my-workflow/main.go + package main + + import ( + "time" + + "github.com/smartcontractkit/cre-sdk-go/cre" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + solanago "github.com/gagliardetto/solana-go" + myconsumer "github.com/your-org/my-project/contracts/solana/src/generated/myconsumer" + ) + + type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ReceiverProgramID string `json:"receiverProgramId"` + ForwarderState string `json:"forwarderState"` + ForwarderAuthority string `json:"forwarderAuthority"` + ReportState string `json:"reportState"` + } + + func workflow(runtime cre.Runtime) (string, error) { + config, err := cre.GetConfig[Config](runtime) + if err != nil { + return "", err + } + + client := &solana.Client{ChainSelector: config.ChainSelector} + + receiverPk := solanago.MustPublicKeyFromBase58(config.ReceiverProgramID) + program := myconsumer.NewMyConsumer(client, receiverPk.Bytes()) + + // Build remaining accounts in the required order. + // Index 0: forwarderState — read-only, consumed by the forwarder + // Index 1: forwarderAuthority — read-only PDA, consumed by the forwarder + // Index 2+: accounts your on_report instruction requires + forwarderStatePk := solanago.MustPublicKeyFromBase58(config.ForwarderState) + forwarderAuthPk := solanago.MustPublicKeyFromBase58(config.ForwarderAuthority) + reportStatePk := solanago.MustPublicKeyFromBase58(config.ReportState) + + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStatePk.Bytes(), IsWritable: false}, + {PublicKey: forwarderAuthPk.Bytes(), IsWritable: false}, + {PublicKey: reportStatePk.Bytes(), IsWritable: true}, + } + + runtime.Log("Submitting price update to Solana") + + result := program.WriteReportFromPriceData(runtime, myconsumer.PriceData{ + Price: 500000, + Timestamp: time.Now().Unix(), + }, accounts, nil).Result() + + runtime.Log("Write succeeded", "txSignature", result.TxHash) + return "ok", nil + } + + func main() { + cre.RunWorkflow(workflow) + } + ``` + +## Step 5: Run the simulation + +{/* TODO: Update when Solana devnet simulation target is fully supported — currently --target staging-solana-devnet throws "no RPC URLs found" */} + + + +1. Add a Solana RPC endpoint to your `project.yaml`. + + ```yaml + local-simulation: + rpcs: + - chain-name: solana-mainnet + url: https://your-solana-rpc-url + ``` + +1. Run the simulation. + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Expected output (dry run): + + ``` + Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] msg="Submitting price update to Solana" + [USER LOG] msg="Write succeeded" txSignature= + + Workflow Simulation Result: + "ok" + + [SIMULATION] Execution finished signal received + ``` + +1. To broadcast a real transaction, add `--broadcast`. + + ```bash + cre workflow simulate my-workflow --target staging-settings --broadcast + ``` + + A successful broadcast logs the transaction signature. You can verify it on [Solana Explorer](https://explorer.solana.com/) or [Solscan](https://solscan.io/). + +## Step 6: Deploy the workflow + +Once simulation succeeds, deploy to the CRE platform: + +```bash +cre workflow deploy my-workflow --target staging-settings +``` + +See [Deploying Workflows](/cre/guides/operations/deploying-workflows) for the full deployment process. + +## What happens on-chain + +When the workflow runs in production: + +1. The DON nodes each execute your workflow and reach consensus on the `PriceData` payload. +1. A DON node calls the Keystone Forwarder program with the signed report and account list. +1. The forwarder verifies all DON signatures. +1. The forwarder CPIs into your program's `on_report` instruction with the decoded payload and receiver accounts. + +The transaction on Solana Explorer will show the forwarder program as the initial callee, with your program appearing as a CPI target. + +## Next steps + +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client-go)**: Full API reference for `solana.Client` and helper functions +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go)**: Details on the binding generator and generated struct structure +- **[Solana Write Capability](/cre/capabilities/solana-write)**: Architecture and account layout reference diff --git a/src/content/cre/guides/workflow/using-solana-client/onchain-write-ts.mdx b/src/content/cre/guides/workflow/using-solana-client/onchain-write-ts.mdx new file mode 100644 index 00000000000..a84f2476b1b --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/onchain-write-ts.mdx @@ -0,0 +1,275 @@ +--- +section: cre +title: "Writing to Solana" +sdkLang: "ts" +pageId: "guides-workflow-solana-onchain-write" +date: Last Modified +metadata: + description: "Write verified CRE workflow results to Solana programs using TypeScript: step-by-step guide with generated bindings and the SolanaClient." + datePublished: "2026-07-06" + lastModified: "2026-07-06" +--- + +import { Aside } from "@components" + +This guide walks through writing data from a CRE workflow to a Solana program using the TypeScript SDK. By the end, you will have a working workflow that writes a Borsh-encoded struct to a Solana program via the Keystone Forwarder. + +## Prerequisites + +- A CRE project initialized with `cre init`. See [Part 1: Project Setup](/cre/getting-started/part-1-project-setup-ts) if you are starting from scratch. +- An Anchor program deployed to Solana Mainnet or Devnet with an `on_report` instruction. See [What you need: a receiver program](#what-you-need-a-receiver-program) below. +- The following addresses from your Chainlink representative or via `cre workflow supported-chains`: + - Keystone Forwarder program ID + - Forwarder state account address + - Your program's report state account address + +## What you need: a receiver program + +Your Solana program must implement an `on_report` instruction that accepts the payload delivered by the Keystone Forwarder. The forwarder CPIs into your program after verifying the DON signatures. + +A minimal `on_report` instruction in Anchor: + +```rust +// programs/my-consumer/src/lib.rs + +#[program] +pub mod my_consumer { + use super::*; + + pub fn on_report(ctx: Context, _metadata: Vec, payload: Vec) -> Result<()> { + // Deserialize the Borsh-encoded payload delivered by the forwarder + let price_data: PriceData = PriceData::try_from_slice(&payload)?; + ctx.accounts.price_store.price = price_data.price; + ctx.accounts.price_store.timestamp = price_data.timestamp; + Ok(()) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct PriceData { + pub price: u64, + pub timestamp: i64, +} +``` + +{/* TODO: Link to a reference Anchor consumer program repository or deployed address once available */} + + + +## Step 1: Get your program's Anchor IDL + +After building your Anchor program, the IDL is at `target/idl/.json`. Copy it to your CRE project: + +1. Create the IDL directory. + + ```bash + mkdir -p contracts/solana/src/idl + ``` + +1. Copy your IDL file. + + ```bash + cp /path/to/anchor-project/target/idl/my_consumer.json contracts/solana/src/idl/ + ``` + +## Step 2: Generate TypeScript bindings + +1. Generate the bindings from the IDL. + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/ts/generated/MyConsumer.ts` (and `MyConsumer_mock.ts` and `index.ts`). + +1. Verify the generated file exists. + + ```bash + ls contracts/solana/ts/generated/ + # MyConsumer.ts MyConsumer_mock.ts index.ts + ``` + + The generated `MyConsumer` class has a `writeReportFromPriceData(runtime, input, remainingAccounts)` method for the `PriceData` struct. + +## Step 3: Configure the workflow + +1. Open (or create) `config.staging.json` and add the Solana parameters. + + {/* TODO: Replace placeholder addresses with real Solana Mainnet / Devnet forwarder addresses once published */} + + ```json + { + "schedule": "0 */1 * * * *", + "chainSelector": "124615329519749607", + "receiverProgramId": "", + "forwarderProgramId": "", + "forwarderState": "", + "forwarderAuthority": "", + "reportState": "" + } + ``` + + | Field | Description | + | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | + | `chainSelector` | Chain selector for Solana Mainnet (`"124615329519749607"`) or Devnet (`"16423721717087811551"`). Store as a string to avoid JSON precision loss. | + | `receiverProgramId` | Base58 program ID of your deployed Anchor program | + | `forwarderProgramId` | Base58 program ID of the Keystone Forwarder — provided by Chainlink | + | `forwarderState` | Base58 address of the forwarder's on-chain state account — provided by Chainlink | + | `forwarderAuthority` | Base58 address of the PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | + | `reportState` | Base58 address of the report state account required by your program | + + + +## Step 4: Write the workflow + +1. Save the following as `my-workflow/main.ts`. + + ```typescript + // my-workflow/main.ts + import { SolanaClient, handler, CronCapability, Runner, type Runtime, solanaAccountMeta } from "@chainlink/cre-sdk" + import { MyConsumer } from "../contracts/solana/ts/generated/MyConsumer" + + type Config = { + schedule: string + chainSelector: string + receiverProgramId: string + forwarderState: string + forwarderAuthority: string + reportState: string + } + + const onCronTrigger = (runtime: Runtime) => { + const config = runtime.config + + const client = new SolanaClient(BigInt(config.chainSelector)) + const myConsumer = new MyConsumer(client, config.receiverProgramId) + + // Build remaining accounts in the required order. + // Index 0: forwarderState — read-only, consumed by the forwarder + // Index 1: forwarderAuthority — read-only PDA, consumed by the forwarder + // Index 2+: accounts your on_report instruction requires + const remainingAccounts = [ + solanaAccountMeta(config.forwarderState, false), + solanaAccountMeta(config.forwarderAuthority, false), + solanaAccountMeta(config.reportState, true), + ] + + runtime.log("Submitting price update to Solana") + + const result = myConsumer.writeReportFromPriceData( + runtime, + { + price: 500000n, + timestamp: BigInt(Math.floor(Date.now() / 1000)), + }, + remainingAccounts + ) + + runtime.log("Write succeeded", { txSignature: result.txHash }) + return result + } + + export const initWorkflow = (config: Config) => { + const cron = new CronCapability() + return [handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)] + } + + export async function main() { + const runner = await Runner.newRunner() + await runner.run(initWorkflow) + } + ``` + +## Step 5: Run the simulation + +{/* TODO: Update when Solana devnet simulation target is fully supported — currently --target staging-solana-devnet throws "no RPC URLs found" */} + + + +1. Add a Solana RPC endpoint to your `project.yaml`. + + ```yaml + local-simulation: + rpcs: + - chain-name: solana-mainnet + url: https://your-solana-rpc-url + ``` + +1. Run the simulation. + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Expected output (dry run): + + ``` + Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] Submitting price update to Solana + [USER LOG] Write succeeded txSignature= + + Workflow Simulation Result: + {} + + [SIMULATION] Execution finished signal received + ``` + +1. To broadcast a real transaction, add `--broadcast`. + + ```bash + cre workflow simulate my-workflow --target staging-settings --broadcast + ``` + + A successful broadcast logs the transaction signature. You can verify it on [Solana Explorer](https://explorer.solana.com/) or [Solscan](https://solscan.io/). + +## Step 6: Deploy the workflow + +Once simulation succeeds, deploy to the CRE platform: + +```bash +cre workflow deploy my-workflow --target staging-settings +``` + +See [Deploying Workflows](/cre/guides/operations/deploying-workflows) for the full deployment process. + +## What happens on-chain + +When the workflow runs in production: + +1. The DON nodes each execute your workflow and reach consensus on the `PriceData` payload. +1. A DON node calls the Keystone Forwarder program with the signed report and account list. +1. The forwarder verifies all DON signatures. +1. The forwarder CPIs into your program's `on_report` instruction with the decoded payload and receiver accounts. + +The transaction on Solana Explorer will show the forwarder program as the initial callee, with your program appearing as a CPI target. + +## Next steps + +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client-ts)**: Full API reference for `SolanaClient` and all helper functions +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-ts)**: Details on the binding generator and generated class structure +- **[Solana Write Capability](/cre/capabilities/solana-write)**: Architecture and account layout reference diff --git a/src/content/cre/guides/workflow/using-solana-client/overview-go.mdx b/src/content/cre/guides/workflow/using-solana-client/overview-go.mdx new file mode 100644 index 00000000000..64943570523 --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/overview-go.mdx @@ -0,0 +1,36 @@ +--- +section: cre +title: "Solana Chain Interactions" +sdkLang: "go" +pageId: "guides-workflow-solana-overview" +date: Last Modified +metadata: + description: "Write data to Solana programs from Go CRE workflows using solana.Client and Borsh-encoded reports." + datePublished: "2026-07-06" + lastModified: "2026-07-06" +--- + +import { Aside } from "@components" + +`solana.Client` is the Go SDK's interface for writing data to Solana programs. It submits cryptographically signed reports through the Keystone Forwarder program, which verifies DON signatures and CPIs into your receiver program. + + + +## How it works + +The Go SDK uses **generated Anchor bindings** for Solana program interactions. You provide your program's Anchor IDL, run the binding generator, and the SDK creates typed Go structs with `WriteReportFrom()` methods that handle Borsh encoding, account hashing, and report submission. + +Key aspects of the Go approach: + +- Anchor IDL-based code generation via `cre generate-bindings solana` +- `github.com/gagliardetto/binary` handles Borsh serialization inside generated bindings +- `bindings.CalculateAccountsHash` and `bindings.ForwarderReport` are the core primitives +- Account public keys are 32-byte slices; your workflow populates `[]*solana.AccountMeta` directly + +## Guides + +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go)**: Generate typed Go structs from your Anchor IDL using the CRE CLI +- **[Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-go)**: Step-by-step guide for submitting your first Solana write from a CRE workflow diff --git a/src/content/cre/guides/workflow/using-solana-client/overview-ts.mdx b/src/content/cre/guides/workflow/using-solana-client/overview-ts.mdx new file mode 100644 index 00000000000..698bde3151f --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/overview-ts.mdx @@ -0,0 +1,36 @@ +--- +section: cre +title: "Solana Chain Interactions" +sdkLang: "ts" +pageId: "guides-workflow-solana-overview" +date: Last Modified +metadata: + description: "Write data to Solana programs from TypeScript CRE workflows using SolanaClient and Borsh-encoded reports." + datePublished: "2026-07-06" + lastModified: "2026-07-06" +--- + +import { Aside } from "@components" + +`SolanaClient` is the TypeScript SDK's interface for writing data to Solana programs. It submits cryptographically signed reports through the Keystone Forwarder program, which verifies DON signatures and CPIs into your receiver program. + + + +## How it works + +The TypeScript SDK uses **generated Anchor bindings** for Solana program interactions. You provide your program's Anchor IDL, run the binding generator, and the SDK creates typed classes with `writeReportFrom()` methods that handle Borsh encoding, account hashing, and report submission. + +Key aspects of the TypeScript approach: + +- Anchor IDL-based code generation: no manual Borsh encoding in your workflow code +- `@solana/addresses` and `@solana/codecs` handle address and codec utilities inside generated bindings +- `SOLANA_DEFAULT_REPORT_ENCODER` configures the correct signing parameters automatically +- Account public keys are base64-encoded inside generated bindings; your workflow passes base58 strings via `solanaAccountMeta()` + +## Guides + +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-ts)**: Generate typed TypeScript classes from your Anchor IDL using the CRE CLI +- **[Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-ts)**: Step-by-step guide for submitting your first Solana write from a CRE workflow diff --git a/src/content/cre/llms-full-go.txt b/src/content/cre/llms-full-go.txt index 078ea88a771..7fa72487e45 100644 --- a/src/content/cre/llms-full-go.txt +++ b/src/content/cre/llms-full-go.txt @@ -8115,7 +8115,18 @@ Each capability is powered by its own independent Decentralized Oracle Network ( ## Invoking Capabilities via the SDK -As a developer, you do not interact with these capability DONs directly. Instead, you invoke them through the developer-friendly interfaces provided by the **CRE SDKs** ([Go](/cre/reference/sdk/core-go) and [TypeScript](/cre/reference/sdk/core-ts)), such as the [`evm.Client`](/cre/reference/sdk/evm-client) or the [`http.Client`](/cre/reference/sdk/http-client). The SDK handles the low-level complexity of communicating with the correct DON and processing the consensus-verified result, allowing you to focus on your business logic. +As a developer, you do not interact with these capability DONs directly. Instead, you invoke them through the developer-friendly interfaces provided by the **CRE SDKs** ([Go](/cre/reference/sdk/core-go) and [TypeScript](/cre/reference/sdk/core-ts)), such as the [`evm.Client`](/cre/reference/sdk/evm-client), the [`solana.Client`](/cre/reference/sdk/solana-client), or the [`http.Client`](/cre/reference/sdk/http-client). The SDK handles the low-level complexity of communicating with the correct DON and processing the consensus-verified result, allowing you to focus on your business logic. + +## Chain Families + +CRE supports blockchain capabilities across multiple **chain families** — groups of networks that share the same virtual machine, serialization format, and address encoding. The chain family determines which SDK client you use and how payloads are encoded. + +| Chain Family | Networks | SDK Client | Payload Encoding | +| ------------ | ------------------------------------------------------------------ | -------------------------------- | ------------------------ | +| EVM | Ethereum, Base, Arbitrum, and [many more](/cre/supported-networks) | `EVMClient` / `evm.Client` | ABI (viem / go-ethereum) | +| Solana | Solana Mainnet, Solana Devnet | `SolanaClient` / `solana.Client` | Borsh | + +Additional chain families may be added in future releases. ## Available Capabilities @@ -8125,8 +8136,9 @@ This section provides a high-level, conceptual overview of the capabilities curr - **[HTTP](/cre/capabilities/http)**: Fetch and post data from external APIs with decentralized consensus. - **[Confidential HTTP](/cre/capabilities/confidential-http)**: Make privacy-preserving API calls with enclave execution, secret injection, and optional response encryption. - **[EVM Read & Write](/cre/capabilities/evm-read-write)**: Interact with smart contracts on EVM-compatible blockchains with decentralized consensus. +- **[Solana Write](/cre/capabilities/solana-write)**: Write verified workflow results to Solana programs via the Keystone Forwarder. -All execution capabilities (HTTP, Confidential HTTP, EVM) automatically use [built-in consensus](/cre/concepts/consensus-computing) to validate results across multiple nodes, ensuring security and reliability. +All execution capabilities automatically use [built-in consensus](/cre/concepts/consensus-computing) to validate results across multiple nodes, ensuring security and reliability. --- @@ -10293,6 +10305,75 @@ Confidential HTTP takes a different approach: by design, only one request is eve --- +# The Solana Write Capability +Source: https://docs.chain.link/cre/capabilities/solana-write +Last Updated: 2026-07-06 + +The **Solana Write** capability lets your CRE workflow submit cryptographically signed reports to Solana programs. Your workflow code calls a single method; the underlying Decentralized Oracle Network (DON) handles consensus, report signing, and on-chain submission through the Keystone Forwarder program. + + + +## How it works + +The Solana Write capability shares the same conceptual model as [EVM Write](/cre/capabilities/evm-read-write): your workflow generates a signed report, and the DON submits it to a Keystone Forwarder program on-chain. The forwarder verifies the oracle signatures, then cross-program invokes (CPI) your receiver program's `on_report` instruction. + +### The secure write flow + +1. **Payload encoding**: Your workflow Borsh-encodes the data to deliver and wraps it in a `ForwarderReport` struct containing an account hash. +2. **Report generation**: `runtime.report()` signs the encoded payload using the `solana` encoder (`ecdsa` + `keccak256`). +3. **DON consensus**: Multiple DON nodes independently validate the report before any on-chain submission. +4. **Forwarder submission**: A DON node calls the Keystone Forwarder program with the signed report and the required accounts. +5. **Delivery to your program**: The forwarder verifies the signatures and CPIs into your receiver program's `on_report` instruction. + +### Account layout + +Every Solana write request includes a `remainingAccounts` list. The Keystone Forwarder expects accounts in the following order: + +| Index | Account | Writable | Description | +| ----- | -------------------- | --------- | ------------------------------------------------------------------------------------------------ | +| 0 | `forwarderState` | No | The Keystone Forwarder's on-chain state account | +| 1 | `forwarderAuthority` | No | PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | +| 2+ | Receiver accounts | As needed | Accounts your program's `on_report` instruction requires | + +The public keys of all accounts in this list are concatenated and SHA-256 hashed to produce the `accountHash` field in the report. The forwarder strips indices 0–1 before CPIing into your receiver, so they must be present and in the correct positions. + +### Key differences from EVM Write + +| Aspect | EVM Write | Solana Write | +| ------------------ | ---------------------------------------- | -------------------------------- | +| Payload encoding | ABI (viem) | Borsh | +| Receiver interface | `onReport(bytes metadata, bytes report)` | `on_report` Anchor instruction | +| Address format | 20-byte hex | 32-byte base58 / base64 | +| Report encoder | `evm` | `solana` | +| Consumer setup | Deploy a Solidity contract | Deploy an Anchor program | +| Compute budget | Gas limit | Compute units (default: 290,000) | + +## Key features + +- **DON consensus**: Reports are signed by multiple oracle nodes before submission. +- **Borsh encoding**: Payloads use Borsh, the standard serialization format for Solana programs. +- **Type-safe bindings**: The CLI binding generator creates typed `writeReportFrom()` helpers from your Anchor IDL. +- **Configurable compute budget**: Override the default 290,000 compute unit limit via `ComputeConfig`. +- **Chain selector support**: Target Solana Mainnet or Devnet using Chainlink's chain selector system. + + + +## Learn more + +- **[Solana Chain Interactions Guide](/cre/guides/workflow/using-solana-client/overview)**: Learn how to write data to Solana programs using the `SolanaClient` +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client)**: Detailed API reference for `SolanaClient` and helper functions +- **[Supported Networks](/cre/supported-networks)**: Chain selector values and minimum SDK versions for Solana networks + +--- + # Finality and Confidence Levels Source: https://docs.chain.link/cre/concepts/finality-go Last Updated: 2025-12-10 @@ -16814,6 +16895,520 @@ After a successful [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparserepo --- +# Generating Solana Bindings +Source: https://docs.chain.link/cre/guides/workflow/using-solana-client/generating-bindings-go +Last Updated: 2026-07-06 + +To interact with a Solana program from your Go workflow, you generate **bindings** from the program's [Anchor IDL](https://www.anchor-lang.com/docs/references/idl). Bindings are typed Go structs and methods that handle Borsh encoding, account hashing, and report submission automatically. + +For each Anchor struct that appears in an `on_report` instruction, the generator creates a `WriteReportFrom()` method. Your workflow calls this method with the struct data and the required accounts; the generated code handles everything else. + +{/* TODO: Confirm minimum CLI version once Solana binding generation is cut in a release */} + + + +## The generation process + +### Step 1: Add your Anchor IDL + +Place your program's Anchor IDL file in `contracts/solana/src/idl/`. The IDL is a JSON file that Anchor generates when you build your program: + +```bash +# In your Anchor project +anchor build +# IDL is written to: target/idl/.json +``` + +Copy it into your CRE project: + +```bash +mkdir -p contracts/solana/src/idl +cp /path/to/your-anchor-project/target/idl/my_program.json contracts/solana/src/idl/ +``` + +### Step 2: Generate the bindings + +From your **project root**, run: + +```bash +cre generate-bindings solana +``` + +This reads all `.json` IDL files in `contracts/solana/src/idl/` and generates Go files in `contracts/solana/src/generated/`. For each IDL, two files are generated: + +- `.go` — The main binding with structs and `WriteReportFrom()` methods +- `_mock.go` — A mock for testing without a live network + +### Step 3: Sync dependencies and import + +After generation, run `go mod tidy` to pull in any new dependencies, then import the generated package: + +```go +import mycreproject "github.com/your-org/my-cre-project/contracts/solana/src/generated/myprogram" +``` + +## What the generator creates + +For each Anchor type that appears in an `on_report` instruction, the generator creates: + +- A **Go struct** matching the Anchor struct fields with Borsh tags +- A **`WriteReportFrom(runtime, input, accounts, computeConfig) error`** method on the binding type that: + 1. Borsh-encodes `input` using `github.com/gagliardetto/binary` + 2. Calculates the account hash via `bindings.CalculateAccountsHash(accounts)` + 3. Encodes the forwarder report via `bindings.ForwarderReport{AccountHash: hash, Payload: encoded}.Marshal()` + 4. Calls `runtime.Report()` with the `solana` encoder to generate the signed report + 5. Calls `client.WriteReport()` to submit the report on-chain + +The binding struct holds a `*solana.Client` reference, which you supply at construction: + +```go +import ( + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + myprogram "github.com/your-org/my-project/contracts/solana/src/generated/myprogram" +) + +client := &solana.Client{ChainSelector: solana.SolanaMainnet} +program := myprogram.NewMyProgram(client, receiverProgramIDBytes) +``` + +## Example: Writing a price update + +Given an Anchor program with a `PriceData` struct, the generated binding exposes: + +```go +// contracts/solana/src/generated/priceconsumer/PriceConsumer.go (generated — do not edit) + +type PriceData struct { + Price uint64 + Timestamp int64 +} + +type PriceConsumer struct { + Client *solana.Client + ProgramID []byte +} + +func (c *PriceConsumer) WriteReportFromPriceData( + runtime cre.Runtime, + input PriceData, + accounts []*solana.AccountMeta, + computeConfig *solana.ComputeConfig, +) cre.Promise[*solana.WriteReportReply] { ... } +``` + +Your workflow code calls the generated method: + +```go +// my-workflow/main.go + +package main + +import ( + "encoding/hex" + + "github.com/smartcontractkit/cre-sdk-go/cre" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + priceconsumer "github.com/your-org/my-project/contracts/solana/src/generated/priceconsumer" +) + +type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ReceiverProgramID string `json:"receiverProgramId"` + ForwarderState string `json:"forwarderState"` + ForwarderAuthority string `json:"forwarderAuthority"` + ReportState string `json:"reportState"` +} + +func workflow(runtime cre.Runtime) (string, error) { + config, err := cre.GetConfig[Config](runtime) + if err != nil { + return "", err + } + + client := &solana.Client{ChainSelector: config.ChainSelector} + + receiverID, _ := base58Decode(config.ReceiverProgramID) + program := priceconsumer.NewPriceConsumer(client, receiverID) + + // Build remaining accounts in the required order: + // Index 0: forwarderState (read-only) + // Index 1: forwarderAuthority (read-only) + // Index 2+: receiver-specific accounts + forwarderStateBytes, _ := base58Decode(config.ForwarderState) + forwarderAuthBytes, _ := base58Decode(config.ForwarderAuthority) + reportStateBytes, _ := base58Decode(config.ReportState) + + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStateBytes, IsWritable: false}, + {PublicKey: forwarderAuthBytes, IsWritable: false}, + {PublicKey: reportStateBytes, IsWritable: true}, + } + + result := program.WriteReportFromPriceData(runtime, priceconsumer.PriceData{ + Price: 500000, + Timestamp: time.Now().Unix(), + }, accounts, nil).Result() + + runtime.Log("Write succeeded", "txSignature", hex.EncodeToString(result.TxHash)) + return "ok", nil +} +``` + +See the full step-by-step walkthrough in the [Writing to Solana guide](/cre/guides/workflow/using-solana-client/onchain-write-go). + +## `accounts` layout + +Every Solana write requires an `[]*solana.AccountMeta` slice. The Keystone Forwarder program expects: + +| Index | Account | `IsWritable` | How to obtain | +| ----- | -------------------- | ----------------------- | ------------------------------------------------------------------------------------------------ | +| 0 | `forwarderState` | `false` | Provided by Chainlink for the forwarder deployment | +| 1 | `forwarderAuthority` | `false` | PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | +| 2+ | Receiver accounts | Depends on your program | Defined by your Anchor program's `on_report` instruction | + + + +## `solana.AccountMeta` fields + +| Field | Type | Description | +| ------------ | -------- | -------------------------------------------- | +| `PublicKey` | `[]byte` | 32-byte raw public key (decoded from base58) | +| `IsWritable` | `bool` | Whether the account is writable | + +## Best practices + +- **Regenerate when needed**: Re-run `cre generate-bindings solana` whenever your Anchor IDL changes. Do not edit generated files. +- **Store addresses in config**: Put program IDs, forwarder addresses, and account addresses in `config.staging.json` / `config.production.json`. +- **`chainSelector` as string in config**: Use `json:"chainSelector,string"` on your config struct to avoid precision loss when unmarshalling `uint64` values from JSON. + +## Where to go next + +- [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-go) — Step-by-step tutorial +- [Solana Client SDK Reference](/cre/reference/sdk/solana-client-go) — Full API reference for `solana.Client` and helper functions + +--- + +# Writing to Solana +Source: https://docs.chain.link/cre/guides/workflow/using-solana-client/onchain-write-go +Last Updated: 2026-07-06 + +This guide walks through writing data from a CRE workflow to a Solana program using the Go SDK. By the end, you will have a working workflow that writes a Borsh-encoded struct to a Solana program via the Keystone Forwarder. + +## Prerequisites + +- A CRE Go project initialized with `cre init`. See [Part 1: Project Setup](/cre/getting-started/part-1-project-setup-go) if you are starting from scratch. +- An Anchor program deployed to Solana Mainnet or Devnet with an `on_report` instruction. See [What you need: a receiver program](#what-you-need-a-receiver-program) below. +- The following addresses from your Chainlink representative or via `cre workflow supported-chains`: + - Keystone Forwarder program ID + - Forwarder state account address + - Your program's report state account address + +## What you need: a receiver program + +Your Solana program must implement an `on_report` instruction that accepts the payload delivered by the Keystone Forwarder. The forwarder CPIs into your program after verifying the DON signatures. + +A minimal `on_report` instruction in Anchor: + +```rust +// programs/my-consumer/src/lib.rs + +#[program] +pub mod my_consumer { + use super::*; + + pub fn on_report(ctx: Context, _metadata: Vec, payload: Vec) -> Result<()> { + // Deserialize the Borsh-encoded payload delivered by the forwarder + let price_data: PriceData = PriceData::try_from_slice(&payload)?; + ctx.accounts.price_store.price = price_data.price; + ctx.accounts.price_store.timestamp = price_data.timestamp; + Ok(()) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct PriceData { + pub price: u64, + pub timestamp: i64, +} +``` + +{/* TODO: Link to a reference Anchor consumer program repository or deployed address once available */} + + + +## Step 1: Get your program's Anchor IDL + +After building your Anchor program, the IDL is at `target/idl/.json`. Copy it to your CRE project: + +1. Create the IDL directory. + + ```bash + mkdir -p contracts/solana/src/idl + ``` + +2. Copy your IDL file. + + ```bash + cp /path/to/anchor-project/target/idl/my_consumer.json contracts/solana/src/idl/ + ``` + +## Step 2: Generate Go bindings + +1. Generate the bindings from the IDL. + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/src/generated/myconsumer/MyConsumer.go` (and `MyConsumer_mock.go`). + +2. Verify the generated file exists. + + ```bash + ls contracts/solana/src/generated/myconsumer/ + # MyConsumer.go MyConsumer_mock.go + ``` + + The generated file contains a `PriceData` struct and a `WriteReportFromPriceData(runtime, input, accounts, computeConfig)` method. + +3. Sync dependencies. + + ```bash + go mod tidy + ``` + +## Step 3: Configure the workflow + +1. Open (or create) `config.staging.json` and add the Solana parameters. + + {/* TODO: Replace placeholder addresses with real Solana Mainnet / Devnet forwarder addresses once published */} + + ```json + { + "schedule": "0 */1 * * * *", + "chainSelector": "124615329519749607", + "receiverProgramId": "", + "forwarderProgramId": "", + "forwarderState": "", + "forwarderAuthority": "", + "reportState": "" + } + ``` + + | Field | Description | + | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | + | `chainSelector` | Chain selector for Solana Mainnet (`"124615329519749607"`) or Devnet (`"16423721717087811551"`). Store as a string to avoid precision loss. | + | `receiverProgramId` | Base58 program ID of your deployed Anchor program | + | `forwarderProgramId` | Base58 program ID of the Keystone Forwarder — provided by Chainlink | + | `forwarderState` | Base58 address of the forwarder's on-chain state account — provided by Chainlink | + | `forwarderAuthority` | Base58 address of the PDA derived from `["forwarder", forwarderState, receiverProgram]` under the forwarder program ID | + | `reportState` | Base58 address of the report state account required by your program | + + + +## Step 4: Write the workflow + +1. Save the following as `my-workflow/main.go`. + + ```go + // my-workflow/main.go + package main + + import ( + "time" + + "github.com/smartcontractkit/cre-sdk-go/cre" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + solanago "github.com/gagliardetto/solana-go" + myconsumer "github.com/your-org/my-project/contracts/solana/src/generated/myconsumer" + ) + + type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ReceiverProgramID string `json:"receiverProgramId"` + ForwarderState string `json:"forwarderState"` + ForwarderAuthority string `json:"forwarderAuthority"` + ReportState string `json:"reportState"` + } + + func workflow(runtime cre.Runtime) (string, error) { + config, err := cre.GetConfig[Config](runtime) + if err != nil { + return "", err + } + + client := &solana.Client{ChainSelector: config.ChainSelector} + + receiverPk := solanago.MustPublicKeyFromBase58(config.ReceiverProgramID) + program := myconsumer.NewMyConsumer(client, receiverPk.Bytes()) + + // Build remaining accounts in the required order. + // Index 0: forwarderState — read-only, consumed by the forwarder + // Index 1: forwarderAuthority — read-only PDA, consumed by the forwarder + // Index 2+: accounts your on_report instruction requires + forwarderStatePk := solanago.MustPublicKeyFromBase58(config.ForwarderState) + forwarderAuthPk := solanago.MustPublicKeyFromBase58(config.ForwarderAuthority) + reportStatePk := solanago.MustPublicKeyFromBase58(config.ReportState) + + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStatePk.Bytes(), IsWritable: false}, + {PublicKey: forwarderAuthPk.Bytes(), IsWritable: false}, + {PublicKey: reportStatePk.Bytes(), IsWritable: true}, + } + + runtime.Log("Submitting price update to Solana") + + result := program.WriteReportFromPriceData(runtime, myconsumer.PriceData{ + Price: 500000, + Timestamp: time.Now().Unix(), + }, accounts, nil).Result() + + runtime.Log("Write succeeded", "txSignature", result.TxHash) + return "ok", nil + } + + func main() { + cre.RunWorkflow(workflow) + } + ``` + +## Step 5: Run the simulation + +{/* TODO: Update when Solana devnet simulation target is fully supported — currently --target staging-solana-devnet throws "no RPC URLs found" */} + + + +1. Add a Solana RPC endpoint to your `project.yaml`. + + ```yaml + local-simulation: + rpcs: + - chain-name: solana-mainnet + url: https://your-solana-rpc-url + ``` + +2. Run the simulation. + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Expected output (dry run): + + ``` + Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] msg="Submitting price update to Solana" + [USER LOG] msg="Write succeeded" txSignature= + + Workflow Simulation Result: + "ok" + + [SIMULATION] Execution finished signal received + ``` + +3. To broadcast a real transaction, add `--broadcast`. + + ```bash + cre workflow simulate my-workflow --target staging-settings --broadcast + ``` + + A successful broadcast logs the transaction signature. You can verify it on [Solana Explorer](https://explorer.solana.com/) or [Solscan](https://solscan.io/). + +## Step 6: Deploy the workflow + +Once simulation succeeds, deploy to the CRE platform: + +```bash +cre workflow deploy my-workflow --target staging-settings +``` + +See [Deploying Workflows](/cre/guides/operations/deploying-workflows) for the full deployment process. + +## What happens on-chain + +When the workflow runs in production: + +1. The DON nodes each execute your workflow and reach consensus on the `PriceData` payload. +2. A DON node calls the Keystone Forwarder program with the signed report and account list. +3. The forwarder verifies all DON signatures. +4. The forwarder CPIs into your program's `on_report` instruction with the decoded payload and receiver accounts. + +The transaction on Solana Explorer will show the forwarder program as the initial callee, with your program appearing as a CPI target. + +## Next steps + +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client-go)**: Full API reference for `solana.Client` and helper functions +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go)**: Details on the binding generator and generated struct structure +- **[Solana Write Capability](/cre/capabilities/solana-write)**: Architecture and account layout reference + +--- + +# Solana Chain Interactions +Source: https://docs.chain.link/cre/guides/workflow/using-solana-client/overview-go +Last Updated: 2026-07-06 + +`solana.Client` is the Go SDK's interface for writing data to Solana programs. It submits cryptographically signed reports through the Keystone Forwarder program, which verifies DON signatures and CPIs into your receiver program. + + + +## How it works + +The Go SDK uses **generated Anchor bindings** for Solana program interactions. You provide your program's Anchor IDL, run the binding generator, and the SDK creates typed Go structs with `WriteReportFrom()` methods that handle Borsh encoding, account hashing, and report submission. + +Key aspects of the Go approach: + +- Anchor IDL-based code generation via `cre generate-bindings solana` +- `github.com/gagliardetto/binary` handles Borsh serialization inside generated bindings +- `bindings.CalculateAccountsHash` and `bindings.ForwarderReport` are the core primitives +- Account public keys are 32-byte slices; your workflow populates `[]*solana.AccountMeta` directly + +## Guides + +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go)**: Generate typed Go structs from your Anchor IDL using the CRE CLI +- **[Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-go)**: Step-by-step guide for submitting your first Solana write from a CRE workflow + +--- + # Cron Trigger Source: https://docs.chain.link/cre/guides/workflow/using-triggers/cron-trigger-go Last Updated: 2025-11-04 @@ -20441,6 +21036,253 @@ For interacting with smart contracts, use the [CRE CLI's binding generator](/cre --- +# SDK Reference: Solana Client +Source: https://docs.chain.link/cre/reference/sdk/solana-client-go +Last Updated: 2026-07-06 + +This page provides a reference for `solana.Client`, the Go SDK interface for writing data to Solana programs via the Keystone Forwarder. It covers the client type, the `bindings` package helpers, and the chain selector reference for Solana networks. + + + +## Import paths + +```go +import ( + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana/bindings" +) +``` + +## Client instantiation + +```go +import solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + +// Solana Mainnet using the package constant +client := &solana.Client{ChainSelector: solana.SolanaMainnet} + +// Solana Devnet +client := &solana.Client{ChainSelector: solana.SolanaDevnet} + +// Or use ChainSelectorFromName +selector, err := solana.ChainSelectorFromName("solana-mainnet") +client := &solana.Client{ChainSelector: selector} +``` + +### Chain selector constants + +| Constant | Value | +| ---------------------- | ---------------------- | +| `solana.SolanaMainnet` | `124615329519749607` | +| `solana.SolanaDevnet` | `16423721717087811551` | + +### `ChainSelectorFromName()` + +```go +func ChainSelectorFromName(name string) (uint64, error) +``` + +Accepts `"solana-mainnet"` or `"solana-devnet"`. Returns an error for unknown names. + +*** + +## Write methods + +### `Client.WriteReport()` + +Submits a cryptographically signed report to a Solana program via the Keystone Forwarder. In most workflows, you call generated binding methods (`WriteReportFrom()`) rather than this method directly. + +**Signature:** + +```go +func (c *Client) WriteReport(runtime cre.Runtime, input *WriteCreReportRequest) cre.Promise[*WriteReportReply] +``` + +#### `WriteCreReportRequest` + +| Field | Type | Description | +| ------------------- | ---------------- | ------------------------------------------------------- | +| `RemainingAccounts` | `[]*AccountMeta` | Ordered accounts required by the forwarder and receiver | +| `Receiver` | `[]byte` | 32-byte raw program ID of the receiver program | +| `ComputeConfig` | `*ComputeConfig` | Optional compute budget configuration | +| `Report` | `*cre.Report` | The signed report returned by `runtime.Report()` | + +#### `AccountMeta` + +| Field | Type | Description | +| ------------ | -------- | ------------------------------- | +| `PublicKey` | `[]byte` | 32-byte raw public key | +| `IsWritable` | `bool` | Whether the account is writable | + +#### `ComputeConfig` + +| Field | Type | Description | +| ------------------ | -------- | ------------------------------------------------------------- | +| `ComputeUnitLimit` | `uint32` | Maximum compute units for the transaction (default: `290000`) | + +#### `WriteReportReply` + +| Field | Type | Description | +| -------------- | ---------- | --------------------------------------------------------------- | +| `TxStatus` | `TxStatus` | `TX_STATUS_SUCCESS`, `TX_STATUS_REVERTED`, or `TX_STATUS_FATAL` | +| `TxHash` | `[]byte` | 64-byte transaction signature (optional) | +| `ErrorMessage` | `string` | Error message if the transaction failed (optional) | + +#### Usage example + +The low-level call, shown for reference. In most workflows, use a generated binding instead. + +```go +import ( + "encoding/base58" + "time" + + "github.com/smartcontractkit/cre-sdk-go/cre" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana/bindings" + binary "github.com/gagliardetto/binary" +) + +client := &solana.Client{ChainSelector: solana.SolanaMainnet} + +forwarderStateBytes, _ := base58.Decode("ForwarderStateBase58...") +forwarderAuthBytes, _ := base58.Decode("ForwarderAuthBase58...") +reportStateBytes, _ := base58.Decode("ReportStateBase58...") +receiverBytes, _ := base58.Decode("ReceiverProgramBase58...") + +accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStateBytes, IsWritable: false}, + {PublicKey: forwarderAuthBytes, IsWritable: false}, + {PublicKey: reportStateBytes, IsWritable: true}, +} + +// 1. Borsh-encode your payload (generated bindings do this automatically) +type PriceData struct { + Price uint64 + Timestamp int64 +} +var buf bytes.Buffer +enc := binary.NewBorshEncoder(&buf) +enc.Encode(PriceData{Price: 500000, Timestamp: time.Now().Unix()}) +payload := buf.Bytes() + +// 2. Compute the account hash and build the forwarder report +accountHash := bindings.CalculateAccountsHash(accounts) +forwarderReport := bindings.ForwarderReport{AccountHash: accountHash, Payload: payload} +encoded, err := forwarderReport.Marshal() + +// 3. Generate a signed report +report := runtime.Report(cre.ReportRequest{ + EncodedPayload: base64.StdEncoding.EncodeToString(encoded), + EncoderName: "solana", + SigningAlgo: "ecdsa", + HashingAlgo: "keccak256", +}).Result() + +// 4. Submit the report +result := client.WriteReport(runtime, &solana.WriteCreReportRequest{ + RemainingAccounts: accounts, + Receiver: receiverBytes, + Report: report, +}).Result() +``` + +*** + +## `bindings` package + +The `bindings` package (`github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana/bindings`) provides the serialization primitives used by generated binding code. You can also use them directly when writing low-level Solana write logic. + +### `ForwarderReport` + +Represents the Borsh-serialized report format expected by the Keystone Forwarder program. + +```go +type ForwarderReport struct { + AccountHash [32]byte + Payload []byte +} +``` + +#### `ForwarderReport.Marshal()` + +Serializes the report into Borsh bytes: `[32-byte accountHash][u32-LE payload length][payload]`. + +```go +func (obj ForwarderReport) Marshal() ([]byte, error) +``` + +**Usage:** + +```go +report := bindings.ForwarderReport{ + AccountHash: bindings.CalculateAccountsHash(accounts), + Payload: borshEncodedPayload, +} + +encoded, err := report.Marshal() +``` + +*** + +### `CalculateAccountsHash()` + +Computes the SHA-256 hash of the concatenated 32-byte public keys of the given accounts. This hash is embedded in the report payload and verified on-chain by the Keystone Forwarder. + +```go +func CalculateAccountsHash(accs []*solana.AccountMeta) [32]byte +``` + +`nil` entries in the slice are skipped. Account order matters: swapping two accounts produces a different hash. + +**Usage:** + +```go +accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStateBytes}, + {PublicKey: forwarderAuthBytes}, + {PublicKey: reportStateBytes}, +} + +hash := bindings.CalculateAccountsHash(accounts) +// → [32]byte +``` + +*** + +## Chain selectors + +| Network | String Name | Numeric ID | +| -------------- | -------------- | -------------------- | +| Solana Mainnet | solana-mainnet | 124615329519749607 | +| Solana Devnet | solana-devnet | 16423721717087811551 | + +In your workflow config (e.g., `config.staging.json`), store the numeric ID as a **string** to avoid precision loss, and use the `json:",string"` struct tag: + +```json +{ + "chainSelector": "124615329519749607" +} +``` + +```go +type Config struct { + ChainSelector uint64 `json:"chainSelector,string"` +} +``` + +## Related resources + +- **[Solana Write Capability](/cre/capabilities/solana-write)**: Architecture reference and account layout +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go)**: How to generate typed Go structs from your Anchor IDL +- **[Writing to Solana guide](/cre/guides/workflow/using-solana-client/onchain-write-go)**: Step-by-step tutorial +- **[Supported Networks](/cre/supported-networks-go)**: Chain selector values for all supported networks + +--- + # SDK Reference: Cron Trigger Source: https://docs.chain.link/cre/reference/sdk/triggers/cron-trigger-go Last Updated: 2025-11-04 @@ -20670,9 +21512,9 @@ This section provides a reference for the built-in trigger capabilities of the C # Supported Networks Source: https://docs.chain.link/cre/supported-networks-go -Last Updated: 2026-06-24 +Last Updated: 2026-07-06 -This page lists EVM-compatible networks supported by CRE workflows, along with the minimum CLI and SDK versions required for each network. +This page lists networks supported by CRE workflows, along with the minimum CLI and SDK versions required for each network. CRE supports two [chain families](/cre/capabilities#chain-families): EVM-compatible chains and Solana.