diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 075959ac8fb..39c00a14ae2 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -359,6 +359,32 @@ export const SIDEBAR: Partial> = { }, ], }, + { + title: "Solana Chain Interactions", + url: "cre/guides/workflow/using-solana-client/overview", + highlightAsCurrent: [ + "cre/guides/workflow/using-solana-client/overview-ts", + "cre/guides/workflow/using-solana-client/overview-go", + ], + children: [ + { + title: "Generating Bindings", + url: "cre/guides/workflow/using-solana-client/generating-bindings", + highlightAsCurrent: [ + "cre/guides/workflow/using-solana-client/generating-bindings-ts", + "cre/guides/workflow/using-solana-client/generating-bindings-go", + ], + }, + { + title: "Onchain Write", + url: "cre/guides/workflow/using-solana-client/onchain-write", + highlightAsCurrent: [ + "cre/guides/workflow/using-solana-client/onchain-write-ts", + "cre/guides/workflow/using-solana-client/onchain-write-go", + ], + }, + ], + }, { title: "API Interactions", url: "cre/guides/workflow/using-http-client", @@ -572,6 +598,7 @@ export const SIDEBAR: Partial> = { highlightAsCurrent: ["cre/capabilities/confidential-http-ts", "cre/capabilities/confidential-http-go"], }, { title: "EVM Read & Write", url: "cre/capabilities/evm-read-write" }, + { title: "Solana Write", url: "cre/capabilities/solana-write" }, ], }, { @@ -693,6 +720,11 @@ export const SIDEBAR: Partial> = { url: "cre/reference/sdk/evm-client", highlightAsCurrent: ["cre/reference/sdk/evm-client-ts", "cre/reference/sdk/evm-client-go"], }, + { + title: "Solana Client", + url: "cre/reference/sdk/solana-client", + highlightAsCurrent: ["cre/reference/sdk/solana-client-ts", "cre/reference/sdk/solana-client-go"], + }, { title: "HTTP Client", url: "cre/reference/sdk/http-client", 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..cd70ffc8b44 --- /dev/null +++ b/src/content/cre/capabilities/solana-write.mdx @@ -0,0 +1,56 @@ +--- +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-13" +--- + +import { Aside } from "@components" + +The **Solana Write** capability lets your CRE workflow submit cryptographically signed reports to Solana programs. Your workflow calls a single method while the underlying Decentralized Oracle Network (DON) handles consensus, report signing, and onchain 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 onchain. 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 onchain 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 onchain 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 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..69cab0842f2 --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/generating-bindings-go.mdx @@ -0,0 +1,318 @@ +--- +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-08" +--- + +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. + +The Solana CRE capability is write-only. For each Anchor struct in your IDL, 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. + + + + + +## The generation process + +The CRE CLI provides an automated binding generator that reads Anchor IDLs and creates corresponding Go packages. + +The target language is **auto-detected** from your project files (presence of `go.mod` picks Go). You can also force Go explicitly with the `--language` flag: + +```bash +cre generate-bindings solana --language go +``` + +Your project must already be a Go module (`go.mod` at the project root, as created by `cre init`). Without `go.mod`, the generator writes files but then fails when resolving Go dependencies. + +### 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 packages in `contracts/solana/src/generated/`. For each IDL, the generator creates a package directory (for example `data_storage/`) with the binding sources, including: + +- Struct types and `WriteReportFrom()` methods +- A mock for testing without a live network + +After generation, run `go mod tidy` if you need to sync new dependencies. + +## Using generated bindings + +### For onchain writes + +For each struct in your IDL's `types` section, the generator creates a `WriteReportFrom()` method that Borsh-encodes your data, builds the forwarder report, and submits it in one step. These methods return a `Promise`, which you must `.Await()` to get the result after consensus. + +**Example: A minimal `DataStorage` IDL** + +The following IDL is enough to generate a `WriteReportFromUserData()` helper. Save it as `contracts/solana/src/idl/data_storage.json`. You can run this example without deploying your own Anchor program: the program ID in the IDL is used as the write receiver, and simulation uses the Solana Devnet mock forwarder built into the CLI. + +```json +{ + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "data_storage", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Minimal CRE Solana write example IDL" + }, + "instructions": [ + { + "name": "on_report", + "discriminator": [214, 173, 18, 221, 173, 148, 151, 208], + "accounts": [], + "args": [ + { "name": "_metadata", "type": "bytes" }, + { "name": "payload", "type": "bytes" } + ] + } + ], + "accounts": [], + "events": [], + "errors": [], + "types": [ + { + "name": "UserData", + "type": { + "kind": "struct", + "fields": [ + { "name": "key", "type": "string" }, + { "name": "value", "type": "string" } + ] + } + } + ] +} +``` + +1. Generate the bindings from your project root. + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/src/generated/data_storage/` with a `UserData` struct and `WriteReportFromUserData(runtime, input, remainingAccounts, computeConfig)`. + +1. Sync dependencies. + + ```bash + go mod tidy + ``` + +1. Add Solana Devnet to `project.yaml` under your simulation target. + + ```yaml + staging-settings: + rpcs: + - chain-name: solana-devnet + url: https://api.devnet.solana.com + ``` + +1. Save the following as `config.staging.json` in your workflow folder. + + These addresses are the Solana Devnet **mock forwarder** values used by `cre workflow simulate`. The `forwarderAuthority` PDA is derived from `["forwarder", forwarderState, receiverProgram]` under the mock forwarder program ID for the `DataStorage` receiver above. `reportState` is a placeholder writable account for this dry-run example. + + ```json + { + "schedule": "*/30 * * * * *", + "chainSelector": "16423721717087811551", + "forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7", + "forwarderAuthority": "5wxb2QHeB4iyN1mPfmsjdsaxoP4P9EmTU7zBzpp4UgVu", + "reportState": "11111111111111111111111111111111" + } + ``` + +1. Save the following as `my-workflow/workflow.go`. + + A Go project is split across two files: `main.go` is the WASM entry point (generated by `cre init` and unchanged), and your workflow logic lives in a separate file such as `workflow.go`. Replace `` with your Go module name from `go.mod`. + + ```go + // my-workflow/workflow.go + package main + + import ( + "log/slog" + + solanago "github.com/gagliardetto/solana-go" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + "github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron" + "github.com/smartcontractkit/cre-sdk-go/cre" + + "/contracts/solana/src/generated/data_storage" + ) + + type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ForwarderState string `json:"forwarderState"` + ForwarderAuthority string `json:"forwarderAuthority"` + ReportState string `json:"reportState"` + } + + func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) { + cronTrigger := cron.Trigger(&cron.Config{Schedule: config.Schedule}) + + return cre.Workflow[*Config]{ + cre.Handler(cronTrigger, onCronTrigger), + }, nil + } + + func onCronTrigger(config *Config, runtime cre.Runtime, _ *cron.Payload) (string, error) { + client := &solana.Client{ChainSelector: config.ChainSelector} + + // NewDataStorage uses the program ID baked into the IDL + ds, err := data_storage.NewDataStorage(client) + if err != nil { + return "", err + } + + forwarderStatePk := solanago.MustPublicKeyFromBase58(config.ForwarderState) + forwarderAuthPk := solanago.MustPublicKeyFromBase58(config.ForwarderAuthority) + reportStatePk := solanago.MustPublicKeyFromBase58(config.ReportState) + + // remainingAccounts layout required by the forwarder: + // Index 0: forwarderState (read-only) + // Index 1: forwarderAuthority PDA (read-only) + // Index 2+: receiver-specific accounts + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStatePk[:], IsWritable: false}, + {PublicKey: forwarderAuthPk[:], IsWritable: false}, + {PublicKey: reportStatePk[:], IsWritable: true}, + } + + runtime.Logger().Info("Submitting UserData to Solana DataStorage program") + + // Solana simulate requires a non-nil ComputeConfig with ComputeLimit > 0 + computeConfig := &solana.ComputeConfig{ComputeLimit: 200_000} + + result, err := ds.WriteReportFromUserData(runtime, data_storage.UserData{ + Key: "price", + Value: "500000", + }, accounts, computeConfig).Await() + if err != nil { + return "", err + } + + runtime.Logger().Info("Write completed", + "txStatus", result.GetTxStatus().String(), + "errorMessage", result.GetErrorMessage(), + ) + return "ok", nil + } + ``` + +1. Set `CRE_SOLANA_PRIVATE_KEY` in your project `.env`, then run the simulation. + + Solana simulation requires a 64-byte base58 keypair even for dry-run (no `--broadcast`). The transmitter account derived from that key must exist and be funded on Solana Devnet. Generate a key with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and put the keypair in `.env`: + + ```bash + CRE_SOLANA_PRIVATE_KEY= + ``` + + Then from your project root: + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Expected output (dry-run against the mock forwarder). This example uses a placeholder receiver program ID, so the + write path completes through the mock forwarder and the CPI into the receiver returns `Custom:2007`. That confirms + bindings, encoding, and report submission worked: + + ``` + ✓ Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] msg="Submitting UserData to Solana DataStorage program" + [USER LOG] msg="Write completed" txStatus=TX_STATUS_FATAL errorMessage="map[InstructionError:[0 map[Custom:2007]]]" + + ✓ Workflow Simulation Result: + "ok" + ``` + + + +## What the CLI generates + +The generator creates a Go package for each IDL file in `contracts/solana/src/generated//`. + +What's inside depends on your IDL: + +- **For all programs**: + - A `` struct and `New(client)` constructor (program ID from the IDL). + - Codec helpers for encoding structs. +- **For onchain writes** (each struct in `types`): + - A `WriteReportFrom(runtime, input, remainingAccounts, computeConfig)` method that returns a `cre.Promise[*solana.WriteReportReply]` you `.Await()`. + - A `WriteReportFroms(...)` variant for Borsh `Vec` payloads. + +## `accounts` layout + +Every Solana write requires an `[]*solana.AccountMeta` slice. The Keystone Forwarder (and the Devnet mock forwarder used in simulation) expects: + +| Index | Account | `IsWritable` | How to obtain | +| ----- | -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------- | +| 0 | `forwarderState` | `false` | Provided by Chainlink for the forwarder deployment (mock values are built into the CLI for Devnet) | +| 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 | + +| Field | Type | Description | +| ------------ | -------- | -------------------------------------------- | +| `PublicKey` | `[]byte` | 32-byte raw public key (decoded from base58) | +| `IsWritable` | `bool` | Whether the account is writable | + + + +## Best practices + +1. **Regenerate when needed**: Re-run `cre generate-bindings solana` whenever your Anchor IDL changes. Do not edit generated files by hand. +1. **Always pass `ComputeConfig`**: Solana simulation rejects a nil or zero `ComputeLimit`. Pass `&solana.ComputeConfig{ComputeLimit: 200_000}` (or another positive limit within the simulator's CU cap). +1. **Handle errors**: Always check errors from constructors and `.Await()`. +1. **Store addresses in config**: Put forwarder and account addresses in `config.staging.json` / `config.production.json`. +1. **`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) — Full write walkthrough (deploy, broadcast, production forwarder addresses) +- [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..d4c61af9ac8 --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/generating-bindings-ts.mdx @@ -0,0 +1,299 @@ +--- +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-08" +--- + +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. + +The Solana CRE capability is write-only. For each Anchor struct in your IDL, 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. + + + +## The generation process + +The CRE CLI reads your IDL files and generates a typed class with the write helpers your workflow needs. + +The target language is **auto-detected** from your project files (presence of `package.json` picks TypeScript). You can also force TypeScript explicitly with the `--language` flag: + +```bash +cre generate-bindings solana --language typescript +``` + +### 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 + +## Using generated bindings + +### For onchain writes + +For each struct in your IDL's `types` section, the generator creates a `writeReportFrom()` method that Borsh-encodes your data, builds the forwarder report, and submits it in one step. + +**Example: A minimal `DataStorage` IDL** + +The following IDL is enough to generate a `writeReportFromUserData()` helper. Save it as `contracts/solana/src/idl/data_storage.json`. You can run this example without deploying your own Anchor program: the program ID in the IDL is used as the write receiver, and simulation uses the Solana Devnet mock forwarder built into the CLI. + +```json +{ + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "data_storage", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Minimal CRE Solana write example IDL" + }, + "instructions": [ + { + "name": "on_report", + "discriminator": [214, 173, 18, 221, 173, 148, 151, 208], + "accounts": [], + "args": [ + { "name": "_metadata", "type": "bytes" }, + { "name": "payload", "type": "bytes" } + ] + } + ], + "accounts": [], + "events": [], + "errors": [], + "types": [ + { + "name": "UserData", + "type": { + "kind": "struct", + "fields": [ + { "name": "key", "type": "string" }, + { "name": "value", "type": "string" } + ] + } + } + ] +} +``` + +1. Generate the bindings from your project root. + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/ts/generated/DataStorage.ts` (plus `DataStorage_mock.ts` and `index.ts`). The generated `DataStorage` class includes `writeReportFromUserData(runtime, input, remainingAccounts, computeConfig?)`. + +1. Add Solana Devnet to `project.yaml` under your simulation target. + + ```yaml + staging-settings: + rpcs: + - chain-name: solana-devnet + url: https://api.devnet.solana.com + ``` + +1. Save the following as `config.staging.json` in your workflow folder. + + These addresses are the Solana Devnet **mock forwarder** values used by `cre workflow simulate`. The `forwarderAuthority` PDA is derived from `["forwarder", forwarderState, receiverProgram]` under the mock forwarder program ID for the `DataStorage` receiver above. `reportState` is a placeholder writable account for this dry-run example. + + ```json + { + "schedule": "*/30 * * * * *", + "chainSelector": "16423721717087811551", + "forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7", + "forwarderAuthority": "5wxb2QHeB4iyN1mPfmsjdsaxoP4P9EmTU7zBzpp4UgVu", + "reportState": "11111111111111111111111111111111" + } + ``` + +1. Save the following as `my-workflow/main.ts`. + + ```typescript + // my-workflow/main.ts + import { CronCapability, handler, Runner, SolanaClient, solanaAccountMeta, type Runtime } from "@chainlink/cre-sdk" + import { DataStorage } from "../contracts/solana/ts/generated/DataStorage" + + type Config = { + schedule: string + chainSelector: string + forwarderState: string + forwarderAuthority: string + reportState: string + } + + const onCronTrigger = (runtime: Runtime) => { + const config = runtime.config + + const client = new SolanaClient(BigInt(config.chainSelector)) + // Program ID defaults to the address baked into the IDL + const ds = new DataStorage(client) + + // remainingAccounts layout required by the forwarder: + // Index 0: forwarderState (read-only) + // Index 1: forwarderAuthority PDA (read-only) + // Index 2+: receiver-specific accounts + const remainingAccounts = [ + solanaAccountMeta(config.forwarderState, false), + solanaAccountMeta(config.forwarderAuthority, false), + solanaAccountMeta(config.reportState, true), + ] + + runtime.log("Submitting UserData to Solana DataStorage program") + + // Solana simulate requires a non-nil computeConfig with computeLimit > 0 + const result = ds.writeReportFromUserData(runtime, { key: "price", value: "500000" }, remainingAccounts, { + computeLimit: 200_000, + }) + + runtime.log(`Write completed txStatus=${result.txStatus} errorMessage=${result.errorMessage ?? ""}`) + 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) + } + ``` + +1. Set `CRE_SOLANA_PRIVATE_KEY` in your project `.env`, then run the simulation. + + Solana simulation requires a 64-byte base58 keypair even for dry-run (no `--broadcast`). The transmitter account derived from that key must exist and be funded on Solana Devnet. Generate a key with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and put the keypair in `.env`: + + ```bash + CRE_SOLANA_PRIVATE_KEY= + ``` + + Then from your project root: + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Expected output (dry-run against the mock forwarder). This example uses a placeholder receiver program ID, so the + write path completes through the mock forwarder and the CPI into the receiver returns `Custom:2007`. That confirms + bindings, encoding, and report submission worked: + + ``` + ✓ Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] Submitting UserData to Solana DataStorage program + [USER LOG] Write completed txStatus=0 errorMessage=map[InstructionError:[0 map[Custom:2007]]] + + ✓ Workflow Simulation Result: + { + "$typeName": "capabilities.blockchain.solana.v1alpha.WriteReportReply", + "errorMessage": "map[InstructionError:[0 map[Custom:2007]]]", + "transactionFee": 7262, + "txStatus": 0 + } + ``` + + + +## What the CLI generates + +For each IDL file, the generator creates three files in `contracts/solana/ts/generated/`: + +- `.ts` — The main binding class +- `_mock.ts` — A mock implementation for testing +- `index.ts` — A barrel re-exporting everything + +What's inside depends on your IDL: + +- **For all programs**: + - A typed `_IDL` constant and `_PROGRAM_ID` from the IDL address. + - A `` class with a `writeReport(runtime, payload, remainingAccounts, computeConfig?)` base method. +- **For onchain writes** (each struct in `types`): + - A `writeReportFrom(runtime, input, remainingAccounts, computeConfig?)` method. + - A `writeReportFroms(...)` variant for Borsh `Vec` payloads. + +You can import from the barrel file to keep imports clean: + +```typescript +import { DataStorage } from "../contracts/solana/ts/generated" +``` + +## `remainingAccounts` layout + +Every Solana write requires a `remainingAccounts` array. The Keystone Forwarder (and the Devnet mock forwarder used in simulation) expects: + +| Index | Account | Writable | How to obtain | +| ----- | -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------- | +| 0 | `forwarderState` | No | Provided by Chainlink for the forwarder deployment (mock values are built into the CLI for Devnet) | +| 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, address, getAddressEncoder } from "@solana/addresses" + +const enc = getAddressEncoder() +const [forwarderAuthority] = await getProgramDerivedAddress({ + programAddress: address(forwarderProgramId), + seeds: [Buffer.from("forwarder"), enc.encode(address(forwarderState)), enc.encode(address(receiverProgramId))], +}) +``` + + + +## Best practices + +1. **Regenerate when needed**: Re-run `cre generate-bindings solana` whenever your Anchor IDL changes. Do not edit generated files by hand. +1. **Always pass `computeConfig`**: Solana simulation rejects a missing or zero `computeLimit`. Pass `{ computeLimit: 200_000 }` (or another positive limit within the simulator's CU cap). +1. **Store addresses in config**: Put forwarder and account addresses in `config.staging.json` / `config.production.json` rather than hard-coding them. +1. **`chainSelector` as string in config**: Store the chain selector as a stringified number in JSON to avoid precision loss, then convert with `BigInt()` in workflow code. +1. **Use explicit `--language` in CI**: If your project has both `go.mod` and `package.json`, pass `--language typescript` explicitly. + +## Where to go next + +- [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-ts) — Full write walkthrough (deploy, broadcast, production forwarder addresses) +- [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..c7204b68bda --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/onchain-write-go.mdx @@ -0,0 +1,395 @@ +--- +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 Anchor bindings." + datePublished: "2026-07-06" + lastModified: "2026-07-13" +--- + +import { Aside } from "@components" + +This guide walks through your first Solana write from a CRE workflow using the Go SDK. The preferred path is +**generated bindings**: you place your Anchor IDL in the project, run `cre generate-bindings solana`, and call a typed +`WriteReportFrom...` helper. The binding Borsh-encodes your payload, builds the forwarder report, and submits it — you +do not hand-roll encoding. + +Onchain, the DON does not call your program directly. It submits a signed report to the **Keystone Forwarder** — a +Chainlink-managed Solana program that verifies DON signatures, then cross-program invokes (CPI) your receiver's +`on_report`. That is the same forwarder model as [EVM Write](/cre/capabilities/evm-read-write); see +[Solana Write](/cre/capabilities/solana-write) for the full flow. Local simulation uses a Devnet stand-in of that +forwarder so you can dry-run without deploying. After you deploy, the live forwarder addresses apply. + +For IDL layout, generator output, and what each `WriteReportFrom...` method does under the hood, see +[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go). + +## 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. +- CRE CLI **v1.24.0 or later** (`cre version` / `cre update`) with Solana binding generation. +- A funded Solana Devnet keypair for `CRE_SOLANA_PRIVATE_KEY` (required even for dry-run). Generate one with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and put the 64-byte base58 keypair in your project `.env`. See [Step 4](#step-4-run-the-simulation). + +## What you need: a receiver program + +Your Solana program must implement an `on_report` instruction that accepts the payload delivered by the forwarder. The +forwarder CPIs into your program after verifying the DON signatures. Your Anchor IDL's `types` section defines the +struct(s) you will write; the generator turns each into a `WriteReportFrom()` method. + +This guide uses a minimal `DataStorage` / `UserData` IDL so you can simulate without deploying your own program. Swap in +your IDL and call the generated helpers for your structs when you are ready. + + + +## Step 1: Add your IDL and generate bindings + +1. Save the following as `contracts/solana/src/idl/data_storage.json` (or copy your own Anchor IDL into that folder). + + ```json + { + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "data_storage", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Minimal CRE Solana write example IDL" + }, + "instructions": [ + { + "name": "on_report", + "discriminator": [214, 173, 18, 221, 173, 148, 151, 208], + "accounts": [], + "args": [ + { "name": "_metadata", "type": "bytes" }, + { "name": "payload", "type": "bytes" } + ] + } + ], + "accounts": [], + "events": [], + "errors": [], + "types": [ + { + "name": "UserData", + "type": { + "kind": "struct", + "fields": [ + { "name": "key", "type": "string" }, + { "name": "value", "type": "string" } + ] + } + } + ] + } + ``` + +1. From your project root, generate the bindings: + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/src/generated/data_storage/` with a `UserData` struct and + `WriteReportFromUserData(runtime, input, remainingAccounts, computeConfig)`. + +1. Sync dependencies. + + ```bash + go mod tidy + ``` + +## Step 2: Configure the workflow + +1. Open (or create) `config.staging.json` and add the Solana Devnet simulation parameters. + + For `cre workflow simulate`, use these fixed Devnet forwarder program and state accounts (baked into the CLI). + `receiverProgramId` matches the IDL address above for this example; with your own program, use your Devnet program + ID. + + ```json + { + "schedule": "*/30 * * * * *", + "chainSelector": "16423721717087811551", + "receiverProgramId": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "forwarderProgramId": "7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK", + "forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7", + "reportState": "11111111111111111111111111111111", + "computeLimit": 200000 + } + ``` + + | Field | Description | + | -------------------- | ------------------------------------------------------------------------------------------- | + | `chainSelector` | Solana Devnet (`"16423721717087811551"`). Store as a string to avoid JSON precision loss. | + | `receiverProgramId` | Your receiver program (IDL address for this example) | + | `forwarderProgramId` | Forwarder program ID used for local simulation (fixed Devnet value) | + | `forwarderState` | Forwarder state account used for local simulation (fixed Devnet value) | + | `reportState` | Writable account required by your receiver (placeholder system program ID for this dry-run) | + | `computeLimit` | Solana compute units; must be greater than 0 for simulation | + + The workflow derives `forwarderAuthority` at runtime from + `[]byte("forwarder")`, forwarder state bytes, and receiver program bytes under `forwarderProgramId`. When you deploy + in [Step 5](#step-5-deploy-the-workflow), keep that derivation and point `forwarderProgramId` / `forwarderState` at + the live forwarder addresses for your network. + +## Step 3: Write the workflow + +This step is a complete, pasteable workflow that performs one Solana write on a cron trigger using **generated +bindings**. It reads config, derives the forwarder authority PDA, builds the account list the forwarder expects, and +calls `WriteReportFromUserData()`. Replace `data_storage` / `UserData` with your generated package and struct when you +use your own IDL. + +1. Save the following as `my-workflow/workflow.go`. + + A Go project is split across two files: `main.go` is the WASM entry point (generated by `cre init` and unchanged), and + your workflow logic lives in a separate file such as `workflow.go`. Replace `` with your Go module name + from `go.mod`. + + ```go + // my-workflow/workflow.go + package main + + import ( + "log/slog" + + solanago "github.com/gagliardetto/solana-go" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + "github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron" + "github.com/smartcontractkit/cre-sdk-go/cre" + + "/contracts/solana/src/generated/data_storage" + ) + + const defaultComputeLimit uint32 = 200_000 + + type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ReceiverProgramID string `json:"receiverProgramId"` + ForwarderProgramID string `json:"forwarderProgramId"` + ForwarderState string `json:"forwarderState"` + ReportState string `json:"reportState"` + ComputeLimit uint32 `json:"computeLimit"` + } + + func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) { + cronTrigger := cron.Trigger(&cron.Config{Schedule: config.Schedule}) + + return cre.Workflow[*Config]{ + cre.Handler(cronTrigger, onCronTrigger), + }, nil + } + + func deriveForwarderAuthority(forwarderState, receiverProgram, forwarderProgram solanago.PublicKey) (solanago.PublicKey, error) { + authority, _, err := solanago.FindProgramAddress( + [][]byte{ + []byte("forwarder"), + forwarderState[:], + receiverProgram[:], + }, + forwarderProgram, + ) + return authority, err + } + + func onCronTrigger(config *Config, runtime cre.Runtime, _ *cron.Payload) (string, error) { + client := &solana.Client{ChainSelector: config.ChainSelector} + + // NewDataStorage uses the program ID baked into the IDL (regenerate bindings when it changes). + ds, err := data_storage.NewDataStorage(client) + if err != nil { + return "", err + } + + forwarderStatePk := solanago.MustPublicKeyFromBase58(config.ForwarderState) + receiverPk := solanago.MustPublicKeyFromBase58(config.ReceiverProgramID) + forwarderProgramPk := solanago.MustPublicKeyFromBase58(config.ForwarderProgramID) + reportStatePk := solanago.MustPublicKeyFromBase58(config.ReportState) + + forwarderAuthority, err := deriveForwarderAuthority(forwarderStatePk, receiverPk, forwarderProgramPk) + if err != nil { + return "", err + } + + // Index 0 forwarderState, 1 forwarderAuthority (derived), 2+ reportState. + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStatePk[:], IsWritable: false}, + {PublicKey: forwarderAuthority[:], IsWritable: false}, + {PublicKey: reportStatePk[:], IsWritable: true}, + } + + payload := data_storage.UserData{ + Key: "price", + Value: "500000", + } + + runtime.Logger().Info("Submitting UserData via bindings", + "key", payload.Key, + "forwarderAuthority", forwarderAuthority.String(), + ) + + computeLimit := config.ComputeLimit + if computeLimit == 0 { + computeLimit = defaultComputeLimit + } + computeConfig := &solana.ComputeConfig{ComputeLimit: computeLimit} + + result, err := ds.WriteReportFromUserData(runtime, payload, accounts, computeConfig).Await() + if err != nil { + return "", err + } + + runtime.Logger().Info("Write completed", + "txStatus", result.GetTxStatus().String(), + "errorMessage", result.GetErrorMessage(), + "txSignature", result.GetTxSignature(), + ) + if msg := result.GetErrorMessage(); msg != "" { + return msg, nil + } + return "Success", nil + } + ``` + +## Step 4: Run the simulation + +Use this step to confirm compile, trigger, and the bindings write path with `cre workflow simulate`. + +1. Add a Solana RPC endpoint to your `project.yaml` under your simulation target. + + ```yaml + staging-settings: + rpcs: + - chain-name: solana-devnet + url: https://api.devnet.solana.com + ``` + +1. Set `CRE_SOLANA_PRIVATE_KEY` in your project `.env`. + + Solana simulation requires a 64-byte base58 keypair even for dry-run. The transmitter account must exist and be + funded on Solana Devnet. Generate a key with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and + put the keypair in `.env`: + + ```bash + CRE_SOLANA_PRIVATE_KEY= + ``` + +1. Run the dry-run simulation. + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Confirm the workflow compiles, the cron trigger runs, and your USER LOGs appear. This example uses a placeholder + receiver program ID, so the simulation write may complete with a receiver CPI error such as `Custom:2007`. That still + confirms bindings, encoding, and report submission worked: + + ``` + ✓ Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] msg="Submitting UserData via bindings" key=price forwarderAuthority=... + [USER LOG] msg="Write completed" txStatus=... errorMessage=... + + ✓ Workflow Simulation Result: + ... + ``` + + When you are ready to run on the DON with **your** receiver, continue to [Step 5](#step-5-deploy-the-workflow). + +## Step 5: Deploy the workflow + +A deployed workflow runs on the DON, which submits reports through the live Keystone Forwarder on Solana Devnet. Prefer +the [private registry](/cre/guides/operations/deploying-to-private-registry-go) for this example (CRE login session; no +Ethereum gas for registry ops). You need [Deploy Access](/cre/account/deploy-access). + +Use **your** receiver program ID and accounts — not the placeholder `DataStorage` address from the dry-run IDL unless +that program is actually deployed and ready to accept writes. Update the IDL address, regenerate bindings, and set +`receiverProgramId` to the same value (Go bindings use the program ID baked into the generated package for the write +receiver; config is used to derive the forwarder authority PDA). + +1. Create `config.production.json` with the live forwarder addresses for your network (same receiver and report state + your program expects). + + ```json + { + "schedule": "30 */5 * * * *", + "chainSelector": "16423721717087811551", + "receiverProgramId": "", + "forwarderProgramId": "", + "forwarderState": "", + "reportState": "", + "computeLimit": 290000 + } + ``` + + Get forwarder program and state addresses from your Chainlink representative. The workflow still derives + `forwarderAuthority` from `[]byte("forwarder")`, forwarder state bytes, and receiver program bytes under that + `forwarderProgramId`. + +1. Point a deploy target at that config and the private registry in `my-workflow/workflow.yaml`. + + ```yaml + production-settings: + user-workflow: + workflow-name: "my-solana-write" + deployment-registry: "private" + workflow-artifacts: + workflow-path: "./main.go" + config-path: "./config.production.json" + secrets-path: "" + ``` + +1. Add the Solana Devnet RPC under `production-settings` in `project.yaml` (same URL as staging is fine). + +1. Deploy from your project root. + + ```bash + cre workflow deploy my-workflow --target production-settings + ``` + + Expected shape: + + ``` + Deploying Workflow: my-solana-write + Registry: private + ... + ✓ Workflow registered in private registry + ... + Status: Active + ``` + +1. After the cron fires, open the workflow in the [CRE platform](https://app.chain.link/cre/workflows) (or run + `cre execution list my-solana-write`) and confirm node logs reach the write path: + + ``` + Submitting UserData via bindings key=... + ``` + + If WriteReport returns a Solana `txSignature`, confirm the transaction on + [Solana Explorer](https://explorer.solana.com/?cluster=devnet) (forwarder as the top-level program, your receiver as + a CPI). + +See [Deploying to the Private Registry](/cre/guides/operations/deploying-to-private-registry-go) for registry details, activate/pause, and CI flags. + +## What happens onchain + +When the deployed workflow runs on the DON: + +1. The DON nodes each execute your workflow and reach consensus on the encoded 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 + +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go)**: IDL layout, generator output, and what `WriteReportFrom...` does +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client-go)**: Full API reference for `solana.Client` and helpers +- **[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..57644f58f47 --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/onchain-write-ts.mdx @@ -0,0 +1,363 @@ +--- +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 Anchor bindings." + datePublished: "2026-07-06" + lastModified: "2026-07-13" +--- + +import { Aside } from "@components" + +This guide walks through your first Solana write from a CRE workflow using the TypeScript SDK. The preferred path is +**generated bindings**: you place your Anchor IDL in the project, run `cre generate-bindings solana`, and call a typed +`writeReportFrom...` helper. The binding Borsh-encodes your payload, builds the forwarder report, and submits it — you +do not hand-roll encoding. + +Onchain, the DON does not call your program directly. It submits a signed report to the **Keystone Forwarder** — a +Chainlink-managed Solana program that verifies DON signatures, then cross-program invokes (CPI) your receiver's +`on_report`. That is the same forwarder model as [EVM Write](/cre/capabilities/evm-read-write); see +[Solana Write](/cre/capabilities/solana-write) for the full flow. Local simulation uses a Devnet stand-in of that +forwarder so you can dry-run without deploying. After you deploy, the live forwarder addresses apply. + +For IDL layout, generator output, and what each `writeReportFrom...` method does under the hood, see +[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-ts). + +## 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. +- CRE CLI **v1.24.0 or later** (`cre version` / `cre update`) with TypeScript Solana binding generation. +- A funded Solana Devnet keypair for `CRE_SOLANA_PRIVATE_KEY` (required even for dry-run). Generate one with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and put the 64-byte base58 keypair in your project `.env`. See [Step 4](#step-4-run-the-simulation). + +## What you need: a receiver program + +Your Solana program must implement an `on_report` instruction that accepts the payload delivered by the forwarder. The +forwarder CPIs into your program after verifying the DON signatures. Your Anchor IDL's `types` section defines the +struct(s) you will write; the generator turns each into a `writeReportFrom()` method. + +This guide uses a minimal `DataStorage` / `UserData` IDL so you can simulate without deploying your own program. Swap in +your IDL and call the generated helpers for your structs when you are ready. + + + +## Step 1: Add your IDL and generate bindings + +1. Save the following as `contracts/solana/src/idl/data_storage.json` (or copy your own Anchor IDL into that folder). + + ```json + { + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "data_storage", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Minimal CRE Solana write example IDL" + }, + "instructions": [ + { + "name": "on_report", + "discriminator": [214, 173, 18, 221, 173, 148, 151, 208], + "accounts": [], + "args": [ + { "name": "_metadata", "type": "bytes" }, + { "name": "payload", "type": "bytes" } + ] + } + ], + "accounts": [], + "events": [], + "errors": [], + "types": [ + { + "name": "UserData", + "type": { + "kind": "struct", + "fields": [ + { "name": "key", "type": "string" }, + { "name": "value", "type": "string" } + ] + } + } + ] + } + ``` + +1. From your project root, generate the bindings: + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/ts/generated/DataStorage.ts` (plus a mock and `index.ts`). The `DataStorage` class + includes `writeReportFromUserData(runtime, input, remainingAccounts, computeConfig?)`. + +## Step 2: Configure the workflow + +1. Open (or create) `config.staging.json` and add the Solana Devnet simulation parameters. + + For `cre workflow simulate`, use these fixed Devnet forwarder program and state accounts (baked into the CLI). + `receiverProgramId` matches the IDL address above for this example; with your own program, use your Devnet program + ID. + + ```json + { + "schedule": "*/30 * * * * *", + "chainSelector": "16423721717087811551", + "receiverProgramId": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "forwarderProgramId": "7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK", + "forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7", + "reportState": "11111111111111111111111111111111", + "computeLimit": 200000 + } + ``` + + | Field | Description | + | -------------------- | ------------------------------------------------------------------------------------------- | + | `chainSelector` | Solana Devnet (`"16423721717087811551"`). Store as a string to avoid JSON precision loss. | + | `receiverProgramId` | Your receiver program (IDL address for this example) | + | `forwarderProgramId` | Forwarder program ID used for local simulation (fixed Devnet value) | + | `forwarderState` | Forwarder state account used for local simulation (fixed Devnet value) | + | `reportState` | Writable account required by your receiver (placeholder system program ID for this dry-run) | + | `computeLimit` | Solana compute units; must be greater than 0 for simulation | + + The workflow derives `forwarderAuthority` at runtime from + `["forwarder", forwarderState, receiverProgram]` under `forwarderProgramId`. When you deploy in [Step 5](#step-5-deploy-the-workflow), + keep that derivation and point `forwarderProgramId` / `forwarderState` at the live forwarder addresses for your + network. + +## Step 3: Write the workflow + +This step is a complete, pasteable workflow that performs one Solana write on a cron trigger using **generated +bindings**. It reads config, derives the forwarder authority PDA, builds the `remainingAccounts` list the forwarder +expects, and calls `writeReportFromUserData()`. Replace `DataStorage` / `UserData` with your generated class and struct +when you use your own IDL. + +1. Save the following as `my-workflow/main.ts`. + + ```typescript + // my-workflow/main.ts + import { + CronCapability, + type CronPayload, + handler, + Runner, + type Runtime, + SolanaClient, + solanaAccountMeta, + } from "@chainlink/cre-sdk" + import { PublicKey } from "@solana/web3.js" + import { DataStorage, type UserData } from "../contracts/solana/ts/generated/DataStorage" + + type Config = { + schedule: string + chainSelector: string + receiverProgramId: string + forwarderProgramId: string + forwarderState: string + reportState: string + computeLimit?: number + } + + const DEFAULT_COMPUTE_LIMIT = 200_000 + const FORWARDER_SEED = Uint8Array.from("forwarder", (c) => c.charCodeAt(0)) + + function deriveForwarderAuthority( + forwarderState: PublicKey, + receiverProgram: PublicKey, + forwarderProgram: PublicKey + ): PublicKey { + const [authority] = PublicKey.findProgramAddressSync( + [FORWARDER_SEED, forwarderState.toBytes(), receiverProgram.toBytes()], + forwarderProgram + ) + return authority + } + + const onCronTrigger = (runtime: Runtime, _trigger: CronPayload): string => { + const cfg = runtime.config + const client = new SolanaClient(BigInt(cfg.chainSelector)) + // Program ID defaults to the address baked into the IDL; pass cfg.receiverProgramId to override. + const receiver = new DataStorage(client, cfg.receiverProgramId) + + const forwarderState = new PublicKey(cfg.forwarderState) + const receiverProgram = new PublicKey(cfg.receiverProgramId) + const forwarderProgram = new PublicKey(cfg.forwarderProgramId) + const reportState = new PublicKey(cfg.reportState) + const forwarderAuthority = deriveForwarderAuthority(forwarderState, receiverProgram, forwarderProgram) + + // Index 0 forwarderState, 1 forwarderAuthority (derived), 2+ reportState. + const remainingAccounts = [ + solanaAccountMeta(forwarderState.toBase58(), false), + solanaAccountMeta(forwarderAuthority.toBase58(), false), + solanaAccountMeta(reportState.toBase58(), true), + ] + + const payload: UserData = { key: "price", value: "500000" } + + runtime.log( + `Submitting UserData via bindings key=${payload.key} forwarderAuthority=${forwarderAuthority.toBase58()}` + ) + + const computeLimit = cfg.computeLimit ?? DEFAULT_COMPUTE_LIMIT + const out = receiver.writeReportFromUserData(runtime, payload, remainingAccounts, { computeLimit }) + + runtime.log( + `Write completed txStatus=${out.txStatus} errorMessage=${out.errorMessage ?? ""} txSignature=${out.txSignature ?? ""}` + ) + if (out.errorMessage && out.errorMessage.length > 0) { + return out.errorMessage + } + return "Success" + } + + 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 4: Run the simulation + +Use this step to confirm compile, trigger, and the bindings write path with `cre workflow simulate`. + +1. Add a Solana RPC endpoint to your `project.yaml` under your simulation target. + + ```yaml + staging-settings: + rpcs: + - chain-name: solana-devnet + url: https://api.devnet.solana.com + ``` + +1. Set `CRE_SOLANA_PRIVATE_KEY` in your project `.env`. + + Solana simulation requires a 64-byte base58 keypair even for dry-run. The transmitter account must exist and be + funded on Solana Devnet. Generate a key with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and + put the keypair in `.env`: + + ```bash + CRE_SOLANA_PRIVATE_KEY= + ``` + +1. Run the dry-run simulation. + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Confirm the workflow compiles, the cron trigger runs, and your USER LOGs appear. This example uses a placeholder + receiver program ID, so the simulation write may complete with a receiver CPI error such as `Custom:2007`. That still + confirms bindings, encoding, and report submission worked: + + ``` + ✓ Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] Submitting UserData via bindings key=price forwarderAuthority=... + [USER LOG] Write completed txStatus=... errorMessage=... + + ✓ Workflow Simulation Result: + ... + ``` + + When you are ready to run on the DON with **your** receiver, continue to [Step 5](#step-5-deploy-the-workflow). + +## Step 5: Deploy the workflow + +A deployed workflow runs on the DON, which submits reports through the live Keystone Forwarder on Solana Devnet. Prefer +the [private registry](/cre/guides/operations/deploying-to-private-registry-ts) for this example (CRE login session; no +Ethereum gas for registry ops). You need [Deploy Access](/cre/account/deploy-access). + +Use **your** receiver program ID and accounts — not the placeholder `DataStorage` address from the dry-run IDL unless +that program is actually deployed and ready to accept writes. + +1. Create `config.production.json` with the live forwarder addresses for your network (same receiver and report state + your program expects). + + ```json + { + "schedule": "30 */5 * * * *", + "chainSelector": "16423721717087811551", + "receiverProgramId": "", + "forwarderProgramId": "", + "forwarderState": "", + "reportState": "", + "computeLimit": 290000 + } + ``` + + Get forwarder program and state addresses from your Chainlink representative. The workflow still derives + `forwarderAuthority` from `["forwarder", forwarderState, receiverProgram]` under that `forwarderProgramId`. + +1. Point a deploy target at that config and the private registry in `my-workflow/workflow.yaml`. + + ```yaml + production-settings: + user-workflow: + workflow-name: "my-solana-write" + deployment-registry: "private" + workflow-artifacts: + workflow-path: "./main.ts" + config-path: "./config.production.json" + secrets-path: "" + ``` + +1. Add the Solana Devnet RPC under `production-settings` in `project.yaml` (same URL as staging is fine). + +1. Deploy from your project root. + + ```bash + cre workflow deploy my-workflow --target production-settings + ``` + + Expected shape: + + ``` + Deploying Workflow: my-solana-write + Registry: private + ... + ✓ Workflow registered in private registry + ... + Status: Active + ``` + +1. After the cron fires, open the workflow in the [CRE platform](https://app.chain.link/cre/workflows) (or run + `cre execution list my-solana-write`) and confirm node logs reach the write path: + + ``` + Submitting UserData via bindings key=... + ``` + + If WriteReport returns a Solana `txSignature`, confirm the transaction on + [Solana Explorer](https://explorer.solana.com/?cluster=devnet) (forwarder as the top-level program, your receiver as + a CPI). + +See [Deploying to the Private Registry](/cre/guides/operations/deploying-to-private-registry-ts) for registry details, activate/pause, and CI flags. + +## What happens onchain + +When the deployed workflow runs on the DON: + +1. The DON nodes each execute your workflow and reach consensus on the encoded 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 + +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-ts)**: IDL layout, generator output, and what `writeReportFrom...` does +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client-ts)**: Full API reference for `SolanaClient` and helpers +- **[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..0e69f2f55bd --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/overview-go.mdx @@ -0,0 +1,35 @@ +--- +section: cre +title: "Solana Chain Interactions" +sdkLang: "go" +pageId: "guides-workflow-solana-overview" +date: Last Modified +metadata: + description: "Interact with Solana from Go CRE workflows using solana.Client and generated Anchor bindings." + datePublished: "2026-07-06" + lastModified: "2026-07-13" +--- + +import { Aside } from "@components" + +`solana.Client` is the Go SDK's interface for Solana chain interactions from CRE workflows. Writes submit cryptographically signed reports through the Keystone Forwarder program, which verifies DON signatures and CPIs into your receiver program. + + + +## How it works + +For Solana program interactions, CRE can **generate typed bindings** from your program's [Anchor IDL](https://www.anchor-lang.com/docs/references/idl). You place the IDL in your project, run `cre generate-bindings solana`, and the CLI creates Go packages with helpers such as `WriteReportFrom()` that handle encoding, account hashing, and report submission for you. + +Typical flow: + +1. Add your Anchor IDL under `contracts/solana/src/idl/` +1. Run `cre generate-bindings solana` from your project root +1. Call the generated helpers from your workflow + +## 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)**: End-to-end first write with generated bindings, simulate, and deploy 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..5502999d74a --- /dev/null +++ b/src/content/cre/guides/workflow/using-solana-client/overview-ts.mdx @@ -0,0 +1,35 @@ +--- +section: cre +title: "Solana Chain Interactions" +sdkLang: "ts" +pageId: "guides-workflow-solana-overview" +date: Last Modified +metadata: + description: "Interact with Solana from TypeScript CRE workflows using SolanaClient and generated Anchor bindings." + datePublished: "2026-07-06" + lastModified: "2026-07-13" +--- + +import { Aside } from "@components" + +`SolanaClient` is the TypeScript SDK's interface for Solana chain interactions from CRE workflows. Writes submit cryptographically signed reports through the Keystone Forwarder program, which verifies DON signatures and CPIs into your receiver program. + + + +## How it works + +For Solana program interactions, CRE can **generate typed bindings** from your program's [Anchor IDL](https://www.anchor-lang.com/docs/references/idl). You place the IDL in your project, run `cre generate-bindings solana`, and the CLI creates TypeScript classes with helpers such as `writeReportFrom()` that handle encoding, account hashing, and report submission for you. + +Typical flow: + +1. Add your Anchor IDL under `contracts/solana/src/idl/` +1. Run `cre generate-bindings solana` from your project root +1. Call the generated helpers from your workflow + +## 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)**: End-to-end first write with generated bindings, simulate, and deploy diff --git a/src/content/cre/llms-full-go.txt b/src/content/cre/llms-full-go.txt index 0da6f11e933..e2a03204f2c 100644 --- a/src/content/cre/llms-full-go.txt +++ b/src/content/cre/llms-full-go.txt @@ -524,7 +524,7 @@ This page provides detailed release notes for CRE. It includes information on ne - **Execution observability from the CLI**: New [`cre execution`](/cre/reference/cli/execution) commands let you inspect deployed workflow runs without opening the UI — `list` for recent executions (filter by status and time range), `status` for a single run, `events` for the capability timeline, and `logs` for emitted log lines. All support `--output json` for scripting. - **Enhanced `cre workflow get`**: [`cre workflow get`](/cre/reference/cli/workflow#cre-workflow-get) now resolves the workflow from your `workflow.yaml` settings and shows deployment health plus the most recent execution, not just registry metadata. -- **Solana Write capability**: Workflows can now submit DON-consensus signed reports to Solana programs on Mainnet and Devnet via the Keystone Forwarder — the same secure write model as EVM. The CLI adds the tooling to build and test these workflows: `CRE_SOLANA_PRIVATE_KEY` for signing, `cre generate-bindings solana` for Go and TypeScript bindings from Anchor IDLs, and local write simulation with `cre workflow simulate` (dry run or `--broadcast`). +- **Solana Write capability**: Workflows can now submit DON-consensus signed reports to Solana programs on Mainnet and Devnet via the Keystone Forwarder — the same secure write model as EVM. See the [Solana Write capability](/cre/capabilities/solana-write), [Solana chain interactions](/cre/guides/workflow/using-solana-client/overview), [generating Solana bindings](/cre/guides/workflow/using-solana-client/generating-bindings) (`cre generate-bindings solana`), and [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write). Local write simulation uses `cre workflow simulate` with `CRE_SOLANA_PRIVATE_KEY`; chain selectors are listed under [Supported Networks](/cre/supported-networks). **How to update:** @@ -8130,7 +8130,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 @@ -8140,8 +8151,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. --- @@ -8255,6 +8267,57 @@ Learn more in the [Onchain Write guide](/cre/guides/workflow/using-evm-client/on --- +# The Solana Write Capability +Source: https://docs.chain.link/cre/capabilities/solana-write +Last Updated: 2026-07-13 + +The **Solana Write** capability lets your CRE workflow submit cryptographically signed reports to Solana programs. Your workflow calls a single method while the underlying Decentralized Oracle Network (DON) handles consensus, report signing, and onchain 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 onchain. 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 onchain 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 onchain 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 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 + +--- + # Consensus Computing in CRE Source: https://docs.chain.link/cre/concepts/consensus-computing Last Updated: 2025-11-04 @@ -17006,6 +17069,733 @@ 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-08 + +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. + +The Solana CRE capability is write-only. For each Anchor struct in your IDL, 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. + + + + + +## The generation process + +The CRE CLI provides an automated binding generator that reads Anchor IDLs and creates corresponding Go packages. + +The target language is **auto-detected** from your project files (presence of `go.mod` picks Go). You can also force Go explicitly with the `--language` flag: + +```bash +cre generate-bindings solana --language go +``` + +Your project must already be a Go module (`go.mod` at the project root, as created by `cre init`). Without `go.mod`, the generator writes files but then fails when resolving Go dependencies. + +### 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 packages in `contracts/solana/src/generated/`. For each IDL, the generator creates a package directory (for example `data_storage/`) with the binding sources, including: + +- Struct types and `WriteReportFrom()` methods +- A mock for testing without a live network + +After generation, run `go mod tidy` if you need to sync new dependencies. + +## Using generated bindings + +### For onchain writes + +For each struct in your IDL's `types` section, the generator creates a `WriteReportFrom()` method that Borsh-encodes your data, builds the forwarder report, and submits it in one step. These methods return a `Promise`, which you must `.Await()` to get the result after consensus. + +**Example: A minimal `DataStorage` IDL** + +The following IDL is enough to generate a `WriteReportFromUserData()` helper. Save it as `contracts/solana/src/idl/data_storage.json`. You can run this example without deploying your own Anchor program: the program ID in the IDL is used as the write receiver, and simulation uses the Solana Devnet mock forwarder built into the CLI. + +```json +{ + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "data_storage", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Minimal CRE Solana write example IDL" + }, + "instructions": [ + { + "name": "on_report", + "discriminator": [214, 173, 18, 221, 173, 148, 151, 208], + "accounts": [], + "args": [ + { "name": "_metadata", "type": "bytes" }, + { "name": "payload", "type": "bytes" } + ] + } + ], + "accounts": [], + "events": [], + "errors": [], + "types": [ + { + "name": "UserData", + "type": { + "kind": "struct", + "fields": [ + { "name": "key", "type": "string" }, + { "name": "value", "type": "string" } + ] + } + } + ] +} +``` + +1. Generate the bindings from your project root. + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/src/generated/data_storage/` with a `UserData` struct and `WriteReportFromUserData(runtime, input, remainingAccounts, computeConfig)`. + +2. Sync dependencies. + + ```bash + go mod tidy + ``` + +3. Add Solana Devnet to `project.yaml` under your simulation target. + + ```yaml + staging-settings: + rpcs: + - chain-name: solana-devnet + url: https://api.devnet.solana.com + ``` + +4. Save the following as `config.staging.json` in your workflow folder. + + These addresses are the Solana Devnet **mock forwarder** values used by `cre workflow simulate`. The `forwarderAuthority` PDA is derived from `["forwarder", forwarderState, receiverProgram]` under the mock forwarder program ID for the `DataStorage` receiver above. `reportState` is a placeholder writable account for this dry-run example. + + ```json + { + "schedule": "*/30 * * * * *", + "chainSelector": "16423721717087811551", + "forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7", + "forwarderAuthority": "5wxb2QHeB4iyN1mPfmsjdsaxoP4P9EmTU7zBzpp4UgVu", + "reportState": "11111111111111111111111111111111" + } + ``` + +5. Save the following as `my-workflow/workflow.go`. + + A Go project is split across two files: `main.go` is the WASM entry point (generated by `cre init` and unchanged), and your workflow logic lives in a separate file such as `workflow.go`. Replace `` with your Go module name from `go.mod`. + + ```go + // my-workflow/workflow.go + package main + + import ( + "log/slog" + + solanago "github.com/gagliardetto/solana-go" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + "github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron" + "github.com/smartcontractkit/cre-sdk-go/cre" + + "/contracts/solana/src/generated/data_storage" + ) + + type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ForwarderState string `json:"forwarderState"` + ForwarderAuthority string `json:"forwarderAuthority"` + ReportState string `json:"reportState"` + } + + func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) { + cronTrigger := cron.Trigger(&cron.Config{Schedule: config.Schedule}) + + return cre.Workflow[*Config]{ + cre.Handler(cronTrigger, onCronTrigger), + }, nil + } + + func onCronTrigger(config *Config, runtime cre.Runtime, _ *cron.Payload) (string, error) { + client := &solana.Client{ChainSelector: config.ChainSelector} + + // NewDataStorage uses the program ID baked into the IDL + ds, err := data_storage.NewDataStorage(client) + if err != nil { + return "", err + } + + forwarderStatePk := solanago.MustPublicKeyFromBase58(config.ForwarderState) + forwarderAuthPk := solanago.MustPublicKeyFromBase58(config.ForwarderAuthority) + reportStatePk := solanago.MustPublicKeyFromBase58(config.ReportState) + + // remainingAccounts layout required by the forwarder: + // Index 0: forwarderState (read-only) + // Index 1: forwarderAuthority PDA (read-only) + // Index 2+: receiver-specific accounts + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStatePk[:], IsWritable: false}, + {PublicKey: forwarderAuthPk[:], IsWritable: false}, + {PublicKey: reportStatePk[:], IsWritable: true}, + } + + runtime.Logger().Info("Submitting UserData to Solana DataStorage program") + + // Solana simulate requires a non-nil ComputeConfig with ComputeLimit > 0 + computeConfig := &solana.ComputeConfig{ComputeLimit: 200_000} + + result, err := ds.WriteReportFromUserData(runtime, data_storage.UserData{ + Key: "price", + Value: "500000", + }, accounts, computeConfig).Await() + if err != nil { + return "", err + } + + runtime.Logger().Info("Write completed", + "txStatus", result.GetTxStatus().String(), + "errorMessage", result.GetErrorMessage(), + ) + return "ok", nil + } + ``` + +6. Set `CRE_SOLANA_PRIVATE_KEY` in your project `.env`, then run the simulation. + + Solana simulation requires a 64-byte base58 keypair even for dry-run (no `--broadcast`). The transmitter account derived from that key must exist and be funded on Solana Devnet. Generate a key with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and put the keypair in `.env`: + + ```bash + CRE_SOLANA_PRIVATE_KEY= + ``` + + Then from your project root: + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Expected output (dry-run against the mock forwarder). This example uses a placeholder receiver program ID, so the + write path completes through the mock forwarder and the CPI into the receiver returns `Custom:2007`. That confirms + bindings, encoding, and report submission worked: + + ``` + ✓ Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] msg="Submitting UserData to Solana DataStorage program" + [USER LOG] msg="Write completed" txStatus=TX_STATUS_FATAL errorMessage="map[InstructionError:[0 map[Custom:2007]]]" + + ✓ Workflow Simulation Result: + "ok" + ``` + + + +## What the CLI generates + +The generator creates a Go package for each IDL file in `contracts/solana/src/generated//`. + +What's inside depends on your IDL: + +- **For all programs**: + - A `` struct and `New(client)` constructor (program ID from the IDL). + - Codec helpers for encoding structs. +- **For onchain writes** (each struct in `types`): + - A `WriteReportFrom(runtime, input, remainingAccounts, computeConfig)` method that returns a `cre.Promise[*solana.WriteReportReply]` you `.Await()`. + - A `WriteReportFroms(...)` variant for Borsh `Vec` payloads. + +## `accounts` layout + +Every Solana write requires an `[]*solana.AccountMeta` slice. The Keystone Forwarder (and the Devnet mock forwarder used in simulation) expects: + +| Index | Account | `IsWritable` | How to obtain | +| ----- | -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------- | +| 0 | `forwarderState` | `false` | Provided by Chainlink for the forwarder deployment (mock values are built into the CLI for Devnet) | +| 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 | + +| Field | Type | Description | +| ------------ | -------- | -------------------------------------------- | +| `PublicKey` | `[]byte` | 32-byte raw public key (decoded from base58) | +| `IsWritable` | `bool` | Whether the account is writable | + + + +## Best practices + +1. **Regenerate when needed**: Re-run `cre generate-bindings solana` whenever your Anchor IDL changes. Do not edit generated files by hand. +2. **Always pass `ComputeConfig`**: Solana simulation rejects a nil or zero `ComputeLimit`. Pass `&solana.ComputeConfig{ComputeLimit: 200_000}` (or another positive limit within the simulator's CU cap). +3. **Handle errors**: Always check errors from constructors and `.Await()`. +4. **Store addresses in config**: Put forwarder and account addresses in `config.staging.json` / `config.production.json`. +5. **`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) — Full write walkthrough (deploy, broadcast, production forwarder addresses) +- [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-13 + +This guide walks through your first Solana write from a CRE workflow using the Go SDK. The preferred path is +**generated bindings**: you place your Anchor IDL in the project, run `cre generate-bindings solana`, and call a typed +`WriteReportFrom...` helper. The binding Borsh-encodes your payload, builds the forwarder report, and submits it — you +do not hand-roll encoding. + +Onchain, the DON does not call your program directly. It submits a signed report to the **Keystone Forwarder** — a +Chainlink-managed Solana program that verifies DON signatures, then cross-program invokes (CPI) your receiver's +`on_report`. That is the same forwarder model as [EVM Write](/cre/capabilities/evm-read-write); see +[Solana Write](/cre/capabilities/solana-write) for the full flow. Local simulation uses a Devnet stand-in of that +forwarder so you can dry-run without deploying. After you deploy, the live forwarder addresses apply. + +For IDL layout, generator output, and what each `WriteReportFrom...` method does under the hood, see +[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go). + +## 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. +- CRE CLI **v1.24.0 or later** (`cre version` / `cre update`) with Solana binding generation. +- A funded Solana Devnet keypair for `CRE_SOLANA_PRIVATE_KEY` (required even for dry-run). Generate one with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and put the 64-byte base58 keypair in your project `.env`. See [Step 4](#step-4-run-the-simulation). + +## What you need: a receiver program + +Your Solana program must implement an `on_report` instruction that accepts the payload delivered by the forwarder. The +forwarder CPIs into your program after verifying the DON signatures. Your Anchor IDL's `types` section defines the +struct(s) you will write; the generator turns each into a `WriteReportFrom()` method. + +This guide uses a minimal `DataStorage` / `UserData` IDL so you can simulate without deploying your own program. Swap in +your IDL and call the generated helpers for your structs when you are ready. + + + +## Step 1: Add your IDL and generate bindings + +1. Save the following as `contracts/solana/src/idl/data_storage.json` (or copy your own Anchor IDL into that folder). + + ```json + { + "address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "metadata": { + "name": "data_storage", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Minimal CRE Solana write example IDL" + }, + "instructions": [ + { + "name": "on_report", + "discriminator": [214, 173, 18, 221, 173, 148, 151, 208], + "accounts": [], + "args": [ + { "name": "_metadata", "type": "bytes" }, + { "name": "payload", "type": "bytes" } + ] + } + ], + "accounts": [], + "events": [], + "errors": [], + "types": [ + { + "name": "UserData", + "type": { + "kind": "struct", + "fields": [ + { "name": "key", "type": "string" }, + { "name": "value", "type": "string" } + ] + } + } + ] + } + ``` + +2. From your project root, generate the bindings: + + ```bash + cre generate-bindings solana + ``` + + This creates `contracts/solana/src/generated/data_storage/` with a `UserData` struct and + `WriteReportFromUserData(runtime, input, remainingAccounts, computeConfig)`. + +3. Sync dependencies. + + ```bash + go mod tidy + ``` + +## Step 2: Configure the workflow + +1. Open (or create) `config.staging.json` and add the Solana Devnet simulation parameters. + + For `cre workflow simulate`, use these fixed Devnet forwarder program and state accounts (baked into the CLI). + `receiverProgramId` matches the IDL address above for this example; with your own program, use your Devnet program + ID. + + ```json + { + "schedule": "*/30 * * * * *", + "chainSelector": "16423721717087811551", + "receiverProgramId": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL", + "forwarderProgramId": "7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK", + "forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7", + "reportState": "11111111111111111111111111111111", + "computeLimit": 200000 + } + ``` + + | Field | Description | + | -------------------- | ------------------------------------------------------------------------------------------- | + | `chainSelector` | Solana Devnet (`"16423721717087811551"`). Store as a string to avoid JSON precision loss. | + | `receiverProgramId` | Your receiver program (IDL address for this example) | + | `forwarderProgramId` | Forwarder program ID used for local simulation (fixed Devnet value) | + | `forwarderState` | Forwarder state account used for local simulation (fixed Devnet value) | + | `reportState` | Writable account required by your receiver (placeholder system program ID for this dry-run) | + | `computeLimit` | Solana compute units; must be greater than 0 for simulation | + + The workflow derives `forwarderAuthority` at runtime from + `[]byte("forwarder")`, forwarder state bytes, and receiver program bytes under `forwarderProgramId`. When you deploy + in [Step 5](#step-5-deploy-the-workflow), keep that derivation and point `forwarderProgramId` / `forwarderState` at + the live forwarder addresses for your network. + +## Step 3: Write the workflow + +This step is a complete, pasteable workflow that performs one Solana write on a cron trigger using **generated +bindings**. It reads config, derives the forwarder authority PDA, builds the account list the forwarder expects, and +calls `WriteReportFromUserData()`. Replace `data_storage` / `UserData` with your generated package and struct when you +use your own IDL. + +1. Save the following as `my-workflow/workflow.go`. + + A Go project is split across two files: `main.go` is the WASM entry point (generated by `cre init` and unchanged), and + your workflow logic lives in a separate file such as `workflow.go`. Replace `` with your Go module name + from `go.mod`. + + ```go + // my-workflow/workflow.go + package main + + import ( + "log/slog" + + solanago "github.com/gagliardetto/solana-go" + solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana" + "github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron" + "github.com/smartcontractkit/cre-sdk-go/cre" + + "/contracts/solana/src/generated/data_storage" + ) + + const defaultComputeLimit uint32 = 200_000 + + type Config struct { + Schedule string `json:"schedule"` + ChainSelector uint64 `json:"chainSelector,string"` + ReceiverProgramID string `json:"receiverProgramId"` + ForwarderProgramID string `json:"forwarderProgramId"` + ForwarderState string `json:"forwarderState"` + ReportState string `json:"reportState"` + ComputeLimit uint32 `json:"computeLimit"` + } + + func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) { + cronTrigger := cron.Trigger(&cron.Config{Schedule: config.Schedule}) + + return cre.Workflow[*Config]{ + cre.Handler(cronTrigger, onCronTrigger), + }, nil + } + + func deriveForwarderAuthority(forwarderState, receiverProgram, forwarderProgram solanago.PublicKey) (solanago.PublicKey, error) { + authority, _, err := solanago.FindProgramAddress( + [][]byte{ + []byte("forwarder"), + forwarderState[:], + receiverProgram[:], + }, + forwarderProgram, + ) + return authority, err + } + + func onCronTrigger(config *Config, runtime cre.Runtime, _ *cron.Payload) (string, error) { + client := &solana.Client{ChainSelector: config.ChainSelector} + + // NewDataStorage uses the program ID baked into the IDL (regenerate bindings when it changes). + ds, err := data_storage.NewDataStorage(client) + if err != nil { + return "", err + } + + forwarderStatePk := solanago.MustPublicKeyFromBase58(config.ForwarderState) + receiverPk := solanago.MustPublicKeyFromBase58(config.ReceiverProgramID) + forwarderProgramPk := solanago.MustPublicKeyFromBase58(config.ForwarderProgramID) + reportStatePk := solanago.MustPublicKeyFromBase58(config.ReportState) + + forwarderAuthority, err := deriveForwarderAuthority(forwarderStatePk, receiverPk, forwarderProgramPk) + if err != nil { + return "", err + } + + // Index 0 forwarderState, 1 forwarderAuthority (derived), 2+ reportState. + accounts := []*solana.AccountMeta{ + {PublicKey: forwarderStatePk[:], IsWritable: false}, + {PublicKey: forwarderAuthority[:], IsWritable: false}, + {PublicKey: reportStatePk[:], IsWritable: true}, + } + + payload := data_storage.UserData{ + Key: "price", + Value: "500000", + } + + runtime.Logger().Info("Submitting UserData via bindings", + "key", payload.Key, + "forwarderAuthority", forwarderAuthority.String(), + ) + + computeLimit := config.ComputeLimit + if computeLimit == 0 { + computeLimit = defaultComputeLimit + } + computeConfig := &solana.ComputeConfig{ComputeLimit: computeLimit} + + result, err := ds.WriteReportFromUserData(runtime, payload, accounts, computeConfig).Await() + if err != nil { + return "", err + } + + runtime.Logger().Info("Write completed", + "txStatus", result.GetTxStatus().String(), + "errorMessage", result.GetErrorMessage(), + "txSignature", result.GetTxSignature(), + ) + if msg := result.GetErrorMessage(); msg != "" { + return msg, nil + } + return "Success", nil + } + ``` + +## Step 4: Run the simulation + +Use this step to confirm compile, trigger, and the bindings write path with `cre workflow simulate`. + +1. Add a Solana RPC endpoint to your `project.yaml` under your simulation target. + + ```yaml + staging-settings: + rpcs: + - chain-name: solana-devnet + url: https://api.devnet.solana.com + ``` + +2. Set `CRE_SOLANA_PRIVATE_KEY` in your project `.env`. + + Solana simulation requires a 64-byte base58 keypair even for dry-run. The transmitter account must exist and be + funded on Solana Devnet. Generate a key with `solana-keygen new`, fund it with `solana airdrop` (or a transfer), and + put the keypair in `.env`: + + ```bash + CRE_SOLANA_PRIVATE_KEY= + ``` + +3. Run the dry-run simulation. + + ```bash + cre workflow simulate my-workflow --target staging-settings + ``` + + Confirm the workflow compiles, the cron trigger runs, and your USER LOGs appear. This example uses a placeholder + receiver program ID, so the simulation write may complete with a receiver CPI error such as `Custom:2007`. That still + confirms bindings, encoding, and report submission worked: + + ``` + ✓ Workflow compiled + [SIMULATION] Simulator Initialized + [SIMULATION] Running trigger trigger=cron-trigger@1.0.0 + [USER LOG] msg="Submitting UserData via bindings" key=price forwarderAuthority=... + [USER LOG] msg="Write completed" txStatus=... errorMessage=... + + ✓ Workflow Simulation Result: + ... + ``` + + When you are ready to run on the DON with **your** receiver, continue to [Step 5](#step-5-deploy-the-workflow). + +## Step 5: Deploy the workflow + +A deployed workflow runs on the DON, which submits reports through the live Keystone Forwarder on Solana Devnet. Prefer +the [private registry](/cre/guides/operations/deploying-to-private-registry-go) for this example (CRE login session; no +Ethereum gas for registry ops). You need [Deploy Access](/cre/account/deploy-access). + +Use **your** receiver program ID and accounts — not the placeholder `DataStorage` address from the dry-run IDL unless +that program is actually deployed and ready to accept writes. Update the IDL address, regenerate bindings, and set +`receiverProgramId` to the same value (Go bindings use the program ID baked into the generated package for the write +receiver; config is used to derive the forwarder authority PDA). + +1. Create `config.production.json` with the live forwarder addresses for your network (same receiver and report state + your program expects). + + ```json + { + "schedule": "30 */5 * * * *", + "chainSelector": "16423721717087811551", + "receiverProgramId": "", + "forwarderProgramId": "", + "forwarderState": "", + "reportState": "", + "computeLimit": 290000 + } + ``` + + Get forwarder program and state addresses from your Chainlink representative. The workflow still derives + `forwarderAuthority` from `[]byte("forwarder")`, forwarder state bytes, and receiver program bytes under that + `forwarderProgramId`. + +2. Point a deploy target at that config and the private registry in `my-workflow/workflow.yaml`. + + ```yaml + production-settings: + user-workflow: + workflow-name: "my-solana-write" + deployment-registry: "private" + workflow-artifacts: + workflow-path: "./main.go" + config-path: "./config.production.json" + secrets-path: "" + ``` + +3. Add the Solana Devnet RPC under `production-settings` in `project.yaml` (same URL as staging is fine). + +4. Deploy from your project root. + + ```bash + cre workflow deploy my-workflow --target production-settings + ``` + + Expected shape: + + ``` + Deploying Workflow: my-solana-write + Registry: private + ... + ✓ Workflow registered in private registry + ... + Status: Active + ``` + +5. After the cron fires, open the workflow in the [CRE platform](https://app.chain.link/cre/workflows) (or run + `cre execution list my-solana-write`) and confirm node logs reach the write path: + + ``` + Submitting UserData via bindings key=... + ``` + + If WriteReport returns a Solana `txSignature`, confirm the transaction on + [Solana Explorer](https://explorer.solana.com/?cluster=devnet) (forwarder as the top-level program, your receiver as + a CPI). + +See [Deploying to the Private Registry](/cre/guides/operations/deploying-to-private-registry-go) for registry details, activate/pause, and CI flags. + +## What happens onchain + +When the deployed workflow runs on the DON: + +1. The DON nodes each execute your workflow and reach consensus on the encoded 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 + +- **[Generating Solana Bindings](/cre/guides/workflow/using-solana-client/generating-bindings-go)**: IDL layout, generator output, and what `WriteReportFrom...` does +- **[Solana Client SDK Reference](/cre/reference/sdk/solana-client-go)**: Full API reference for `solana.Client` and helpers +- **[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-13 + +`solana.Client` is the Go SDK's interface for Solana chain interactions from CRE workflows. Writes submit cryptographically signed reports through the Keystone Forwarder program, which verifies DON signatures and CPIs into your receiver program. + + + +## How it works + +For Solana program interactions, CRE can **generate typed bindings** from your program's [Anchor IDL](https://www.anchor-lang.com/docs/references/idl). You place the IDL in your project, run `cre generate-bindings solana`, and the CLI creates Go packages with helpers such as `WriteReportFrom()` that handle encoding, account hashing, and report submission for you. + +Typical flow: + +1. Add your Anchor IDL under `contracts/solana/src/idl/` +2. Run `cre generate-bindings solana` from your project root +3. Call the generated helpers from your workflow + +## 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)**: End-to-end first write with generated bindings, simulate, and deploy + +--- + # Cron Trigger Source: https://docs.chain.link/cre/guides/workflow/using-triggers/cron-trigger-go Last Updated: 2025-11-04 @@ -20633,6 +21423,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_ABORTED`, or `TX_STATUS_FATAL` | +| `TxSignature` | `[]byte` | Transaction signature bytes, accessed via `GetTxSignature()` (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 @@ -20862,9 +21899,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-13 -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.