# Writing to Solana
Source: https://docs.chain.link/cre/guides/workflow/using-solana-client/onchain-write-go
Last Updated: 2026-10-19

> For the complete documentation index, see [llms.txt](/llms.txt).

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 Read & Write](/cre/capabilities/solana-read-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.
- The latest version of the CRE CLI (`cre version` / `cre update`); **v1.29.0 or later** is required for 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<StructName>()` method.

This guide uses a minimal `cre_docs_receiver` program deployed to Solana Devnet at
`7k8NypziCPqVY8GYyCw7aqaR5aamFAMdH5ZaHVrZCS94`. Its `on_report` re-derives and verifies the `forwarder_authority` PDA,
then Borsh-decodes the report payload into a `UserData { key, value }` struct and logs it. It requires **no
receiver-specific accounts** beyond the two the forwarder always supplies (`state` and `forwarder_authority`). You can
simulate against it without deploying your own program. Swap in your IDL and call the generated helpers for your structs
when you are ready.

```rust
// cre_docs_receiver on_report (abridged)
pub fn on_report(ctx: Context<OnReport>, metadata: Vec<u8>, report: Vec<u8>) -> Result<()> {
    // ... re-derive + verify forwarder_authority PDA ...
    let user = UserData::deserialize(&mut &report[..])?; // Borsh { key, value }
    msg!("user.key={} user.value={}", user.key, user.value);
    Ok(())
}

#[derive(Accounts)]
pub struct OnReport<'info> {
    /// CHECK: forwarder state; its owner is the forwarder program.
    pub state: UncheckedAccount<'info>,
    /// PDA signer supplied by the forwarder CPI.
    pub forwarder_authority: Signer<'info>,
}
```

> **NOTE: How remainingAccounts maps to the forwarder**
>
> You provide `remainingAccounts` as `[forwarderState, forwarderAuthority, ...receiverAccounts]`. The **whole** list is
> hashed into the report (an integrity check the forwarder re-computes onchain). The Solana write capability then
> consumes indices 0 and 1 (`forwarderState`, `forwarderAuthority`) — it builds those into the forwarder instruction
> itself — and forwards only indices 2+ (your receiver-specific accounts) to your program's `on_report` CPI. So indices
> 0 and 1 must be present in the list you build (so the hash matches), even though the capability supplies them to the
> instruction directly. `cre_docs_receiver` has no receiver-specific accounts, so its list is just `[forwarderState,
>   forwarderAuthority]`.

## Step 1: Add your IDL and generate bindings

1. Save the following as `contracts/solana/src/idl/cre_docs_receiver.json` (or copy your own Anchor IDL into that
   folder). This is the deployed `cre_docs_receiver` program's IDL.

   ```json
   {
     "address": "7k8NypziCPqVY8GYyCw7aqaR5aamFAMdH5ZaHVrZCS94",
     "metadata": {
       "name": "cre_docs_receiver",
       "version": "0.1.0",
       "spec": "0.1.0",
       "description": "Receiver for cre-cli Solana forwarder simulation that logs a decoded UserData payload"
     },
     "instructions": [
       {
         "name": "on_report",
         "discriminator": [214, 173, 18, 221, 173, 148, 151, 208],
         "accounts": [
           { "name": "state" },
           {
             "name": "forwarder_authority",
             "docs": ["PDA signer supplied by the forwarder CPI."],
             "signer": true
           }
         ],
         "args": [
           { "name": "metadata", "type": "bytes" },
           { "name": "report", "type": "bytes" }
         ]
       }
     ],
     "events": [
       {
         "name": "ReceivedUserData",
         "discriminator": [154, 5, 186, 144, 208, 168, 118, 242]
       }
     ],
     "errors": [
       {
         "code": 6000,
         "name": "InvalidForwarderAuthority",
         "msg": "forwarder_authority is not the PDA for this state, receiver, and forwarder program"
       },
       {
         "code": 6001,
         "name": "InvalidUserData",
         "msg": "report payload could not be decoded as UserData"
       }
     ],
     "types": [
       {
         "name": "ReceivedUserData",
         "type": {
           "kind": "struct",
           "fields": [
             { "name": "forwarder", "type": "pubkey" },
             { "name": "forwarder_state", "type": "pubkey" },
             { "name": "forwarder_authority", "type": "pubkey" },
             { "name": "metadata_len", "type": "u32" },
             { "name": "report_len", "type": "u32" },
             { "name": "user", "type": { "defined": { "name": "UserData" } } }
           ]
         }
       },
       {
         "name": "UserData",
         "type": {
           "kind": "struct",
           "fields": [
             { "name": "key", "type": "string" },
             { "name": "value", "type": "string" }
           ]
         }
       }
     ]
   }
   ```

   > **NOTE: Why UserData is referenced by the event**
   >
   > Anchor only emits a type into the IDL if it is referenced by an instruction argument, account, or event. Because
   > `on_report` takes the payload as raw `bytes`, `UserData` would be dropped from the IDL — and the generator would not
   > produce `WriteReportFromUserData`. `cre_docs_receiver` references `UserData` in its `ReceivedUserData` event to keep
   > it in the IDL. If your own program already takes a typed struct or exposes it via an account/event, no extra step is
   > needed.

2. From your project root, generate the bindings:

   ```bash
   cre generate-bindings solana --language go
   ```

   This creates `contracts/solana/src/generated/cre_docs_receiver/` 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 mock forwarder program and state accounts for Solana Devnet.
   `receiverProgramId` matches the IDL address above for this example; with your own program, use your Devnet program
   ID.

   ```json
   {
     "schedule": "*/30 * * * * *",
     "chainSelector": "16423721717087811551",
     "receiverProgramId": "7k8NypziCPqVY8GYyCw7aqaR5aamFAMdH5ZaHVrZCS94",
     "forwarderProgramId": "7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK",
     "forwarderState": "5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7"
   }
   ```

   | 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)                    |

   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.

### Simulation vs production forwarder addresses

The forwarder program and state accounts differ between local simulation and production. The config above uses the
**mock forwarder** addresses for `cre workflow simulate`. When you deploy to the DON, you must
replace them with the live Keystone Forwarder addresses for your network.

> **CAUTION: Important: Different addresses for simulation vs production**
>
> The mock forwarder program and state accounts used during simulation are **different** from the production Keystone
> Forwarder accounts used by deployed workflows. After testing with simulation, update `forwarderProgramId` and
> `forwarderState` in your production config to the live forwarder addresses for your network. See
> [Step 5](#step-5-deploy-the-workflow).

#### Simulation (mock forwarder)

These are the mock forwarder addresses for `cre workflow simulate`. They are the values shown in
the `config.staging.json` above:

| Network        | Mock Forwarder Program ID                    | Mock Forwarder State Account                 |
| -------------- | -------------------------------------------- | -------------------------------------------- |
| Solana Devnet  | 7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK | 5Tipz3yhTBdVsDbaBxZkrp7Gjf3brGq5SKkxReefPMP7 |
| Solana Mainnet | 7kuEAA3mSC1Tz8gQjnvH7bKFda9xSPRRin9SZbH49cNK | jhCjuD4Z3V7HeSUChMRpkRwpw6B9yC63mxDMv8SdLNX  |

#### Production (Keystone Forwarder)

For production deployments, use these live Keystone Forwarder addresses:

| Network        | Keystone Forwarder Program ID                | Keystone Forwarder State Account             |
| -------------- | -------------------------------------------- | -------------------------------------------- |
| Solana Devnet  | CXsKEJcs25TQEYU2e5jZ8QTPE3ffMLZhH6BWHrdcCCB5 | 8QoomCQyPSkJ8WopJbX9B4HyvrFzziwvJdU8hZE6DCr9 |
| Solana Mainnet | GFrSSvQXaVGkc6Nrr8y2msie6pivJkR1s2EnDk4et294 | 9FgdPyU28bGMCuJyD34pzw9W7Ys36ziLbT9cbZtsaraV |

The workflow still derives `forwarderAuthority` from `[]byte("forwarder")`, forwarder state bytes, and receiver program bytes under the
production `forwarderProgramId`.

## 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, and calls
`WriteReportFromUserData()`. Replace `cre_docs_receiver` / `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 `<project-name>` 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"

   	"<project-name>/contracts/solana/src/generated/cre_docs_receiver"
   )

   type Config struct {
   	Schedule           string `json:"schedule"`
   	ChainSelector      uint64 `json:"chainSelector,string"`
   	ReceiverProgramID  string `json:"receiverProgramId"`
   	ForwarderState     string `json:"forwarderState"`
   	ForwarderProgramID string `json:"forwarderProgramId"`
   }

   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}

   	// NewCreDocsReceiver uses the program ID baked into the IDL (regenerate bindings when it changes).
   	ds, err := cre_docs_receiver.NewCreDocsReceiver(client)
   	if err != nil {
   		return "", err
   	}

   	forwarderStatePk := solanago.MustPublicKeyFromBase58(config.ForwarderState)
   	receiverPk := solanago.MustPublicKeyFromBase58(config.ReceiverProgramID)
   	forwarderProgramPk := solanago.MustPublicKeyFromBase58(config.ForwarderProgramID)

   	// Derive the forwarder_authority PDA the same way the on-chain forwarder /
   	// receiver do: seeds = ["forwarder", forwarderState, receiverProgram],
   	// program = forwarderProgram.
   	forwarderAuthority, _, err := solanago.FindProgramAddress(
   		[][]byte{
   			[]byte("forwarder"),
   			forwarderStatePk[:],
   			receiverPk[:],
   		},
   		forwarderProgramPk,
   	)
   	if err != nil {
   		return "", err
   	}

   	// remainingAccounts layout:
   	//   index 0: forwarderState, index 1: forwarderAuthority, index 2+: receiver accounts.
   	// The full list is hashed into the report; the Solana write capability consumes
   	// indices 0/1 and forwards index 2+ to your program's on_report CPI.
   	// cre_docs_receiver needs no receiver-specific accounts, so we pass just [state, authority].
   	accounts := []*solana.AccountMeta{
   		{PublicKey: forwarderStatePk[:], IsWritable: false},
   		{PublicKey: forwarderAuthority[:], IsWritable: false},
   		// Append any receiver-specific accounts your program requires at index 2+, e.g.:
   		// {PublicKey: myWritableStatePda[:], IsWritable: true},
   	}

   	payload := cre_docs_receiver.UserData{
   		Key:   "price",
   		Value: "500000",
   	}

   	runtime.Logger().Info("Submitting UserData to Solana cre_docs_receiver program")

   	// Solana simulate requires a non-nil computeConfig with computeLimit > 0
   	computeConfig := &solana.ComputeConfig{ComputeLimit: 200_000}

   	result, err := ds.WriteReportFromUserData(runtime, payload, accounts, computeConfig).Await()
   	if err != nil {
   		return "", err
   	}

   	runtime.Logger().Info("Solana write result",
   		"txStatus", result.GetTxStatus().String(),
   		"receiverStatus", result.GetReceiverContractExecutionStatus().String(),
   		"transactionFee", result.GetTransactionFee(),
   	)
   	if sig := result.GetTxSignature(); len(sig) > 0 {
   		// txSignature is []byte (raw 64-byte signature); convert to base58 for explorer links.
   		var txSig solanago.Signature
   		copy(txSig[:], sig)
   		txHash := txSig.String()
   		runtime.Logger().Info("Transaction broadcast",
   			"txHash", txHash,
   			"explorer", "https://explorer.solana.com/tx/"+txHash+"?cluster=devnet",
   		)
   	} else {
   		runtime.Logger().Info("Transaction not broadcast (simulation does not submit the transaction)")
   	}
   	if msg := result.GetErrorMessage(); msg != "" {
   		runtime.Logger().Info("errorMessage", "msg", msg)
   	}

   	return "Success", nil
   }
   ```

## Step 4: Run the simulation

Use this step to confirm compile, trigger, and the bindings write path with `cre workflow simulate`.

> **NOTE: Two separate YAML files**
>
> CRE uses two configuration files that live in different directories:
>
> - **`workflow.yaml`** lives inside your workflow folder (`my-workflow/workflow.yaml`). It defines workflow metadata
>   (name, registry) and points to your `main.go` and config JSON.
> - **`project.yaml`** lives at your **project root** (the directory you ran `cre init` in, one level above
>   `my-workflow/`). It defines RPC endpoints and other project-level settings per target.
>
> Both files use the same target names (e.g. `staging-settings`, `production-settings`) to link a workflow to its
> RPCs. You will add to both files in this step.

1. Create a `workflow.yaml` in your workflow folder so the CLI can find your staging target.

   ```yaml
   staging-settings:
     user-workflow:
       workflow-name: "my-solana-write"
     workflow-artifacts:
       workflow-path: "./main.go"
       config-path: "./config.staging.json"
       secrets-path: ""
   ```

2. Add a Solana RPC endpoint to your `project.yaml` (at your project root, **not** inside `my-workflow/`) under your
   simulation target.

   ```yaml
   staging-settings:
     rpcs:
       - chain-name: solana-devnet
         url: https://api.devnet.solana.com
   ```

3. 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=<your-64-byte-base58-keypair>
   ```

   > **CAUTION: CRE\_ETH\_PRIVATE\_KEY is also required**
   >
   > The CRE CLI requires `CRE_ETH_PRIVATE_KEY` to be set even for Solana-only workflows — it is used for workflow
   > owner derivation, not for the Solana transaction itself. Set any valid EVM private key in your `.env`:
   >
   > ```bash
   > CRE_ETH_PRIVATE_KEY=0x<your-evm-private-key>
   > ```
   >
   > Without it, `cre workflow simulate` and `cre workflow deploy` will fail with
   > `CRE_ETH_PRIVATE_KEY is not set`.

