Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/config/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,14 @@ export const SIDEBAR: Partial<Record<Sections, SectionEntry[]>> = {
"cre/guides/workflow/using-solana-client/onchain-write-go",
],
},
{
title: "Forwarder Directory",
url: "cre/guides/workflow/using-solana-client/forwarder-directory",
highlightAsCurrent: [
"cre/guides/workflow/using-solana-client/forwarder-directory-ts",
"cre/guides/workflow/using-solana-client/forwarder-directory-go",
],
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
section: cre
title: "Solana Forwarder Directory"
sdkLang: "go"
pageId: "solana-forwarder-directory"
date: Last Modified
metadata:
description: "Find Solana forwarder program and state account addresses for CRE workflows on simulation and production networks."
datePublished: "2026-07-15"
lastModified: "2026-07-15"
---

import { Aside, CopyText } from "@components"

This page lists forwarder program and state account addresses for CRE Solana workflows. Solana uses a different forwarder
model than EVM: instead of a single contract address, each forwarder deployment has a **program ID** and a **state
account**. Your workflow config needs both values.

<Aside type="note" title="Looking for supported networks?">
For a complete list of supported networks and version requirements, see [Supported
Networks](/cre/supported-networks-go).
</Aside>

<Aside type="tip" title="Tenant-scoped chain list">
Chain availability and mock forwarder addresses depend on your CRE tenant. Run [`cre workflow
supported-chains`](/cre/reference/cli/workflow#cre-workflow-supported-chains) after `cre login` to see the chains and
mock forwarder addresses enabled for your organization. Use `--output json` for scripting.
</Aside>

## How to use this page

Your workflow config (`config.staging.json` / `config.production.json`) includes `forwarderProgramId` and
`forwarderState` fields. Use the simulation addresses for `cre workflow simulate` and the production addresses when
deploying to the DON. Learn more: [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-go).

{/* prettier-ignore */}
<Aside type="caution" title="Different addresses for simulation vs production">
The mock forwarder addresses used during simulation are **different** from the production Keystone Forwarder addresses
used by deployed workflows. After testing with simulation, update `forwarderProgramId` and `forwarderState` in your
production config. See [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-go#simulation-vs-production-forwarder-addresses).
</Aside>

## Simulation (mock forwarder)

These addresses are baked into the CLI and used automatically by `cre workflow simulate`. Use these **only** during local
development and testing.

| Network | Mock Forwarder Program ID | Mock Forwarder State Account |
| -------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Solana Devnet | <CopyText text="7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK" code/> | <CopyText text="5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7" code/> |
| Solana Mainnet | <CopyText text="7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK" code/> | <CopyText text="jhCjuD4Z3V7HeSUChMRpkRwpw6B9yC63mxDMv8SdLNX" code/> |

## Production (Keystone Forwarder)

For production deployments, use these live Keystone Forwarder addresses:

| Network | Keystone Forwarder Program ID | Keystone Forwarder State Account |
| -------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Solana Devnet | <CopyText text="CXsKEJcs25TQEYU2e5jZ8QTPE3ffMLZhH6BWHrdcCCB5" code/> | <CopyText text="8QoomCQyPSkJ8WopJbX9B4HyvrFzziwvJdU8hZE6DCr9" code/> |
| Solana Mainnet | <CopyText text="GFrSSvQXaVGkc6Nrr8y2msie6pivJkR1s2EnDk4et294" code/> | <CopyText text="9FgdPyU28bGMCuJyD34pzw9W7Ys36ziLbT9cbZtsaraV" code/> |

The workflow derives `forwarderAuthority` at runtime from `[]byte("forwarder")`, forwarder state bytes, and receiver
program bytes under the `forwarderProgramId`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
section: cre
title: "Solana Forwarder Directory"
sdkLang: "ts"
pageId: "solana-forwarder-directory"
date: Last Modified
metadata:
description: "Find Solana forwarder program and state account addresses for CRE workflows on simulation and production networks."
datePublished: "2026-07-15"
lastModified: "2026-07-15"
---

import { Aside, CopyText } from "@components"

This page lists forwarder program and state account addresses for CRE Solana workflows. Solana uses a different forwarder
model than EVM: instead of a single contract address, each forwarder deployment has a **program ID** and a **state
account**. Your workflow config needs both values.

<Aside type="note" title="Looking for supported networks?">
For a complete list of supported networks and version requirements, see [Supported
Networks](/cre/supported-networks-ts).
</Aside>

<Aside type="tip" title="Tenant-scoped chain list">
Chain availability and mock forwarder addresses depend on your CRE tenant. Run [`cre workflow
supported-chains`](/cre/reference/cli/workflow#cre-workflow-supported-chains) after `cre login` to see the chains and
mock forwarder addresses enabled for your organization. Use `--output json` for scripting.
</Aside>

## How to use this page

Your workflow config (`config.staging.json` / `config.production.json`) includes `forwarderProgramId` and
`forwarderState` fields. Use the simulation addresses for `cre workflow simulate` and the production addresses when
deploying to the DON. Learn more: [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-ts).

{/* prettier-ignore */}
<Aside type="caution" title="Different addresses for simulation vs production">
The mock forwarder addresses used during simulation are **different** from the production Keystone Forwarder addresses
used by deployed workflows. After testing with simulation, update `forwarderProgramId` and `forwarderState` in your
production config. See [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-ts#simulation-vs-production-forwarder-addresses).
</Aside>

## Simulation (mock forwarder)

These addresses are baked into the CLI and used automatically by `cre workflow simulate`. Use these **only** during local
development and testing.

| Network | Mock Forwarder Program ID | Mock Forwarder State Account |
| -------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Solana Devnet | <CopyText text="7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK" code/> | <CopyText text="5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7" code/> |
| Solana Mainnet | <CopyText text="7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK" code/> | <CopyText text="jhCjuD4Z3V7HeSUChMRpkRwpw6B9yC63mxDMv8SdLNX" code/> |

## Production (Keystone Forwarder)

For production deployments, use these live Keystone Forwarder addresses:

| Network | Keystone Forwarder Program ID | Keystone Forwarder State Account |
| -------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Solana Devnet | <CopyText text="CXsKEJcs25TQEYU2e5jZ8QTPE3ffMLZhH6BWHrdcCCB5" code/> | <CopyText text="8QoomCQyPSkJ8WopJbX9B4HyvrFzziwvJdU8hZE6DCr9" code/> |
| Solana Mainnet | <CopyText text="GFrSSvQXaVGkc6Nrr8y2msie6pivJkR1s2EnDk4et294" code/> | <CopyText text="9FgdPyU28bGMCuJyD34pzw9W7Ys36ziLbT9cbZtsaraV" code/> |

The workflow derives `forwarderAuthority` at runtime from `["forwarder", forwarderState, receiverProgram]` under the
`forwarderProgramId`.
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ For each struct in your IDL's `types` section, the generator creates a `WriteRep

**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.
The following IDL is enough to generate a `WriteReportFromUserData()` helper. Save it as `contracts/solana/src/idl/data_storage.json`.

```json
{
"address": "ECL8142j2YQAvs9R9geSsRnkVH2wLEi7soJCRyJ74cfL",
"address": "64Q1Xu7AEbc2xgaN21zZU1y1mkaLJNnP5cHFkthGuW31",
"metadata": {
"name": "data_storage",
"version": "0.1.0",
Expand Down Expand Up @@ -118,150 +118,38 @@ The following IDL is enough to generate a `WriteReportFromUserData()` helper. Sa
}
```

1. Generate the bindings from your project root.
After running `cre generate-bindings solana`, use the generated `data_storage` package in your workflow:

```bash
cre generate-bindings solana
```
```go
import (
solanago "github.com/gagliardetto/solana-go"
solana "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana"

This creates `contracts/solana/src/generated/data_storage/` with a `UserData` struct and `WriteReportFromUserData(runtime, input, remainingAccounts, computeConfig)`.
"<project-name>/contracts/solana/src/generated/data_storage"
)

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.
// In your workflow handler...
client := &solana.Client{ChainSelector: config.ChainSelector}
ds, err := data_storage.NewDataStorage(client)
if err != nil {
return "", err
}

```json
{
"schedule": "*/30 * * * * *",
"chainSelector": "16423721717087811551",
"forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7",
"forwarderAuthority": "5wxb2QHeB4iyN1mPfmsjdsaxoP4P9EmTU7zBzpp4UgVu",
"reportState": "11111111111111111111111111111111"
}
```
// remainingAccounts: Index 0 forwarderState, 1 forwarderAuthority, 2+ receiver accounts
accounts := []*solana.AccountMeta{
{PublicKey: forwarderStatePk[:], IsWritable: false},
{PublicKey: forwarderAuthPk[:], IsWritable: false},
{PublicKey: reportStatePk[:], IsWritable: true},
}

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 `<project-name>` with your Go module name from `go.mod`.
// Pass the struct data directly — types are derived from the IDL
result, err := ds.WriteReportFromUserData(runtime, data_storage.UserData{
Key: "price",
Value: "500000",
}, accounts, &solana.ComputeConfig{ComputeLimit: 200_000}).Await()
```

```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"

"<project-name>/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=<your-64-byte-base58-keypair>
```

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"
```
For a full walkthrough including config, simulation, and production deployment, see [Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-go).

<Aside type="note" title="What does the WriteReport helper do?">
The `WriteReportFromUserData` method Borsh-encodes the `UserData` struct, hashes `remainingAccounts` via
Expand Down
Loading
Loading