Solana Log Trigger

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.

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 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.
  • A Devnet transaction signature that already emitted a PriceUpdated event. Use the signature provided in Step 4, or publish your own.

What you need: an event to trigger on

This guide uses a minimal example program deployed to Solana Devnet at 67qyQ7aDLwamUFCbz6xzB5dayveVStif9tkis9rK1jNY. 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):

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

    {
      "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:

    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 comes from the IDL baked into the generated bindings. 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.

{
  "chainSelector": "16423721717087811551",
  "asset": "SOL/USD"
}
FieldDescription
chainSelectorSolana Devnet ("16423721717087811551"). Store as a string to avoid JSON precision loss.
assetSpecific 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.

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

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

Producing a test event

The transaction signature used in Step 4 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:

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.

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

    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.

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

    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"
    

    Omit interactive mode (drop --non-interactive and its accompanying flags) to be prompted for the transaction signature and event index instead. See 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. 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) 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

Get the latest Chainlink content straight to your inbox.