4. Run the dry-run simulation.

   ```bash
   cre workflow simulate my-workflow --target staging-settings
   ```

   Because `cre_docs_receiver` is deployed and the account list matches what the forwarder expects, the write completes
   successfully (`txStatus: SUCCESS`, `receiverStatus: SUCCESS`). Simulation does not broadcast the transaction here, so there
   is no `txSignature` / explorer link in the dry-run.

   ```
   ✓ Workflow compiled
   [SIMULATION] Simulator Initialized
   [SIMULATION] Running trigger trigger=cron-trigger@1.0.0
   [USER LOG] msg="Submitting UserData to Solana cre_docs_receiver program"
   [USER LOG] msg="Solana write result" txStatus="SUCCESS" receiverStatus="SUCCESS" transactionFee=15667
   [USER LOG] msg="Transaction not broadcast (simulation does not submit the transaction)"

   ✓ Workflow Simulation Result:
   {
     "$typeName": "capabilities.blockchain.solana.v1alpha.WriteReportReply",
     "receiverContractExecutionStatus": 0,
     "transactionFee": 15667,
     "txStatus": 2
   }
   ```

   > **NOTE: Troubleshooting write failures**
   >
   > `Custom:6002` (`InvalidAccountHash`) means the report's account hash did not match what the forwarder computed —
   > usually because `forwarderProgramId` / `forwarderState` do not match the forwarder you are targeting, or the account
   > list you built is wrong. `Custom:6000` (`InvalidForwarderAuthority`) means the derived `forwarder_authority` PDA is
   > wrong for that state/receiver/forwarder combination. `Custom:6001` (`InvalidUserData`) means the receiver could not
   > Borsh-decode the payload into `UserData`.

