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 solana.FilterLogTriggerRequest watches a single program address, not a list, so you register one trigger per
program you want to watch.
Prerequisites
- A CRE Go 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 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
PriceUpdatedevent. 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
-
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" } ] } } ] } -
From your project root, generate the bindings:
cre generate-bindings solana --language goThis creates
contracts/solana/src/generated/cre_docs_log_trigger_example/with aLogTriggerPriceUpdatedLog(...)method and aPriceUpdatedFiltersstruct for filtering, intriggers.go. -
Sync dependencies.
go mod tidy
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.
{
"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.
-
Save the following as
my-workflow/workflow.go. Replace<project-name>with your Go module name fromgo.mod.// my-workflow/workflow.go package main import ( "fmt" "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/blockchain/solana/bindings" "github.com/smartcontractkit/cre-sdk-go/cre" "<project-name>/contracts/solana/src/generated/cre_docs_log_trigger_example" ) type Config struct { ChainSelector uint64 `json:"chainSelector,string"` Asset string `json:"asset"` } func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) { client := &solana.Client{ChainSelector: config.ChainSelector} // NewCreDocsLogTriggerExample uses the program ID baked into the IDL (regenerate bindings when it changes). feed, err := cre_docs_log_trigger_example.NewCreDocsLogTriggerExample(client) if err != nil { return nil, err } // Only fire for updates to this asset; ignore PriceUpdated events for every other asset // published through the same program. logTrigger, err := feed.LogTriggerPriceUpdatedLog( config.ChainSelector, "price-updated-"+config.Asset, []cre_docs_log_trigger_example.PriceUpdatedFilters{ {Asset: &config.Asset}, }, nil, // opts: PriceUpdated is emitted with Anchor's emit!, not emit_cpi!. ) if err != nil { return nil, fmt.Errorf("failed to create log trigger: %w", err) } return cre.Workflow[*Config]{ cre.Handler(logTrigger, onPriceUpdated), }, nil } func onPriceUpdated(config *Config, runtime cre.Runtime, payload *bindings.DecodedLog[cre_docs_log_trigger_example.PriceUpdated]) (string, error) { logger := runtime.Logger() // payload.Data is the decoded PriceUpdated event; payload.Log is the raw log metadata. var txSig solanago.Signature copy(txSig[:], payload.Log.GetTxHash()) logger.Info("PriceUpdated event", "asset", payload.Data.Asset, "price", payload.Data.Price, "blockNumber", payload.Log.GetBlockNumber(), "txHash", txSig.String(), ) return "Success", nil }
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:
const {
Connection,
Keypair,
PublicKey,
Transaction,
TransactionInstruction,
sendAndConfirmTransaction,
} = require("@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, price) {
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.
-
Create a
workflow.yamlin 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.go" config-path: "./config.staging.json" secrets-path: "" -
Add a Solana RPC endpoint to your
project.yaml(at your project root, not insidemy-workflow/) under your simulation target.staging-settings: rpcs: - chain-name: solana-devnet url: https://api.devnet.solana.com -
Set
CRE_SOLANA_PRIVATE_KEYin 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_KEYis required too, for the same workflow-owner-derivation reason described there. -
Run the simulation, passing the transaction signature and event index non-interactively.
publish_priceemits exactly one event with nothing else in the transaction, so the event index is always0.cre workflow simulate my-workflow \ --non-interactive \ --trigger-index 0 \ --solana-tx-sig Ub8Xo7QMik6eLewpecdZCANc7CLmsJP7vd7bCLhDoe7fNXmRM7fL4qEx3drMGkMbE4Sh8W7nL49EV3tijziTy4z \ --solana-event-index 0 \ --target staging-settingsThe simulator fetches the transaction, extracts the
PriceUpdatedevent, decodes it, and runs your handler:✓ Workflow compiled [SIMULATION] Simulator Initialized [SIMULATION] Running trigger trigger=solana:ChainSelector:16423721717087811551@1.0.0 [USER LOG] msg="PriceUpdated event" asset=SOL/USD price=15000 blockNumber=493117543 txHash=Ub8Xo7QMik6eLewpecdZCANc7CLmsJP7vd7bCLhDoe7fNXmRM7fL4qEx3drMGkMbE4Sh8W7nL49EV3tijziTy4z ✓ Workflow Simulation Result: "Success"To use interactive mode instead, drop
--non-interactiveand its accompanying flags and you will be prompted for the transaction signature and event index. 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
- Solana Log Trigger SDK Reference: Full filter configuration and payload reference
- Writing to Solana: Submit CRE workflow results to a Solana program
- The Trigger Capability: Conceptual overview of all CRE trigger types