Submitting Reports Onchain
This guide shows how to manually submit a generated CRE report to the blockchain using the low-level evm.Client.WriteReport() method. The forwarder verifies signatures onchain: no separate offchain verification step.
Use this approach when:
- You've already generated a report using
runtime.GenerateReport()(from single value or struct generation) - You need fine-grained control over the submission process
- You don't have (or can't use) the
WriteReportFrom<StructName>()binding helper
Prerequisites
You must have:
- A generated report ready to submit (from single value or struct generation)
- A consumer contract address that implements the
IReceiverinterface
Step-by-step example
Step 1: Create an EVM client
import "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm"
evmClient := &evm.Client{
ChainSelector: config.ChainSelector, // e.g., 16015286601757825753 for Sepolia
}
Step 2: Prepare submission parameters
import "github.com/ethereum/go-ethereum/common"
// Receiver contract address (must implement IReceiver interface)
receiverAddress := common.HexToAddress(config.ReceiverAddress)
// Optional gas configuration
gasConfig := &evm.GasConfig{
GasLimit: config.GasLimit, // e.g., 1000000
}
Step 3: Submit the report
writePromise := evmClient.WriteReport(runtime, &evm.WriteCreReportRequest{
Receiver: receiverAddress.Bytes(),
Report: report, // The report from runtime.GenerateReport()
GasConfig: gasConfig,
})
resp, err := writePromise.Await()
if err != nil {
return fmt.Errorf("failed to write report: %w", err)
}
// Extract transaction hash
txHash := fmt.Sprintf("0x%x", resp.TxHash)
logger.Info("Report submitted successfully", "txHash", txHash)
Understanding the response
The WriteReportReply struct provides comprehensive transaction details:
type WriteReportReply struct {
TxStatus TxStatus // SUCCESS, REVERTED, or FATAL
ReceiverContractExecutionStatus *ReceiverContractExecutionStatus // Contract execution status
TxHash []byte // Transaction hash
TransactionFee *pb.BigInt // Fee paid in Wei
ErrorMessage *string // Error message if failed
}
Key fields to check:
TxStatus: Indicates whether the transaction succeeded, reverted, or had a fatal errorTxHash: The transaction hash you can use to verify on a block explorer (e.g., Etherscan)TransactionFee: The total gas cost paid for the transaction in WeiReceiverContractExecutionStatus: Whether your consumer contract'sonReport()function executed successfullyErrorMessage: If the transaction failed, this field contains details about what went wrong
Best practices
When submitting reports onchain, follow these practices to ensure reliability and observability:
-
Log transaction details: Always log the transaction hash for debugging and monitoring. This allows you to track your submission on block explorers and troubleshoot issues.
txHash := fmt.Sprintf("0x%x", resp.TxHash) logger.Info("Report submitted successfully", "txHash", txHash, "status", resp.TxStatus) -
Handle gas configuration: Provide explicit gas limits for complex transactions to avoid out-of-gas errors. Adjust based on your contract's complexity and the data size.
gasConfig := &evm.GasConfig{ GasLimit: 500000, // Adjust based on your needs } -
Monitor transaction status: Always check the
TxStatusfield in the response to ensure your transaction was successful. HandleREVERTEDandFATALstatuses appropriately.if resp.TxStatus != evm.TxStatusSuccess { return fmt.Errorf("transaction failed with status: %v, error: %s", resp.TxStatus, *resp.ErrorMessage) }
Complete example
Here's a full workflow that generates a report from a single value and submits it onchain:
//go:build wasip1
package main
import (
"fmt"
"log/slog"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm"
"github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
"github.com/smartcontractkit/cre-sdk-go/cre"
"github.com/smartcontractkit/cre-sdk-go/cre/wasm"
)
type Config struct {
Schedule string `json:"schedule"`
ReceiverAddress string `json:"receiverAddress"`
ChainSelector uint64 `json:"chainSelector"`
GasLimit uint64 `json:"gasLimit"`
}
type MyResult struct {
TxHash string
}
func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
return cre.Workflow[*Config]{
cre.Handler(cron.Trigger(&cron.Config{Schedule: config.Schedule}), onCronTrigger),
}, nil
}
func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
logger := runtime.Logger()
// Step 1: Create and encode a value
myValue := big.NewInt(123456789)
logger.Info("Created value to encode", "value", myValue.String())
uint256Type, err := abi.NewType("uint256", "", nil)
if err != nil {
return nil, fmt.Errorf("failed to create type: %w", err)
}
args := abi.Arguments{{Type: uint256Type}}
encodedValue, err := args.Pack(myValue)
if err != nil {
return nil, fmt.Errorf("failed to encode value: %w", err)
}
logger.Info("Encoded value", "hex", fmt.Sprintf("0x%x", encodedValue))
// Step 2: Generate report
reportPromise := runtime.GenerateReport(&cre.ReportRequest{
EncodedPayload: encodedValue,
EncoderName: "evm",
SigningAlgo: "ecdsa",
HashingAlgo: "keccak256",
})
report, err := reportPromise.Await()
if err != nil {
return nil, fmt.Errorf("failed to generate report: %w", err)
}
logger.Info("Report generated successfully")
// Step 3: Create EVM client
evmClient := &evm.Client{
ChainSelector: config.ChainSelector,
}
// Step 4: Submit report onchain
receiverAddress := common.HexToAddress(config.ReceiverAddress)
gasConfig := &evm.GasConfig{GasLimit: config.GasLimit}
writePromise := evmClient.WriteReport(runtime, &evm.WriteCreReportRequest{
Receiver: receiverAddress.Bytes(),
Report: report,
GasConfig: gasConfig,
})
logger.Info("Submitting report onchain...")
resp, err := writePromise.Await()
if err != nil {
return nil, fmt.Errorf("failed to submit report: %w", err)
}
// Check transaction status
if resp.TxStatus != evm.TxStatusSuccess {
errorMsg := "unknown error"
if resp.ErrorMessage != nil {
errorMsg = *resp.ErrorMessage
}
return nil, fmt.Errorf("transaction failed with status %v: %s", resp.TxStatus, errorMsg)
}
txHash := fmt.Sprintf("0x%x", resp.TxHash)
logger.Info("Report submitted successfully", "txHash", txHash, "fee", resp.TransactionFee)
return &MyResult{TxHash: txHash}, nil
}
func main() {
wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow)
}
Configuration file (config.json):
{
"schedule": "0 */1 * * * *",
"receiverAddress": "0xYourReceiverContractAddress",
"chainSelector": 16015286601757825753,
"gasLimit": 1000000
}
Broadcasting transactions
By default, cre workflow simulate performs a dry run without broadcasting transactions to the network. To execute real onchain transactions, use the --broadcast flag:
cre workflow simulate my-workflow --broadcast --target staging-settings
See the CLI Reference for more details.
Troubleshooting
"failed to submit report" or transaction fails to broadcast
- Verify your consumer contract address is correct and deployed on the target chain
- Check that you're using the correct chain selector for your target blockchain
- Verify network connectivity and RPC endpoint availability
Transaction succeeds but TxStatus is REVERTED
- Check the
ErrorMessagefield for details about why the transaction reverted - Verify your consumer contract implements the
IReceiverinterface correctly (see Building Consumer Contracts) - Review your consumer contract's
onReport()validation logicāit may be rejecting the report - Ensure the report data format matches what your consumer contract expects
- Important: A reverted transaction opens a replay window. The forwarder does not mark reverted reports as used, meaning anyone can resubmit the signed report once conditions change. If your workflow takes corrective action after seeing a revert, see Same-chain replay on failure for how to protect against double-execution
"out of gas" error or transaction runs out of gas
- Increase the
GasLimitin yourGasConfig - Check if your consumer contract's
onReport()function has unexpectedly complex logic - Review the transaction on a block explorer to see the actual gas used
ReceiverContractExecutionStatus indicates failure
- Your consumer contract's
onReport()function executed but encountered an error - Review the contract's event logs and error messages on a block explorer
- Check that your contract's validation logic (e.g., forwarder checks, workflow ID checks) is correctly configured
- Verify the decoded data in your contract matches the expected struct/value format
"invalid receiver address" or address-related errors
- Confirm the receiver address is a valid Ethereum address format
- Verify the contract is deployed at that address on the target chain
- Use
common.HexToAddress()to properly convert address strings
Learn more
- Onchain Write Overview: Understand all onchain write approaches
- Using WriteReportFrom Helpers: Use the simpler all-in-one helper for struct submission
- Generating Reports: Single Values: Generate reports for single primitive values
- Generating Reports: Structs: Generate reports for struct data
- Building Consumer Contracts: Create contracts that can receive reports
- EVM Client Reference: Complete API documentation