Generating Reports: Structs
This guide shows how to generate a CRE report containing a struct with multiple fields. See Key Terms: Report for the full definition. After generation, deliver it onchain or via HTTP; HTTP receivers should verify offchain. See API Interactions: CRE reports over HTTP.
There are two encoding approaches depending on whether you have generated bindings for your contract.
Choosing your approach
Use this table to determine which method applies to your situation:
| Situation | Binding Helper Available? | Recommended Approach | Section |
|---|---|---|---|
Struct appears in a public or external function's signature (as a parameter or return value) | Yes, Codec.Encode<StructName>Struct() exists | Use the binding's encoding helper | Using Binding Helpers |
| Struct is NOT in the ABI | No helper available | Manual tuple encoding | Manual Encoding |
Don't meet these requirements? If you're sending a single value instead of a struct, see Generating Reports: Single Values. For other approaches, see the Onchain Write hub page.
Using binding helpers
If you have generated bindings for a contract that includes your struct in its ABI, the binding generator creates an Encode<StructName>Struct() method on the Codec. This is the simplest and recommended approach.
When this applies
This method is available when your struct appears in a public or external function's signature (as a parameter or return value). These function types appear in the contract's ABI, which allows the binding generator to detect the struct and automatically create the encoding helper.
Example contract:
contract MyContract {
struct PaymentData {
address recipient;
uint256 amount;
uint256 nonce;
}
// Because this public function uses PaymentData,
// the binding generator creates an encoding helper
function processPayment(PaymentData memory data) public {}
}
Step 1: Identify the helper method
After running cre generate-bindings, your binding will include:
type MyContractCodec interface {
// ... other methods
EncodePaymentDataStruct(in PaymentData) ([]byte, error)
// ...
}
Step 2: Use the helper
import "my-project/contracts/evm/src/generated/my_contract"
// Create your struct
paymentData := my_contract.PaymentData{
Recipient: common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"),
Amount: big.NewInt(1000000000000000000),
Nonce: big.NewInt(42),
}
// Create contract instance to access the Codec
contract, err := my_contract.NewMyContract(evmClient, contractAddress, nil)
if err != nil {
return err
}
// Use the encoding helper
encodedStruct, err := contract.Codec.EncodePaymentDataStruct(paymentData)
if err != nil {
return fmt.Errorf("failed to encode struct: %w", err)
}
Step 3: Generate the report
reportPromise := runtime.GenerateReport(&cre.ReportRequest{
EncodedPayload: encodedStruct,
EncoderName: "evm",
SigningAlgo: "ecdsa",
HashingAlgo: "keccak256",
})
report, err := reportPromise.Await()
if err != nil {
return fmt.Errorf("failed to generate report: %w", err)
}
Understanding the report
The runtime.GenerateReport() function returns a *cre.Report object. This report contains:
- Your ABI-encoded struct data (the payload)
- Cryptographic signatures from the DON nodes
- Metadata about the workflow (ID, name, owner)
- Consensus proof that the data was agreed upon by the network
This report is designed to be passed directly to either:
evm.Client.WriteReport()for onchain deliveryhttp.Clientfor offchain delivery
The report can now be submitted onchain or sent via HTTP.
Manual encoding
If your struct is not in the contract's ABI, you won't have a binding helper and must manually create the tuple type and encode it.
When to use this approach
- You're working with a custom struct that doesn't appear in any
publicorexternalfunction's signature - You're encoding data for a third-party contract without bindings
- You need full control over the encoding process
Step-by-step example
Let's manually encode a PaymentData struct:
struct PaymentData {
address recipient;
uint256 amount;
uint256 nonce;
}
1. Define the Go struct
Create a Go struct that matches your Solidity struct:
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
type PaymentData struct {
Recipient common.Address
Amount *big.Int
Nonce *big.Int
}
2. Create your struct instance
paymentData := PaymentData{
Recipient: common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"),
Amount: big.NewInt(1000000000000000000), // 1 ETH in wei
Nonce: big.NewInt(42),
}
3. Create the tuple type
Define the struct's fields as a tuple using abi.NewType():
import "github.com/ethereum/go-ethereum/accounts/abi"
tupleType, err := abi.NewType(
"tuple", "",
[]abi.ArgumentMarshaling{
{Name: "recipient", Type: "address"},
{Name: "amount", Type: "uint256"},
{Name: "nonce", Type: "uint256"},
},
)
if err != nil {
return fmt.Errorf("failed to create tuple type: %w", err)
}
Important: The field names and types must match your Solidity struct exactly.
4. ABI-encode the struct
args := abi.Arguments{
{Name: "paymentData", Type: tupleType},
}
encodedStruct, err := args.Pack(paymentData)
if err != nil {
return fmt.Errorf("failed to encode struct: %w", err)
}
5. Generate the report
reportPromise := runtime.GenerateReport(&cre.ReportRequest{
EncodedPayload: encodedStruct,
EncoderName: "evm",
SigningAlgo: "ecdsa",
HashingAlgo: "keccak256",
})
report, err := reportPromise.Await()
if err != nil {
return fmt.Errorf("failed to generate report: %w", err)
}
logger.Info("Report generated successfully")
Complete working example
Here's a full workflow that generates a report from a struct:
//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/scheduler/cron"
"github.com/smartcontractkit/cre-sdk-go/cre"
"github.com/smartcontractkit/cre-sdk-go/cre/wasm"
)
type Config struct {
Schedule string `json:"schedule"`
}
// Go struct matching Solidity struct
type PaymentData struct {
Recipient common.Address
Amount *big.Int
Nonce *big.Int
}
type MyResult struct {
EncodedHex 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 struct instance
paymentData := PaymentData{
Recipient: common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"),
Amount: big.NewInt(1000000000000000000), // 1 ETH
Nonce: big.NewInt(42),
}
logger.Info("Created payment data", "recipient", paymentData.Recipient.Hex(), "amount", paymentData.Amount.String())
// Step 2: Create tuple type matching Solidity struct
tupleType, err := abi.NewType(
"tuple", "",
[]abi.ArgumentMarshaling{
{Name: "recipient", Type: "address"},
{Name: "amount", Type: "uint256"},
{Name: "nonce", Type: "uint256"},
},
)
if err != nil {
return nil, fmt.Errorf("failed to create tuple type: %w", err)
}
// Step 3: Encode the struct
args := abi.Arguments{{Name: "paymentData", Type: tupleType}}
encodedStruct, err := args.Pack(paymentData)
if err != nil {
return nil, fmt.Errorf("failed to encode struct: %w", err)
}
logger.Info("Encoded struct", "hex", fmt.Sprintf("0x%x", encodedStruct))
// Step 4: Generate report
reportPromise := runtime.GenerateReport(&cre.ReportRequest{
EncodedPayload: encodedStruct,
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")
// At this point, you would typically submit the report:
// - To the blockchain: see "Submitting Reports Onchain" guide
// - Via HTTP: see "Submitting Reports via HTTP" guide
// For this example, we'll just return the encoded data for verification
_ = report // Report is ready to use
return &MyResult{
EncodedHex: fmt.Sprintf("0x%x", encodedStruct),
}, nil
}
func main() {
wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow)
}
Best practices
- Always check errors: Both encoding and report generation can failāhandle both error paths
- Use binding helpers when available: The
Codec.Encode<StructName>Struct()helper is simpler and less error-prone than manual encoding - Match Solidity types exactly: For manual encoding, ensure your tuple definition matches your Solidity struct field-by-field, including order and types
- Log the encoded data: For debugging, log the hex-encoded bytes to verify your struct is encoded correctly:
logger.Info("ABI-encoded struct", "hex", fmt.Sprintf("0x%x", encodedStruct))
Troubleshooting
"failed to create tuple type" error
- Verify the field types in your
ArgumentMarshalingmatch Solidity exactly (e.g.,uint256, notuintorint) - Ensure field names match
- Check that nested types are properly defined if you have complex structs
"failed to encode struct" error
- Verify your Go struct fields match the Solidity struct in order and type
- Ensure you're using the correct Go types (e.g.,
*big.Intforuint256,common.Addressforaddress). A list of mappings can be found here. - Check that all fields are populated (Go's zero values might not match what you expect)
Binding helper not found
- Confirm your struct is used in a
publicorexternalfunction parameter in your contract - Verify you've run
cre generate-bindingsafter updating your contract - Check the generated binding fileāthe method should be named
Encode<YourStructName>Struct()
Report generation succeeds but onchain submission fails
- This guide only covers report generation. See Submitting Reports Onchain for troubleshooting submission issues
Learn more
- Onchain Write Overview: Understand all onchain write approaches
- Submitting Reports Onchain: Submit your generated report to the blockchain
- Generating Reports: Single Values: Generate reports for single primitive values
- Using WriteReportFrom Helpers: Use the all-in-one helper that handles encoding, generation, and submission
- Building Consumer Contracts: Create contracts that can receive your reports
- EVM Client Reference: Complete API documentation