# Solana Log Trigger
Source: https://docs.chain.link/cre/guides/workflow/using-triggers/solana-log-trigger-ts
Last Updated: 2026-10-19

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

The Solana Log trigger fires when a specific event is emitted by a program on Solana (via Anchor's `emit!` or
`emit_cpi!`). This guide walks through triggering a workflow whenever a price feed program publishes an update for
an asset you care about, using the same generated-bindings pattern as [Writing to
Solana](/cre/guides/workflow/using-solana-client/onchain-write-ts).

A `FilterLogTriggerRequestJson` watches a single program address, not a list, so you register one trigger per
program you want to watch.

## 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.
- The latest version of the CRE CLI (`cre version` / `cre update`); **v1.33.0 or later** is required for TypeScript Solana log trigger binding generation.
- A funded Solana Devnet keypair for `CRE_SOLANA_PRIVATE_KEY` (required for every Solana simulation, including a trigger-only dry run). See [Step 4](#step-4-run-the-simulation).
- A Devnet transaction signature that already emitted a `PriceUpdated` event. Use the signature provided in [Step
  4](#step-4-run-the-simulation), or [publish your own](#producing-a-test-event).

## What you need: an event to trigger on

This guide uses a minimal example program deployed to Solana Devnet at
[`67qyQ7aDLwamUFCbz6xzB5dayveVStif9tkis9rK1jNY`](https://explorer.solana.com/address/67qyQ7aDLwamUFCbz6xzB5dayveVStif9tkis9rK1jNY?cluster=devnet).
It has one permissionless instruction, `publish_price`, which anyone can call directly. There are no accounts, no
state, and no CRE involvement in publishing. Calling it emits a `PriceUpdated` event with the data at the **top
level** of the event, which matters for filtering (see [Step 3](#step-3-write-the-workflow)):

```rust
// cre_docs_log_trigger_example (abridged)
pub fn publish_price(ctx: Context<Publish>, asset: String, price: u64) -> Result<()> {
    emit!(PriceUpdated { asset, price });
    Ok(())
}

#[event]
pub struct PriceUpdated {
    pub asset: String,
    pub price: u64,
}
```

Because this program is already deployed, you do not need to deploy anything to follow this guide. You only need
one Devnet transaction that already called `publish_price`.

## Step 1: Add the IDL and generate bindings

1. Save the following as `contracts/solana/src/idl/cre_docs_log_trigger_example.json`.

   ```json
   {
     "address": "67qyQ7aDLwamUFCbz6xzB5dayveVStif9tkis9rK1jNY",
     "metadata": {
       "name": "cre_docs_log_trigger_example",
       "version": "0.1.0",
       "spec": "0.1.0",
       "description": "Minimal permissionless price-feed example for the CRE Solana Log Trigger docs guide"
     },
     "instructions": [
       {
         "name": "publish_price",
         "discriminator": [117, 13, 6, 171, 29, 204, 11, 1],
         "accounts": [],
         "args": [
           { "name": "asset", "type": "string" },
           { "name": "price", "type": "u64" }
         ]
       }
     ],
     "accounts": [],
     "events": [
       {
         "name": "PriceUpdated",
         "discriminator": [154, 72, 87, 150, 246, 230, 23, 217]
       }
     ],
     "errors": [],
     "types": [
       {
         "name": "PriceUpdated",
         "type": {
           "kind": "struct",
           "fields": [
             { "name": "asset", "type": "string" },
             { "name": "price", "type": "u64" }
           ]
         }
       }
     ]
   }
   ```

2. From your workflow directory, generate the bindings:

   ```bash
   cd my-workflow
   cre generate-bindings solana
   ```

   This creates `contracts/solana/ts/generated/CreDocsLogTriggerExample.ts` (plus a mock and `index.ts`). The class
   includes a `logTriggerPriceUpdatedLog(...)` method and a `PriceUpdatedFilters` type for filtering.

## Step 2: Configure the workflow

Open (or create) `config.staging.json`. Every log trigger needs the chain to watch, which you set with `chainSelector`;
the IDL baked into the generated bindings supplies the program address. The `asset` field is specific to this example:
it is not a general CRE or Solana concept, just a config value this guide uses to demonstrate filtering on one of
`PriceUpdated`'s fields. Your own program's events will have their own fields to filter on instead.

```json
{
  "chainSelector": "16423721717087811551",
  "asset": "SOL/USD"
}
```

| Field           | Description                                                                               |
| --------------- | ----------------------------------------------------------------------------------------- |
| `chainSelector` | Solana Devnet (`"16423721717087811551"`). Store as a string to avoid JSON precision loss. |
| `asset`         | Specific to this example: the `PriceUpdated.asset` value to filter on.                    |

## Step 3: Write the workflow

This step is a complete, pasteable workflow that logs the decoded `PriceUpdated` event whenever the price feed
publishes an update for the configured asset, using the generated `logTriggerPriceUpdatedLog` binding helper.

The generated bindings import from `@solana/codecs` and `@solana/addresses`, and this workflow also imports
`@solana/codecs-strings` (to base58-encode the transaction signature). If you run `tsc --noEmit` outside of
`cre workflow simulate`, install them in your workflow folder:
`npm install @solana/codecs @solana/addresses @solana/codecs-strings`.

1. Save the following as `my-workflow/main.ts`.

   ```typescript
   // my-workflow/main.ts
   import { handler, Runner, SolanaClient, type Runtime } from "@chainlink/cre-sdk"
   import { getBase58Decoder } from "@solana/codecs-strings"
   import {
     CreDocsLogTriggerExample,
     type PriceUpdated,
   } from "./contracts/solana/ts/generated/CreDocsLogTriggerExample"
   import type { SolanaDecodedLog } from "@chainlink/cre-sdk"

   type Config = {
     chainSelector: string
     asset: string
   }

   const onPriceUpdated = (runtime: Runtime<Config>, payload: SolanaDecodedLog<PriceUpdated>) => {
     const { log, data } = payload

     // log.txHash is raw bytes; base58-encode it for explorer links, matching Solana's usual signature format.
     const txHash = getBase58Decoder().decode(log.txHash)

     runtime.log(`PriceUpdated event: asset=${data.asset} price=${data.price}`)
     runtime.log(`  blockNumber: ${log.blockNumber}`)
     runtime.log(`  txHash:      ${txHash}`)

     return "Success"
   }

   export const initWorkflow = (config: Config) => {
     const client = new SolanaClient(BigInt(config.chainSelector))
     const feed = new CreDocsLogTriggerExample(client)

     // Only fire for updates to this asset; ignore PriceUpdated events for every other asset
     // published through the same program.
     const logTrigger = feed.logTriggerPriceUpdatedLog(`price-updated-${config.asset}`, [{ asset: config.asset }])

     return [handler(logTrigger, onPriceUpdated)]
   }

   export async function main() {
     const runner = await Runner.newRunner<Config>()
     await runner.run(initWorkflow)
   }
   ```

> **NOTE: Filtering by event fields**
>
> The second argument to `logTrigger<EventName>Log` filters on the event's top-level scalar fields. Both `asset` and
> `price` are top-level here, so both are filterable: `[{ asset: config.asset }]` only fires for updates to that
> asset. Nested objects, arrays, `boolean`, and 128-bit integers are not auto-filterable; if your own program needs
> to filter on a field like that, emit it as a top-level scalar instead. See the [SDK
> reference](/cre/reference/sdk/triggers/solana-log-trigger-ts) for the full filtering model.

## Producing a test event

The transaction signature used in [Step 4](#step-4-run-the-simulation) already works, so you can skip this section
on a first read. To test with your own data, you need a fresh transaction. This script sends a real Solana
transaction that calls `publish_price` directly, entirely outside of CRE. That call is the action that emits the
`PriceUpdated` event your workflow reacts to. The instruction is permissionless, so any funded Devnet keypair can
call it. For example, using `@solana/web3.js` in a short script:

```typescript
import {
  Connection,
  Keypair,
  PublicKey,
  Transaction,
  TransactionInstruction,
  sendAndConfirmTransaction,
} from "@solana/web3.js"

const PROGRAM_ID = new PublicKey("67qyQ7aDLwamUFCbz6xzB5dayveVStif9tkis9rK1jNY")
const DISCRIMINATOR = Buffer.from([117, 13, 6, 171, 29, 204, 11, 1]) // sha256("global:publish_price")[:8]

function encodeArgs(asset: string, price: number): Buffer {
  const assetBytes = Buffer.from(asset, "utf8")
  const lenBuf = Buffer.alloc(4)
  lenBuf.writeUInt32LE(assetBytes.length, 0)
  const priceBuf = Buffer.alloc(8)
  priceBuf.writeBigUInt64LE(BigInt(price), 0)
  return Buffer.concat([DISCRIMINATOR, lenBuf, assetBytes, priceBuf])
}

async function main() {
  const payer = Keypair.fromSecretKey(/* your funded Devnet keypair bytes */)
  const connection = new Connection("https://api.devnet.solana.com", "confirmed")
  const ix = new TransactionInstruction({ programId: PROGRAM_ID, keys: [], data: encodeArgs("SOL/USD", 15000) })
  const sig = await sendAndConfirmTransaction(connection, new Transaction().add(ix), [payer])
  console.log("Signature:", sig)
}

main()
```

## Step 4: Run the simulation

Solana log trigger simulation replays a real onchain event: you give the simulator a transaction signature and an
event index, and it fetches that event from Devnet.

> **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.ts` 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`) to link a workflow to its RPCs.

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-log-trigger"
     workflow-artifacts:
       workflow-path: "./main.ts"
       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 funded 64-byte base58 keypair for
   every run, even a trigger-only dry run that never submits a transaction. See [Writing to Solana: Step
   4](/cre/guides/workflow/using-solana-client/onchain-write-ts#step-4-run-the-simulation) for how to generate and
   fund one. `CRE_ETH_PRIVATE_KEY` is required too, for the same workflow-owner-derivation reason described there.

4. Run the simulation, passing the transaction signature and event index non-interactively. `publish_price` emits
   exactly one event with nothing else in the transaction, so the event index is always `0`.

   ```bash
   cre workflow simulate my-workflow \
     --non-interactive \
     --trigger-index 0 \
     --solana-tx-sig Ub8Xo7QMik6eLewpecdZCANc7CLmsJP7vd7bCLhDoe7fNXmRM7fL4qEx3drMGkMbE4Sh8W7nL49EV3tijziTy4z \
     --solana-event-index 0 \
     --target staging-settings
   ```

   The simulator fetches the transaction, extracts the `PriceUpdated` event, decodes it, and runs your handler:

   ```
   ✓ Workflow compiled
   [SIMULATION] Simulator Initialized
   [SIMULATION] Running trigger trigger=solana:ChainSelector:16423721717087811551@1.0.0
   [USER LOG] PriceUpdated event: asset=SOL/USD price=15000
   [USER LOG]   blockNumber: 493117543
   [USER LOG]   txHash:      Ub8Xo7QMik6eLewpecdZCANc7CLmsJP7vd7bCLhDoe7fNXmRM7fL4qEx3drMGkMbE4Sh8W7nL49EV3tijziTy4z

   ✓ Workflow Simulation Result: "Success"
   ```

   To use interactive mode instead, drop `--non-interactive` and its accompanying flags and you will be prompted for the
   transaction signature and event index. See [Simulating Workflows: Solana log
   trigger](/cre/guides/operations/simulating-workflows#solana-log-trigger) for both modes.

## Deploying the workflow

Once your handler behaves as expected in simulation, deploy it the same way as any other CRE workflow. See [Deploying
to the Private Registry](/cre/guides/operations/deploying-to-private-registry-ts). A deployed log trigger runs
continuously: the DON watches for new `PriceUpdated` events onchain and runs your handler each time one occurs for
your configured asset, without you needing to supply a transaction signature.

To verify a deployed workflow, emit a fresh `PriceUpdated` event after deployment (see [Producing a test
event](#producing-a-test-event)) and check the workflow's executions with `cre execution list`. A deployed log
trigger only reacts to events emitted **after** it is deployed — it does not replay events that happened before.

## Next steps

- **[Solana Log Trigger SDK Reference](/cre/reference/sdk/triggers/solana-log-trigger-ts)**: Full filter configuration and payload reference
- **[Writing to Solana](/cre/guides/workflow/using-solana-client/onchain-write-ts)**: Submit CRE workflow results to a Solana program
- **[The Trigger Capability](/cre/capabilities/triggers)**: Conceptual overview of all CRE trigger types