Skip to content

Commit 4d3022b

Browse files
committed
verifying tx status guides
1 parent 689c24c commit 4d3022b

File tree

6 files changed

+510
-18
lines changed

6 files changed

+510
-18
lines changed

src/config/sidebar.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ export const SIDEBAR: Partial<Record<Sections, SectionEntry[]>> = {
245245
title: "Submitting Reports Onchain",
246246
url: "cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain",
247247
},
248+
{
249+
title: "Verifying Transaction Status",
250+
url: "cre/guides/workflow/using-evm-client/onchain-write/verifying-transaction-status",
251+
highlightAsCurrent: [
252+
"cre/guides/workflow/using-evm-client/onchain-write/verifying-transaction-status-ts",
253+
"cre/guides/workflow/using-evm-client/onchain-write/verifying-transaction-status-go",
254+
],
255+
},
248256
],
249257
},
250258
{

src/content/cre/guides/workflow/using-evm-client/onchain-write/overview-ts.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ This overview explains how writing data onchain works in CRE and how the TypeScr
1717
- [Understanding how CRE writes work](#understanding-how-cre-writes-work) - The secure write flow
1818
- [What you need: A consumer contract](#what-you-need-a-consumer-contract) - Contract requirements
1919
- [The TypeScript write process](#the-typescript-write-process) - Two-step approach overview
20-
- [Next steps](#next-steps) - Where to go from here
2120

2221
## Understanding how CRE writes work
2322

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
---
2+
section: cre
3+
title: "Verifying Transaction Status"
4+
sdkLang: "go"
5+
pageId: "guides-workflow-evm-verifying-transaction-status"
6+
date: Last Modified
7+
metadata:
8+
description: "Verify consumer contract execution in Go: learn to check both transaction success and your contract's onReport() execution status."
9+
datePublished: "2025-12-08"
10+
lastModified: "2025-12-08"
11+
---
12+
13+
import { Aside, ClickToZoom } from "@components"
14+
15+
When your workflow writes data to the blockchain, you can verify both that the transaction was mined and that your consumer contract successfully processed the data. This guide explains how to properly check both levels of execution.
16+
17+
<Aside type="note" title="Prerequisites">
18+
This guide assumes you're already familiar with how CRE's onchain write process works. If you haven't read it yet,
19+
start with [Onchain Write Overview](/cre/guides/workflow/using-evm-client/onchain-write/overview-go) to understand the
20+
secure write flow.
21+
</Aside>
22+
23+
## Why two levels of verification?
24+
25+
Your workflow's data goes through a two-tier transaction model:
26+
27+
1. **Outer Transaction**: Your workflow → `KeystoneForwarder` contract (on the blockchain)
28+
1. **Inner Execution**: `KeystoneForwarder` → Your [consumer contract](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts)'s `onReport()` function
29+
30+
A common mistake is only checking the outer transaction status. **The transaction can succeed while your consumer contract's `onReport()` function reverts.**
31+
32+
## Understanding the status fields
33+
34+
When you call `WriteReport()` on the EVM client and await the promise, you receive a `WriteReportReply` struct. This struct contains the complete results of your write operation, including two status indicators:
35+
36+
| Field | What it checks | Success means | Failure means |
37+
| --------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
38+
| `TxStatus` | Was the transaction mined on the blockchain? | Forwarder received and processed the report | Network issues, insufficient gas, or forwarder rejected the report |
39+
| `ReceiverContractExecutionStatus` | Did YOUR consumer contract's `onReport()` complete without reverting? | All validation passed (if any), `_processReport()` executed successfully | Forwarder validation failed, workflow ID mismatch, or your business logic reverted |
40+
41+
<Aside type="caution" title="Simulation Limitation">
42+
The `receiverContractExecutionStatus` field is currently not populated during workflow simulation. This verification
43+
only works in production environments. During simulation, you can only verify that the transaction to the Forwarder
44+
succeeded (`txStatus`), but cannot detect if your consumer contract's `onReport()` function reverted.
45+
</Aside>
46+
47+
## The complete verification pattern
48+
49+
### Only checks outer transaction
50+
51+
```go
52+
resp, err := writePromise.Await()
53+
if err != nil {
54+
return fmt.Errorf("write failed: %w", err)
55+
}
56+
57+
// INCOMPLETE: Only verifies the transaction was mined
58+
if resp.TxStatus == evm.TxStatus_TX_STATUS_SUCCESS {
59+
logger.Info("Transaction succeeded")
60+
return nil
61+
}
62+
```
63+
64+
**Problem**: Your consumer contract could have reverted, but you'd never know because you only checked if the transaction was mined.
65+
66+
### Checks both levels
67+
68+
```go
69+
resp, err := writePromise.Await()
70+
if err != nil {
71+
return fmt.Errorf("write failed: %w", err)
72+
}
73+
74+
// Step 1: Check outer transaction status
75+
if resp.TxStatus != evm.TxStatus_TX_STATUS_SUCCESS {
76+
return fmt.Errorf("transaction failed with status: %v", resp.TxStatus)
77+
}
78+
79+
// Step 2: Check consumer contract execution status
80+
if resp.ReceiverContractExecutionStatus != nil &&
81+
*resp.ReceiverContractExecutionStatus == evm.ReceiverContractExecutionStatus_RECEIVER_CONTRACT_EXECUTION_STATUS_REVERTED {
82+
logger.Error("Consumer contract reverted",
83+
"error", resp.GetErrorMessage(),
84+
"txHash", common.BytesToHash(resp.TxHash).Hex())
85+
return fmt.Errorf("consumer contract execution failed: %s", resp.GetErrorMessage())
86+
}
87+
88+
logger.Info("Both transaction AND consumer contract execution succeeded",
89+
"txHash", common.BytesToHash(resp.TxHash).Hex())
90+
```
91+
92+
**What this checks**:
93+
94+
1. Transaction was mined successfully
95+
1. Your consumer contract's `onReport()` function executed without reverting
96+
1. Your business logic completed successfully
97+
98+
## Common scenarios
99+
100+
### Scenario 1: Everything succeeded
101+
102+
```go
103+
// Transaction mined + Consumer contract executed successfully
104+
TxStatus: TX_STATUS_SUCCESS
105+
ReceiverContractExecutionStatus: RECEIVER_CONTRACT_EXECUTION_STATUS_SUCCESS
106+
```
107+
108+
**What happened**: The report was delivered and your contract processed it successfully.
109+
110+
**Action**: None needed - everything worked as expected.
111+
112+
### Scenario 2: Transaction succeeded, but contract reverted
113+
114+
```go
115+
// Transaction was mined, but your contract rejected the data
116+
TxStatus: TX_STATUS_SUCCESS
117+
ReceiverContractExecutionStatus: RECEIVER_CONTRACT_EXECUTION_STATUS_REVERTED
118+
```
119+
120+
**What happened**: The forwarder successfully submitted the transaction, but your consumer contract's `onReport()` function reverted during execution.
121+
122+
**Common causes**:
123+
124+
- Forwarder address mismatch: You configured the wrong forwarder address in your consumer contract (simulation forwarders are different from production forwarders - see [Supported Networks](/cre/guides/workflow/using-evm-client/supported-networks-go))
125+
- Security check failed (if you configured expected values for workflow ID, owner, or name in your contract)
126+
- Custom validation in `_processReport()` rejected the data
127+
- ABI decoding failure (struct mismatch between workflow and contract)
128+
- Custom business logic constraints not met
129+
130+
**Action**: Check the error message and review your consumer contract's validation logic. If moving from simulation to production, ensure you updated the forwarder address in your contract.
131+
132+
### Scenario 3: Transaction failed
133+
134+
```go
135+
// Transaction failed to be mined
136+
TxStatus: TX_STATUS_REVERTED or TX_STATUS_FATAL
137+
ReceiverContractExecutionStatus: N/A
138+
```
139+
140+
**What happened**: The transaction couldn't be mined on the blockchain.
141+
142+
**Common causes**:
143+
144+
- Insufficient gas
145+
- Network connectivity issues
146+
- Incorrect forwarder address
147+
- RPC endpoint problems
148+
149+
**Action**: Check RPC endpoint, gas configuration, network status, and forwarder address.
150+
151+
## Best practices helper function
152+
153+
Create a reusable helper to verify both status levels:
154+
155+
```go
156+
// verifyWriteSuccess checks both transaction and contract execution status
157+
func verifyWriteSuccess(resp *evm.WriteReportReply, logger *slog.Logger) error {
158+
// Check outer transaction
159+
if resp.TxStatus != evm.TxStatus_TX_STATUS_SUCCESS {
160+
return fmt.Errorf("transaction failed with status %v", resp.TxStatus)
161+
}
162+
163+
// Check consumer contract execution
164+
if resp.ReceiverContractExecutionStatus != nil &&
165+
*resp.ReceiverContractExecutionStatus == evm.ReceiverContractExecutionStatus_RECEIVER_CONTRACT_EXECUTION_STATUS_REVERTED {
166+
errorMsg := "unknown error"
167+
if resp.ErrorMessage != nil {
168+
errorMsg = *resp.ErrorMessage
169+
}
170+
return fmt.Errorf("consumer contract reverted: %s", errorMsg)
171+
}
172+
173+
// Log success with transaction hash
174+
txHash := common.BytesToHash(resp.TxHash).Hex()
175+
logger.Info("Write verification succeeded",
176+
"txHash", txHash,
177+
"txStatus", resp.TxStatus,
178+
"contractStatus", resp.ReceiverContractExecutionStatus)
179+
180+
return nil
181+
}
182+
183+
// Usage in your workflow
184+
func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
185+
logger := runtime.Logger()
186+
187+
// ... prepare data and write report ...
188+
189+
writePromise := contract.WriteReportFromMyData(runtime, data, nil)
190+
resp, err := writePromise.Await()
191+
if err != nil {
192+
return nil, fmt.Errorf("write report await failed: %w", err)
193+
}
194+
195+
// Use the helper for complete verification
196+
if err := verifyWriteSuccess(resp, logger); err != nil {
197+
return nil, err
198+
}
199+
200+
return &MyResult{TxHash: common.BytesToHash(resp.TxHash).Hex()}, nil
201+
}
202+
```
203+
204+
## Accessing error details
205+
206+
The `WriteReportReply` provides multiple ways to access error information:
207+
208+
```go
209+
resp, err := writePromise.Await()
210+
if err != nil {
211+
return fmt.Errorf("await failed: %w", err)
212+
}
213+
214+
// Option 1: Direct field access (pointer, can be nil)
215+
if resp.ErrorMessage != nil {
216+
logger.Error("Error message (direct)", "message", *resp.ErrorMessage)
217+
}
218+
219+
// Option 2: Using the getter method (safer, returns empty string if nil)
220+
logger.Info("Error message (getter)", "message", resp.GetErrorMessage())
221+
222+
// Option 3: Check status enum
223+
logger.Info("Contract execution status", "status", resp.GetReceiverContractExecutionStatus())
224+
```
225+
226+
**Best practice**: Use the getter methods (`GetErrorMessage()`, `GetReceiverContractExecutionStatus()`) as they handle nil values safely.
227+
228+
## Related resources
229+
230+
- **[EVM Client Reference](/cre/reference/sdk/evm-client-go#evmwritereportreply)** - Complete API documentation for `WriteReportReply`, including all field definitions and status constant values
231+
- **[Building Consumer Contracts](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts)** - Learn about forwarder validation and the `IReceiver` interface
232+
- **[Supported Networks](/cre/guides/workflow/using-evm-client/supported-networks)** - Forwarder addresses for simulation and production
233+
- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)** - Complete guide to the write process

0 commit comments

Comments
 (0)