5. (Optional) Broadcast the transaction to Solana Devnet.

   By default, simulation dry-runs the write and returns no signature. Add `--broadcast` to actually submit the
   transaction through the mock forwarder. Your `CRE_SOLANA_PRIVATE_KEY` (transmitter) pays the fee, so it must be funded
   on Devnet.

   ```bash
   cre workflow simulate my-workflow --target staging-settings --broadcast
   ```

   This time `WriteReport` returns a real `txSignature`, and the workflow logs a `txSignature` plus an explorer link:

   ```
   [USER LOG] msg="Submitting UserData to Solana cre_docs_receiver program"
   [USER LOG] msg="Solana write result" txStatus="SUCCESS" receiverStatus="SUCCESS" transactionFee=5000
   [USER LOG] msg="Transaction broadcast" txSignature="c8ifuihaPd2erDuQvees1s2o8ikFmZuNF7WQE63PPGG2SXHZk5vXrHfe7nm4cj4fo7cE3Aq48ksMjP6ugPxAhqn"
   [USER LOG] msg="explorer" explorer="https://explorer.solana.com/tx/c8ifuihaPd2erDuQvees1s2o8ikFmZuNF7WQE63PPGG2SXHZk5vXrHfe7nm4cj4fo7cE3Aq48ksMjP6ugPxAhqn?cluster=devnet"

   ✓ Workflow Simulation Result:
   {
     "$typeName": "capabilities.blockchain.solana.v1alpha.WriteReportReply",
     "receiverContractExecutionStatus": 0,
     "transactionFee": 5000,
     "txSignature": "Hkwkf/rL44luYyMfBIOJ9JgyLUUFUcMChfbqDTGcCND3zN9RziakjeWeywBkcjgXpYBRc23eVFM0BYeLcuMNBQ==",
     "txStatus": 2
   }
   ```

   Open the explorer link to confirm the transaction onchain: the mock forwarder is the top-level program and
   `cre_docs_receiver` appears as a CPI target.

   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).

You can use the `cre_docs_receiver` example receiver as-is — it's deployed on Devnet and ready to accept writes. If you
have your own receiver program, update the IDL address, regenerate bindings, and set `receiverProgramId` to your own
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`. Keep your `receiverProgramId` and swap in the production Keystone Forwarder
   addresses:

   ```json
   {
     "schedule": "30 */5 * * * *",
     "chainSelector": "16423721717087811551",
     "receiverProgramId": "7k8NypziCPqVY8GYyCw7aqaR5aamFAMdH5ZaHVrZCS94",
     "forwarderProgramId": "CXsKEJcs25TQEYU2e5jZ8QTPE3ffMLZhH6BWHrdcCCB5",
     "forwarderState": "8QoomCQyPSkJ8WopJbX9B4HyvrFzziwvJdU8hZE6DCr9"
   }
   ```

   These are the Solana Devnet production Keystone Forwarder addresses. For Mainnet, see the
   [forwarder address table](#production-keystone-forwarder) above. If you are using your own receiver program, replace
   `receiverProgramId` with your own value. The workflow still derives
   `forwarderAuthority` from `[]byte("forwarder")`, forwarder state bytes, and receiver program bytes under that
   `forwarderProgramId`.

2. Add a `production-settings` target to your existing `my-workflow/workflow.yaml` (append it below the
   `staging-settings` block you created in Step 4 — do not replace the staging block).

   ```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 a `production-settings` block in your `project.yaml` (at your project root —
   append it below the existing `staging-settings` block). The URL can be the same as staging.

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, check that the workflow executed successfully. You can view executions and logs in the
   [CRE platform](https://app.chain.link/cre/workflows), or use the CLI. List recent executions:

   ```bash
   cre execution list my-solana-write
   ```

   ```
   Executions

   1. 47517057f8ecb7455ddf9eb58d4a635e96bd968158146ed1973f9591cfdc1421
        Workflow:  my-solana-write
        Status:    SUCCESS
        Started:   2026-08-20 19:50:31 UTC
        Finished:  2026-08-20 19:50:43 UTC (12s)
   ```

6. View the execution logs to confirm the write reached Solana:

   ```bash
   cre execution logs <execution-id>
   ```

   Each DON node executes the workflow and logs the write result. A successful execution shows all nodes reaching
   `txStatus: SUCCESS` with a real on-chain transaction hash:

   ```
   [2026-08-20 19:50:31 UTC] [Node 1] Submitting UserData to Solana cre_docs_receiver program
   [2026-08-20 19:50:31 UTC] [Node 6] Submitting UserData to Solana cre_docs_receiver program
   ...
   [2026-08-20 19:50:43 UTC] [Node 1] Solana write result:
   [2026-08-20 19:50:43 UTC] [Node 1]   txStatus:       SUCCESS (2)
   [2026-08-20 19:50:43 UTC] [Node 1]   receiverStatus: (n/a)
   [2026-08-20 19:50:43 UTC] [Node 1]   transactionFee: 0 lamports
   [2026-08-20 19:50:43 UTC] [Node 1]   txHash:         2dJiCNgbGGhoMd7LSQH9FoUCWwUSzFyP8JHDzNrfcsGZcroSt1rsVPNzgq6LbMG8vjTzJPgeoGaViSABdeqa2v62
   [2026-08-20 19:50:43 UTC] [Node 1]   explorer:       https://explorer.solana.com/tx/2dJiCNgbGGhoMd7LSQH9FoUCWwUSzFyP8JHDzNrfcsGZcroSt1rsVPNzgq6LbMG8vjTzJPgeoGaViSABdeqa2v62?cluster=devnet
   ```

   Open the explorer link to confirm the transaction onchain: the Keystone Forwarder is the top-level program and
   `cre_docs_receiver` appears as a CPI target. All nodes share the same `txHash` because the DON reaches consensus on
   a single transaction.

   When you are done testing, pause the workflow to stop it from running:

   ```bash
   cre workflow pause my-workflow
   ```

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 and re-computes the account hash over the full account list.
4. The forwarder supplies `forwarderState` + `forwarderAuthority` and CPIs into your program's `on_report` instruction
   with the decoded payload and your receiver-specific accounts (indices 2+).

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 Read & Write Capabilities](/cre/capabilities/solana-read-write)**: Architecture and account layout reference