diff --git a/src/content/cre/llms-full-go.txt b/src/content/cre/llms-full-go.txt
index ea6f358e373..61843287d6b 100644
--- a/src/content/cre/llms-full-go.txt
+++ b/src/content/cre/llms-full-go.txt
@@ -18637,204 +18637,27 @@ func InitWorkflow(config *Config) []cre.HandlerDefinition {
## Task Authoring: The "Bridge" Pattern
-The fastest way to migrate is using the official **Automation Migration** starter template. This template uses a "Bridge" pattern: you deploy a generic [`AutomationReceiver.sol`](https://github.com/smartcontractkit/cre-templates/blob/main/starter-templates/automation-migration/contracts/evm/src/AutomationReceiver.sol) contract, which receives CRE reports and forwards approved calls to your existing Automation contracts.
-
-Your Solidity consumer contract usually does not need to reimplement `checkUpkeep`, `checkLog`, or `performUpkeep`, but some contracts still need a permission update. If your existing contract checks `msg.sender`, uses an Automation Forwarder allowlist, or has role-based permissions, authorize the new `AutomationReceiver` or adjust that permission boundary before deployment.
-
-### Logic Migration (`checkUpkeep` → Handler)
-
-In the handler, you use `evmClient.callContract()` to perform the same checks previously done in `checkUpkeep`. Since this runs off-chain, you are not limited by on-chain gas constraints for the "check" phase.
-
-### On-chain Execution (`performUpkeep` → `onReport`)
-
-Your workflow generates a signed report and submits it to the `AutomationReceiver`. The receiver then calls the original `performUpkeep` function on your target contract.
-
-```
-// AutomationReceiver.sol (included in template)
-function _processReport(bytes calldata report) internal override {
- (address target, bytes memory data) = abi.decode(report, (address, bytes));
-
- if (target == address(0)) {
- revert InvalidTargetAddress();
- }
-
- (bool success, bytes memory returnData) = target.call(data);
- if (!success) {
- revert CallExecutionFailed(target, returnData);
- }
-}
-```
-
-`AutomationReceiver` inherits `ReceiverTemplate`, so it validates the configured CRE forwarder before processing reports. For production, also configure workflow identity checks such as expected workflow ID and/or expected workflow owner address, or narrow the receiver to known target contracts and function selectors. A generic receiver that accepts arbitrary `(target, data)` is convenient for migration, but it should not be left broadly reusable without explicit authorization controls.
-
-## Get started
-
-### 1. Initialize your project
-
-Use the CRE CLI to scaffold a new project using the migration template:
-
-```shell
-cre init --template=automation-migration-go --project-name my-automation-migration --workflow-name my-workflow
-```
-
-The template is available in the `main` branch. You can scaffold a new project using the CRE CLI:
-
-```shell
-cre init --template=automation-migration-go
-```
-
-Or browse the template directly at [`smartcontractkit/cre-templates`](https://github.com/smartcontractkit/cre-templates/tree/main/starter-templates/automation-migration).
-
-### 2. Deploy the Bridge
-
-Deploy `AutomationReceiver.sol` (found in the template) to your target chain, passing the CRE `KeystoneForwarder` address for that network to the constructor. Find the correct forwarder address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-go). This contract acts as a translation layer, allowing you to reuse your existing `checkUpkeep`, `checkLog`, and `performUpkeep` logic while moving trigger and check orchestration into CRE.
-
-### 3. Configure and Authorize the Receiver
-
-Update `my-workflow/config.test.json` in your new project with your previously deployed AutomationReceiver contract address, target contract address, migration type (`CRON`, `CUSTOM`, or `LOG`), schedule, and log filters if applicable.
-
-#### 3a. Allow Upkeep Calls
-
-Before your workflow can call your target contract through the receiver, you must authorize the function call using `setCallAllowed()`. This setter transaction grants the receiver permission to forward calls to your target contract with a specific function selector.
-
-**Parameters:**
-
-- **target** (address): The address of your existing Automation upkeep contract
-- **selector** (bytes4): The 4-byte function selector you want to allow (e.g., `0x4585e33b` for `performUpkeep`)
-- **allowed** (bool): Set to `true` to enable the call
-
-**Example transaction:**
-
-```
-Function: setCallAllowed (0xb1e17766)
-target (address): 0x0971Ad145A5462f6Ae18b1aD2b9c9b0c6d8CC9C8
-selector (bytes4): 0x4585e33b
-allowed (bool): true
-```
-
-#### 3b. Set Workflow Identity Checks
-
-For production deployments, configure additional authorization layers by setting expected workflow identity parameters. These optional checks ensure only authorized workflows can invoke your receiver.
-
-**Available setters:**
-
-- **setExpectedAuthor** — Restrict calls to workflows authored by a specific address
- - Parameter: `_author` (address)
-- **setExpectedWorkflowId** — Restrict calls to a specific workflow ID
- - Parameter: `_workflowId` (string)
-- **setExpectedWorkflowName** — Restrict calls to workflows with a specific name
- - Parameter: `_workflowName` (string)
-
-**Example transactions:**
-
-```
-Function: setExpectedAuthor
-_author (address): 0x5c5c48fc95d68f88b4a13e0c9b0c6d8cc9c8c8c
-
-Function: setExpectedWorkflowId
-_workflowId (string): 0x0971ad145a5462f6ae18b1ad2b9c9b0c6d8cc9c8
-
-Function: setExpectedWorkflowName
-_workflowName (string): automation-migration-test
-```
-
-### Parameters Quick Reference
-
-#### Getting the Target Address
-
-The target address is the address of your existing **Automation upkeep contract** — the contract that currently implements `performUpkeep()`. You can find this by:
-
-- Reviewing your Automation registry registration
-- Checking your deployment scripts or configuration
-- Looking up the contract address in your blockchain explorer
-
-#### Computing the Function Selector
-
-The function selector is the first 4 bytes of the keccak256 hash of the function signature. Use the `cast` command-line tool to compute it:
-
-```shell
-# For performUpkeep(bytes)
-cast sig 'performUpkeep(bytes)'
-# Output: 0x4585e33b
-
-# For custom functions
-cast sig 'myCustomFunction(uint256,address)'
-# Output: 0xabcdef12
-```
-
-Alternatively, compute it programmatically in your test or deployment script:
-
-```go
-import "github.com/ethereum/go-ethereum/crypto"
-
-selector := crypto.Keccak256([]byte("performUpkeep(bytes)"))[:4]
-// Result: 0x4585e33b
-```
-
-### Example Transaction Outputs
-
-A successful `setCallAllowed` transaction on a block explorer shows:
-
-```
-Transaction Type: Contract Interaction
-Function: setCallAllowed(address,bytes4,bool)
-Status: ✓ Success
-From: 0xYourAddress
-To: 0xAutomationReceiverAddress
-Gas Used: 35,400 gas
-
-Decoded Input:
-target: 0x0971Ad145A5462f6Ae18b1aD2b9c9b0c6d8CC9C8
-selector: 0x4585e33b
-allowed: true
-```
-
-### 3c. Configure and Simulate
-
-Then install dependencies and simulate from the project root. The CRE CLI prepares the workflow build tooling during simulation.
-
-```shell
-cd my-workflow
-bun install
-cd ..
-
-cre workflow simulate my-workflow --target=test-settings
-```
-
-For log-trigger migrations, provide a transaction hash containing the event so simulation does not wait for a live event:
-
-```shell
-cre workflow simulate my-workflow \
- --target=test-settings \
- --non-interactive \
- --trigger-index=0 \
- --evm-tx-hash=0x... \
- --evm-event-index=0
-```
-
-### 4. Deploy
-
-Once verified, deploy your workflow to the CRE DON:
-
-```shell
-cre workflow deploy my-workflow --target=production-settings
-```
-
-For end-to-end runnable examples, see the [`smartcontractkit/cre-templates`](https://github.com/smartcontractkit/cre-templates/tree/main/starter-templates/automation-migration) repository.
-
-## Troubleshooting
-
-### CallNotAllowed Error
-
-If your workflow execution fails with a `CallNotAllowed` error, verify the following:
-
-1. **Function selector mismatch** — Ensure the selector you configured in `setCallAllowed()` matches the function your workflow is attempting to call. Use `cast sig 'functionName(types)'` to compute the correct selector. For more information, see the [Function Selector Reference](https://rareskills.io/post/function-selector).
-
-2. **Permission not set** — Confirm that `setCallAllowed()` was called with `allowed: true` for your target contract and function selector.
-
-3. **Workflow authorization** — If you set workflow identity checks (`setExpectedAuthor`, `setExpectedWorkflowId`, or `setExpectedWorkflowName`), ensure the workflow deploying the call matches those values.
-
-4. **Forwarder mismatch** — Verify that the `KeystoneForwarder` address passed to the `AutomationReceiver` constructor matches the address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-go).
+
+
+## Resources
+
+- [Migrate from Chainlink Automation to Chainlink CRE (TypeScript)](/cre/reference/cla-migration-ts) — the full Bridge-pattern walkthrough, including `AutomationReceiver.sol` deployment and configuration
+- [Chainlink Automation Documentation](https://docs.chain.link/automation)
+- [CRE Getting Started Guide](/cre)
+- [Legacy Migration Toolkit](https://github.com/smartcontractkit/cla-cre-migration) (alternative manual-encoding approach)
---
diff --git a/src/content/cre/llms-full-ts.txt b/src/content/cre/llms-full-ts.txt
index 5a3c6e82dc9..a1cffb24dc3 100644
--- a/src/content/cre/llms-full-ts.txt
+++ b/src/content/cre/llms-full-ts.txt
@@ -18503,9 +18503,12 @@ const initWorkflow = (config: Config) => {
## Task Authoring: The "Bridge" Pattern
-The fastest way to migrate is using the official **Automation Migration** starter template. This template uses a "Bridge" pattern: you deploy a generic [`AutomationReceiver.sol`](https://github.com/smartcontractkit/cre-templates/blob/main/starter-templates/automation-migration/contracts/evm/src/AutomationReceiver.sol) contract, which receives CRE reports and forwards approved calls to your existing Automation contracts.
+The fastest way to migrate is using the official **Automation Migration** starter template. This template uses a "Bridge" pattern:
-Your Solidity consumer contract usually does not need to reimplement `checkUpkeep`, `checkLog`, or `performUpkeep`, but some contracts still need a permission update. If your existing contract checks `msg.sender`, uses an Automation Forwarder allowlist, or has role-based permissions, authorize the new `AutomationReceiver` or adjust that permission boundary before deployment.
+1. **[`AutomationReceiver.sol`](https://github.com/smartcontractkit/cre-templates/blob/main/starter-templates/automation-migration/contracts/evm/src/AutomationReceiver.sol)** — a bridge contract you deploy once on-chain. It receives CRE reports and forwards execution to your existing legacy contracts. It enforces two independent authorization layers (see [Security Model](#security-model) below).
+2. **Workflows** — CRE workflows that poll your legacy `checkUpkeep` or `checkLog` functions and trigger the bridge when needed. The `CUSTOM`/`LOG` paths use generated, type-safe contract bindings; the `CRON` path ABI-encodes a configured function signature at runtime.
+
+You can usually migrate to CRE without rewriting your original upkeep logic — provided your legacy contract lets you re-point who is allowed to call it (see [Compatibility](#compatibility)). If your existing contract checks `msg.sender`, uses an Automation Forwarder allowlist, or has role-based permissions, authorize the deployed `AutomationReceiver` there before relying on the workflow.
### Logic Migration (`checkUpkeep` → Handler)
@@ -18513,29 +18516,36 @@ In the handler, you use `evmClient.callContract()` to perform the same checks pr
### On-chain Execution (`performUpkeep` → `onReport`)
-Your workflow generates a signed report and submits it to the `AutomationReceiver`. The receiver then calls the original `performUpkeep` function on your target contract.
+Your workflow generates a signed report and submits it to the `AutomationReceiver`. The report payload is ABI-encoded as `(address target, uint256 blockNumber, bytes data)`, where `data` is a full function call (4-byte selector followed by its arguments). The receiver executes `target.call(data)`, so it can drive any function signature — `performUpkeep(bytes)` for custom-logic/log upkeeps, or a custom function like `performAction(uint256)` for time-based ones. The `blockNumber` field feeds the optional monotonicity check ([3e](#3e-configure-block-number-monotonicity--staleness-protection-optional)) and is otherwise ignored. Every call is gated by the closed-by-default `(target, selector)` allowlist ([3a](#3a-allow-upkeep-calls)).
-```
-// AutomationReceiver.sol (included in template)
-function _processReport(bytes calldata report) internal override {
- (address target, bytes memory data) = abi.decode(report, (address, bytes));
+`_processReport` distinguishes five outcomes:
- if (target == address(0)) {
- revert InvalidTargetAddress();
- }
+- **Authorization failure** — a zero target, a target with no deployed code, calldata shorter than a 4-byte selector, or a `(target, selector)` that is not allowlisted — **reverts** (`InvalidTargetAddress` / `TargetHasNoCode` / `MissingSelector` / `CallNotAllowed`).
+- **Stale report** — the block-number monotonicity check is enabled for the pair and the report's block number is older than the last accepted one — does **not** revert. The receiver emits `StaleReportSkipped` and consumes the report.
+- **Gas guard failure** — `setConsumerGasLimit` is configured for the pair and the incoming gas is below the required threshold — **reverts** with `InsufficientGas`, so the CRE Forwarder records the delivery as failed and retryable.
+- **Paused** — the owner has called `pause(bool retryable)`. With `pause(true)` it **reverts** with `EnforcedPause` (retryable); with `pause(false)` it emits `ReportSkippedWhilePaused` and consumes the report.
+- **Execution failure** — an allowed call that itself reverts — does **not** revert `onReport`. The receiver emits `CallFailed(target, selector, reason)` and the report is consumed. This mirrors Chainlink Automation's fire-and-forget behavior, where a failed `performUpkeep` simply ends that round and the next trigger re-evaluates eligibility.
- (bool success, bytes memory returnData) = target.call(data);
- if (!success) {
- revert CallExecutionFailed(target, returnData);
- }
-}
-```
+### Security Model
+
+`AutomationReceiver` authorizes reports on two separate layers, and **both** apply:
+
+- **Inbound — who may deliver a report**: the CRE Forwarder address is set at construction and validated by `ReceiverTemplate`. `AutomationReceiver._processReport` adds two hard guards: it rejects any delivery if the forwarder was ever set to `address(0)` post-deployment, and it requires at least one complete workflow identity option to be configured — an unconfigured receiver rejects all reports with `WorkflowIdentityNotConfigured`. Two options are accepted: **(a)** `workflowId` is set (binds the receiver to one specific workflow), or **(b)** both `workflowOwner` and `workflowName` are set together (binds to a named workflow from a specific owner). Either piece of option (b) alone is insufficient.
+- **Outbound — what a report may make the receiver do**: a **closed-by-default allowlist** of `(target, function-selector)` pairs. Inbound checks only prove a report came from your workflow; they do **not** constrain the `(target, data)` it carries. Until you allowlist a pair with `setCallAllowed`, the receiver rejects it.
+
+On top of these layers, the owner can trigger a global **emergency pause** via `pause(bool retryable)`. While paused, all deliveries are rejected; the `retryable` flag chosen at pause time decides whether they stay retryable (revert) or are dropped (consumed). See [Emergency Pause](#3d-emergency-pause-optional-but-recommended-for-production).
-`AutomationReceiver` inherits `ReceiverTemplate`, so it validates the configured CRE forwarder before processing reports. For production, also configure workflow identity checks such as expected workflow ID and/or expected workflow owner address, or narrow the receiver to known target contracts and function selectors. A generic receiver that accepts arbitrary `(target, data)` is convenient for migration, but it should not be left broadly reusable without explicit authorization controls.
+## Migration Path Mapping
+
+| Your Automation Setup | CRE Equivalent | Migration Type |
+| :-------------------- | :-------------- | :------------- |
+| Time-based upkeep | Cron Trigger | `CRON` |
+| Custom logic upkeep | Cron + EVM Read | `CUSTOM` |
+| Log trigger upkeep | EVM Log Trigger | `LOG` |
## Get started
-### 1. Initialize your project
+### 1. Initialize the template
Use the CRE CLI to scaffold a new project using the migration template:
@@ -18543,133 +18553,180 @@ Use the CRE CLI to scaffold a new project using the migration template:
cre init --template=automation-migration-ts --project-name my-automation-migration --workflow-name my-workflow
```
-The template is available in the `main` branch. You can scaffold a new project using the CRE CLI:
+Or browse the template directly at [`smartcontractkit/cre-templates`](https://github.com/smartcontractkit/cre-templates/tree/main/starter-templates/automation-migration).
+
+### 2. Build and deploy the Bridge
+
+Deploy `AutomationReceiver.sol` to your target chain. You only need to do this once to support multiple upkeeps.
+
+The `contracts/evm` directory ships a ready-to-use [Foundry](https://book.getfoundry.sh/) project — a `foundry.toml` and a vendored copy of the only OpenZeppelin files used (`Ownable`, `Pausable`, `Context`). No `forge install` or local node is required:
```shell
-cre init --template=automation-migration-ts
+cd contracts/evm
+forge build
+forge test # 54 tests covering the receiver + permission template
+
+# Deploy, passing the CRE Forwarder address for your DON as the constructor arg
+forge create src/AutomationReceiver.sol:AutomationReceiver \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --constructor-args "$FORWARDER_ADDRESS"
```
-Or browse the template directly at [`smartcontractkit/cre-templates`](https://github.com/smartcontractkit/cre-templates/tree/main/starter-templates/automation-migration).
+`FORWARDER_ADDRESS` is the CRE Forwarder for your target network — it is the **only** address allowed to call `onReport`. Look it up in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts) for your network; do not guess it. A wrong value means the DON's reports are rejected. The constructor blocks `address(0)`, and even if the owner ever sets the forwarder to `address(0)` post-deployment (via `setForwarderAddress`), `AutomationReceiver._processReport` will reject all subsequent deliveries with `InvalidForwarderAddress`.
-### 2. Deploy the Bridge
+### 3. Configure and authorize the receiver
-Deploy `AutomationReceiver.sol` (found in the template) to your target chain, passing the CRE `KeystoneForwarder` address for that network to the constructor. Find the correct forwarder address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts). This contract acts as a translation layer, allowing you to reuse your existing `checkUpkeep`, `checkLog`, and `performUpkeep` logic while moving trigger and check orchestration into CRE.
+The receiver rejects every outbound call until you allowlist it. You must set up both the call allowlist and the workflow identity checks.
-### 3. Configure and Authorize the Receiver
+#### 3a. Allow Upkeep Calls
-Update `my-workflow/config.test.json` in your new project with your previously deployed AutomationReceiver contract address, target contract address, migration type (`CRON`, `CUSTOM`, or `LOG`), schedule, and log filters if applicable.
+For each migrated upkeep, allowlist the exact `(target, selector)` the workflow will invoke:
-#### 3a. Allow Upkeep Calls
+```shell
+# Custom-logic / log-trigger upkeeps call performUpkeep(bytes)
+cast send "$RECEIVER_ADDRESS" \
+ "setCallAllowed(address,bytes4,bool)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" true \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
-Before your workflow can call your target contract through the receiver, you must authorize the function call using `setCallAllowed()`. This setter transaction grants the receiver permission to forward calls to your target contract with a specific function selector.
+# Time-based upkeeps call your specific function, e.g. performAction(uint256)
+cast send "$RECEIVER_ADDRESS" \
+ "setCallAllowed(address,bytes4,bool)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performAction(uint256)')" true \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+```
**Parameters:**
-- **target** (address): The address of your existing Automation upkeep contract
-- **selector** (bytes4): The 4-byte function selector you want to allow (e.g., `0x4585e33b` for `performUpkeep`)
-- **allowed** (bool): Set to `true` to enable the call
+- **target** (address): The address of your existing upkeep contract. When allowing (`allowed = true`) it must be a deployed contract — `setCallAllowed` reverts with `TargetHasNoCode` if the address has no code. The code check is skipped when revoking (`allowed = false`), so an entry can always be removed even if the target has since self-destructed.
+- **selector** (bytes4): The 4-byte function selector, computed from the function signature via `cast sig`. It must match the function the workflow encodes (`performUpkeep` for `CUSTOM`/`LOG`, or your `targetFunction` for `CRON`) — a mismatch makes `onReport` revert with `CallNotAllowed`.
+- **allowed** (bool): `true` to allow, `false` to revoke.
-**Example transaction:**
+#### 3b. Configure the Consumer Gas Limit (Recommended)
-```
-Function: setCallAllowed (0xb1e17766)
-target (address): 0x0971Ad145A5462f6Ae18b1aD2b9c9b0c6d8CC9C8
-selector (bytes4): 0x4585e33b
-allowed (bool): true
-```
+Set the minimum gas the receiver must have available before forwarding the call to your upkeep consumer contract. When configured, `_processReport` reverts with `InsufficientGas` — causing the CRE Forwarder to record the delivery as **failed and retryable** — if the incoming gas is below `consumerGasLimit + consumerGasLimit / 63 + 7,000` (the on-chain overhead).
+
+The limit is configured **per `(target, selector)` pair**. Zero (the default) disables the guard and preserves fire-and-forget semantics, mirroring Chainlink Automation's behavior where a failed `performUpkeep` simply ends that round.
-#### 3b. Set Workflow Identity Checks
+```shell
+# Custom-logic / log-trigger upkeeps: performUpkeep(bytes)
+cast send "$RECEIVER_ADDRESS" \
+ "setConsumerGasLimit(address,bytes4,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" "$PERFORM_GAS_LIMIT" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
-For production deployments, configure additional authorization layers by setting expected workflow identity parameters. These optional checks ensure only authorized workflows can invoke your receiver.
+# Time-based upkeeps: your specific function, e.g. performAction(uint256)
+cast send "$RECEIVER_ADDRESS" \
+ "setConsumerGasLimit(address,bytes4,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performAction(uint256)')" "$PERFORM_GAS_LIMIT" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+```
-**Available setters:**
+#### 3c. Set Workflow Identity Checks
-- **setExpectedAuthor** — Restrict calls to workflows authored by a specific address
- - Parameter: `_author` (address)
-- **setExpectedWorkflowId** — Restrict calls to a specific workflow ID
- - Parameter: `_workflowId` (string)
-- **setExpectedWorkflowName** — Restrict calls to workflows with a specific name
- - Parameter: `_workflowName` (string)
+
-**Example transactions:**
+- **Option A — `workflowId`**: set the workflow ID; owner and name are not required.
+- **Option B — `workflowOwner` + `workflowName`**: set both together; either piece alone is insufficient, and `workflowId` is not required.
-```
-Function: setExpectedAuthor
-_author (address): 0x5c5c48fc95d68f88b4a13e0c9b0c6d8cc9c8c8c
+```shell
+# Option A — identify by workflow ID
+cast send "$RECEIVER_ADDRESS" \
+ "setExpectedWorkflowId(bytes32)" \
+ "$WORKFLOW_ID" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
-Function: setExpectedWorkflowId
-_workflowId (string): 0x0971ad145a5462f6ae18b1ad2b9c9b0c6d8cc9c8
+# Option B — identify by owner and name (both required)
+cast send "$RECEIVER_ADDRESS" \
+ "setExpectedAuthor(address)" \
+ "$WORKFLOW_OWNER_ADDRESS" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
-Function: setExpectedWorkflowName
-_workflowName (string): automation-migration-test
+cast send "$RECEIVER_ADDRESS" \
+ "setExpectedWorkflowName(string)" \
+ "$WORKFLOW_NAME" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
-### Parameters Quick Reference
-
-#### Getting the Target Address
+You may also combine both options for the strongest guarantee (e.g. set all three fields). Note that `setExpectedWorkflowName` must always be paired with `setExpectedAuthor` — workflow names are unique per owner, not globally.
-The target address is the address of your existing **Automation upkeep contract** — the contract that currently implements `performUpkeep()`. You can find this by:
+#### 3d. Emergency Pause (Optional but recommended for production)
-- Reviewing your Automation registry registration
-- Checking your deployment scripts or configuration
-- Looking up the contract address in your blockchain explorer
+The receiver includes an owner-only global emergency stop built on OpenZeppelin's `Pausable`. This is the CRE equivalent of Chainlink Automation's registry-wide pause: it halts report processing across the whole receiver (there is no per-upkeep `pauseUpkeep` equivalent).
-#### Computing the Function Selector
+`pause(bool retryable)` lets you decide, per pause, what happens to reports delivered while paused:
-The function selector is the first 4 bytes of the keccak256 hash of the function signature. Use the `cast` command-line tool to compute it:
+- **`pause(true)` — retryable**: `_processReport` reverts with `EnforcedPause`, so the CRE Forwarder records the transmission as failed and retryable. Reports resume delivery once you `unpause()`.
+- **`pause(false)` — drop**: `_processReport` emits `ReportSkippedWhilePaused` and consumes the report. Dropped reports are not redelivered after `unpause()`.
```shell
-# For performUpkeep(bytes)
-cast sig 'performUpkeep(bytes)'
-# Output: 0x4585e33b
+# Halt processing, keeping reports retryable
+cast send "$RECEIVER_ADDRESS" "pause(bool)" true --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
-# For custom functions
-cast sig 'myCustomFunction(uint256,address)'
-# Output: 0xabcdef12
+# Halt processing, dropping reports delivered while paused
+cast send "$RECEIVER_ADDRESS" "pause(bool)" false --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+
+# Resume report processing
+cast send "$RECEIVER_ADDRESS" "unpause()" --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
-Alternatively, compute it programmatically in your test or deployment script:
+Pausing or deleting a workflow stops the DON from producing *new* reports, but reports already signed remain permissionlessly deliverable afterward. The on-chain `pause()` closes that gap by rejecting those in-flight reports at the receiver.
-```ts
-import { getAddress, toFunctionSelector } from "viem"
+#### 3e. Configure Block Number Monotonicity / Staleness Protection (Optional)
+
+Chainlink Automation's registry rejected any report whose trigger block preceded the last performed block. CRE's delivery path does **not** preserve this ordering — the Forwarder deduplicates only exact retransmissions of the same report. If your upkeep relied on that implicit ordering (typical for conditional-trigger and log-trigger upkeeps), enable this opt-in check per `(target, selector)` pair (off by default). The workflow always stamps a block number into the report payload, so you can enable this later without any workflow change.
+
+```shell
+# Enable with an explicit floor (recommended): the first accepted report must be >= this block
+cast send "$RECEIVER_ADDRESS" \
+ "setBlockNumberCheck(address,bytes4,bool,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" true "$INITIAL_BLOCK" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
-const selector = toFunctionSelector("performUpkeep(bytes)")
-// Result: 0x4585e33b
+# Disable the check (also clears the stored floor)
+cast send "$RECEIVER_ADDRESS" \
+ "setBlockNumberCheck(address,bytes4,bool,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" false 0 \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
-### Example Transaction Outputs
+Prefer an explicit `initialBlockNumber` over passing `0` (which snapshots `block.number` at that moment) — a report generated between deployment and that snapshot could still carry a higher block number and would not be rejected. Disabling the check is not retroactive: it resets the floor to `0` so out-of-order reports execute again going forward, but it does not revive reports already skipped as stale.
-A successful `setCallAllowed` transaction on a block explorer shows:
+### 4. Configure the workflow
-```
-Transaction Type: Contract Interaction
-Function: setCallAllowed(address,bytes4,bool)
-Status: ✓ Success
-From: 0xYourAddress
-To: 0xAutomationReceiverAddress
-Gas Used: 35,400 gas
+Update `my-workflow/config.test.json`:
-Decoded Input:
-target: 0x0971Ad145A5462f6Ae18b1aD2b9c9b0c6d8CC9C8
-selector: 0x4585e33b
-allowed: true
-```
+- `receiverAddress`: Your deployed `AutomationReceiver`.
+- `targetAddress`: Your existing Automation contract.
+- `migrationType`: `CRON`, `CUSTOM`, or `LOG`.
+- `schedule`: Required for `CRON` and `CUSTOM`.
+- `targetFunction` and `targetInputs`: Required for `CRON`. These are ABI-encoded at runtime from the config string (e.g. `"performAction(uint256)"`), so unlike the `CUSTOM`/`LOG` paths they are not statically type-checked — a malformed signature or mismatched inputs fails when the workflow runs.
+- `logTriggerAddress`, `logTriggerEventSignature`, and optional `topic1`/`topic2`/`topic3`: Required for `LOG`.
-### 3c. Configure and Simulate
+
-Then install dependencies and simulate from the project root. The CRE CLI prepares the workflow build tooling during simulation.
+Install workflow dependencies once:
```shell
cd my-workflow
bun install
cd ..
-
-cre workflow simulate my-workflow --target=test-settings
```
-For log-trigger migrations, provide a transaction hash containing the event so simulation does not wait for a live event:
+### 5. Simulate
```shell
+# For CRON (time-based) or CUSTOM (custom logic) — both run on the cron scheduler
+cre workflow simulate my-workflow --target=test-settings
+
+# For Log Trigger (requires a transaction hash containing the event)
cre workflow simulate my-workflow \
--target=test-settings \
--non-interactive \
@@ -18678,7 +18735,7 @@ cre workflow simulate my-workflow \
--evm-event-index=0
```
-### 4. Deploy
+### 6. Deploy
Once verified, deploy your workflow to the CRE DON:
@@ -18688,19 +18745,72 @@ cre workflow deploy my-workflow --target=production-settings
For end-to-end runnable examples, see the [`smartcontractkit/cre-templates`](https://github.com/smartcontractkit/cre-templates/tree/main/starter-templates/automation-migration) repository.
+## Technical Details
+
+### Log Mapping
+
+Legacy Automation contracts expect a specific `Log` struct in `checkLog`. The template includes a utility (`mapLogToAutomation`) that maps the CRE `EVMLog` into that struct so your existing on-chain decoding logic keeps working.
+
+The CRE log does not carry the block timestamp, so `log.timestamp` is always mapped to `0`. If your `checkLog` relies on `log.timestamp`, read it from another source (e.g. an EVM read of the block) instead.
+
+### Read Finality
+
+The generated `checkUpkeep` / `checkLog` bindings read at the **last finalized block** so every DON node observes the same state and reaches consensus deterministically. This differs from Automation, which simulates against the chain head. On Ethereum mainnet finality lags the head by \~13 minutes; on most L2s it is seconds. Account for this latency when migrating time-sensitive interval checks.
+
+### Gas Limit
+
+`writeGasLimit` (default `"500000"`) caps the on-chain execution gas forwarded to `onReport`. The on-chain guard formula is `consumerGasLimit + consumerGasLimit/63 + 7,000`, but on the **first** delivery to a `(target, selector)` pair an additional \~17,500 gas is consumed before that check point (cold `SLOAD`s for identity checks, the allowlist lookup, the gas-limit read, and `paused()`). After slots warm, pre-guard overhead drops to \~5,500 gas. With the block-number monotonicity check enabled, budget a further \~10,000 gas. Set `writeGasLimit` to at least your `performGasLimit` plus \~30,000 (or \~40,000 with the monotonicity check enabled). If deliveries still fail with `InsufficientGas`, increase `writeGasLimit` in 10,000-gas increments until they pass.
+
+## Compatibility
+
+Migrating **without redeploying** your upkeep contract requires that contract to let you re-point who is authorized to call it:
+
+- Contracts that expose a setter for the Automation Forwarder / caller (the recommended Automation pattern) can simply point it at the deployed `AutomationReceiver`.
+- Contracts that **hardcode** the Automation registry/forwarder with no setter, or that have an immutable role for it, cannot be migrated in place and must be redeployed.
+
+After migration, the `msg.sender` your target sees is the `AutomationReceiver` address (not the Automation Forwarder), so authorize that address in whatever permission check your contract uses.
+
+## Out of Scope
+
+Off-chain `offchainConfig` / gas-price-threshold controls are out of scope for this template. In CRE these are expressed in the workflow, not on the registry.
+
+
+
## Troubleshooting
### CallNotAllowed Error
If your workflow execution fails with a `CallNotAllowed` error, verify the following:
-1. **Function selector mismatch** — Ensure the selector you configured in `setCallAllowed()` matches the function your workflow is attempting to call. Use `cast sig 'functionName(types)'` to compute the correct selector. For more information, see the [Function Selector Reference](https://rareskills.io/post/function-selector).
-
+1. **Function selector mismatch** — Ensure the selector you configured in `setCallAllowed()` matches the function your workflow is attempting to call. Use `cast sig 'functionName(types)'` to compute the correct selector.
2. **Permission not set** — Confirm that `setCallAllowed()` was called with `allowed: true` for your target contract and function selector.
+3. **Target has no code** — `setCallAllowed` reverts with `TargetHasNoCode` if `target` isn't a deployed contract when `allowed` is `true`.
+
+### WorkflowIdentityNotConfigured Error
+
+The receiver rejects all reports until at least one complete workflow identity option is set: `setExpectedWorkflowId`, or both `setExpectedAuthor` and `setExpectedWorkflowName` together. See [3c. Set Workflow Identity Checks](#3c-set-workflow-identity-checks).
+
+### InsufficientGas Error
+
+The gas forwarded with the report fell below `consumerGasLimit + consumerGasLimit / 63 + 7,000` for that `(target, selector)` pair. Increase `writeGasLimit` in your workflow config — see [Gas Limit](#gas-limit).
+
+### EnforcedPause Error / ReportSkippedWhilePaused Event
+
+The receiver is paused. Check `paused()` and `retryableWhilePaused()`, and call `unpause()` to resume processing.
+
+### Forwarder mismatch
+
+Verify that the `KeystoneForwarder` address passed to the `AutomationReceiver` constructor matches the address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts).
-3. **Workflow authorization** — If you set workflow identity checks (`setExpectedAuthor`, `setExpectedWorkflowId`, or `setExpectedWorkflowName`), ensure the workflow deploying the call matches those values.
+## Resources
-4. **Forwarder mismatch** — Verify that the `KeystoneForwarder` address passed to the `AutomationReceiver` constructor matches the address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts).
+- [Chainlink Automation Documentation](https://docs.chain.link/automation)
+- [CRE Getting Started Guide](/cre)
+- [Legacy Migration Toolkit](https://github.com/smartcontractkit/cla-cre-migration) (alternative manual-encoding approach)
---
diff --git a/src/content/cre/reference/cla-migration-go.mdx b/src/content/cre/reference/cla-migration-go.mdx
index 3922058fed9..3a914eb73cd 100644
--- a/src/content/cre/reference/cla-migration-go.mdx
+++ b/src/content/cre/reference/cla-migration-go.mdx
@@ -109,201 +109,25 @@ func InitWorkflow(config *Config) []cre.HandlerDefinition {
## Task Authoring: The "Bridge" Pattern
-The fastest way to migrate is using the official **Automation Migration** starter template. This template uses a "Bridge" pattern: you deploy a generic [`AutomationReceiver.sol`](https://github.com/smartcontractkit/cre-templates/blob/main/starter-templates/automation-migration/contracts/evm/src/AutomationReceiver.sol) contract, which receives CRE reports and forwards approved calls to your existing Automation contracts.
+
-3. **Workflow authorization** — If you set workflow identity checks (`setExpectedAuthor`, `setExpectedWorkflowId`, or `setExpectedWorkflowName`), ensure the workflow deploying the call matches those values.
+## Resources
-4. **Forwarder mismatch** — Verify that the `KeystoneForwarder` address passed to the `AutomationReceiver` constructor matches the address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-go).
+- [Migrate from Chainlink Automation to Chainlink CRE (TypeScript)](/cre/reference/cla-migration-ts) — the full Bridge-pattern walkthrough, including `AutomationReceiver.sol` deployment and configuration
+- [Chainlink Automation Documentation](https://docs.chain.link/automation)
+- [CRE Getting Started Guide](/cre)
+- [Legacy Migration Toolkit](https://github.com/smartcontractkit/cla-cre-migration) (alternative manual-encoding approach)
diff --git a/src/content/cre/reference/cla-migration-ts.mdx b/src/content/cre/reference/cla-migration-ts.mdx
index 93b2c5da27c..4d92c33977c 100644
--- a/src/content/cre/reference/cla-migration-ts.mdx
+++ b/src/content/cre/reference/cla-migration-ts.mdx
@@ -109,9 +109,12 @@ const initWorkflow = (config: Config) => {
## Task Authoring: The "Bridge" Pattern
-The fastest way to migrate is using the official **Automation Migration** starter template. This template uses a "Bridge" pattern: you deploy a generic [`AutomationReceiver.sol`](https://github.com/smartcontractkit/cre-templates/blob/main/starter-templates/automation-migration/contracts/evm/src/AutomationReceiver.sol) contract, which receives CRE reports and forwards approved calls to your existing Automation contracts.
+The fastest way to migrate is using the official **Automation Migration** starter template. This template uses a "Bridge" pattern:
-Your Solidity consumer contract usually does not need to reimplement `checkUpkeep`, `checkLog`, or `performUpkeep`, but some contracts still need a permission update. If your existing contract checks `msg.sender`, uses an Automation Forwarder allowlist, or has role-based permissions, authorize the new `AutomationReceiver` or adjust that permission boundary before deployment.
+1. **[`AutomationReceiver.sol`](https://github.com/smartcontractkit/cre-templates/blob/main/starter-templates/automation-migration/contracts/evm/src/AutomationReceiver.sol)** — a bridge contract you deploy once on-chain. It receives CRE reports and forwards execution to your existing legacy contracts. It enforces two independent authorization layers (see [Security Model](#security-model) below).
+2. **Workflows** — CRE workflows that poll your legacy `checkUpkeep` or `checkLog` functions and trigger the bridge when needed. The `CUSTOM`/`LOG` paths use generated, type-safe contract bindings; the `CRON` path ABI-encodes a configured function signature at runtime.
+
+You can usually migrate to CRE without rewriting your original upkeep logic — provided your legacy contract lets you re-point who is allowed to call it (see [Compatibility](#compatibility)). If your existing contract checks `msg.sender`, uses an Automation Forwarder allowlist, or has role-based permissions, authorize the deployed `AutomationReceiver` there before relying on the workflow.
### Logic Migration (`checkUpkeep` → Handler)
@@ -119,29 +122,36 @@ In the handler, you use `evmClient.callContract()` to perform the same checks pr
### On-chain Execution (`performUpkeep` → `onReport`)
-Your workflow generates a signed report and submits it to the `AutomationReceiver`. The receiver then calls the original `performUpkeep` function on your target contract.
+Your workflow generates a signed report and submits it to the `AutomationReceiver`. The report payload is ABI-encoded as `(address target, uint256 blockNumber, bytes data)`, where `data` is a full function call (4-byte selector followed by its arguments). The receiver executes `target.call(data)`, so it can drive any function signature — `performUpkeep(bytes)` for custom-logic/log upkeeps, or a custom function like `performAction(uint256)` for time-based ones. The `blockNumber` field feeds the optional monotonicity check ([3e](#3e-configure-block-number-monotonicity--staleness-protection-optional)) and is otherwise ignored. Every call is gated by the closed-by-default `(target, selector)` allowlist ([3a](#3a-allow-upkeep-calls)).
-```
-// AutomationReceiver.sol (included in template)
-function _processReport(bytes calldata report) internal override {
- (address target, bytes memory data) = abi.decode(report, (address, bytes));
-
- if (target == address(0)) {
- revert InvalidTargetAddress();
- }
-
- (bool success, bytes memory returnData) = target.call(data);
- if (!success) {
- revert CallExecutionFailed(target, returnData);
- }
-}
-```
+`_processReport` distinguishes five outcomes:
+
+- **Authorization failure** — a zero target, a target with no deployed code, calldata shorter than a 4-byte selector, or a `(target, selector)` that is not allowlisted — **reverts** (`InvalidTargetAddress` / `TargetHasNoCode` / `MissingSelector` / `CallNotAllowed`).
+- **Stale report** — the block-number monotonicity check is enabled for the pair and the report's block number is older than the last accepted one — does **not** revert. The receiver emits `StaleReportSkipped` and consumes the report.
+- **Gas guard failure** — `setConsumerGasLimit` is configured for the pair and the incoming gas is below the required threshold — **reverts** with `InsufficientGas`, so the CRE Forwarder records the delivery as failed and retryable.
+- **Paused** — the owner has called `pause(bool retryable)`. With `pause(true)` it **reverts** with `EnforcedPause` (retryable); with `pause(false)` it emits `ReportSkippedWhilePaused` and consumes the report.
+- **Execution failure** — an allowed call that itself reverts — does **not** revert `onReport`. The receiver emits `CallFailed(target, selector, reason)` and the report is consumed. This mirrors Chainlink Automation's fire-and-forget behavior, where a failed `performUpkeep` simply ends that round and the next trigger re-evaluates eligibility.
+
+### Security Model
+
+`AutomationReceiver` authorizes reports on two separate layers, and **both** apply:
+
+- **Inbound — who may deliver a report**: the CRE Forwarder address is set at construction and validated by `ReceiverTemplate`. `AutomationReceiver._processReport` adds two hard guards: it rejects any delivery if the forwarder was ever set to `address(0)` post-deployment, and it requires at least one complete workflow identity option to be configured — an unconfigured receiver rejects all reports with `WorkflowIdentityNotConfigured`. Two options are accepted: **(a)** `workflowId` is set (binds the receiver to one specific workflow), or **(b)** both `workflowOwner` and `workflowName` are set together (binds to a named workflow from a specific owner). Either piece of option (b) alone is insufficient.
+- **Outbound — what a report may make the receiver do**: a **closed-by-default allowlist** of `(target, function-selector)` pairs. Inbound checks only prove a report came from your workflow; they do **not** constrain the `(target, data)` it carries. Until you allowlist a pair with `setCallAllowed`, the receiver rejects it.
+
+On top of these layers, the owner can trigger a global **emergency pause** via `pause(bool retryable)`. While paused, all deliveries are rejected; the `retryable` flag chosen at pause time decides whether they stay retryable (revert) or are dropped (consumed). See [Emergency Pause](#3d-emergency-pause-optional-but-recommended-for-production).
-`AutomationReceiver` inherits `ReceiverTemplate`, so it validates the configured CRE forwarder before processing reports. For production, also configure workflow identity checks such as expected workflow ID and/or expected workflow owner address, or narrow the receiver to known target contracts and function selectors. A generic receiver that accepts arbitrary `(target, data)` is convenient for migration, but it should not be left broadly reusable without explicit authorization controls.
+## Migration Path Mapping
+
+| Your Automation Setup | CRE Equivalent | Migration Type |
+| :-------------------- | :-------------- | :------------- |
+| Time-based upkeep | Cron Trigger | `CRON` |
+| Custom logic upkeep | Cron + EVM Read | `CUSTOM` |
+| Log trigger upkeep | EVM Log Trigger | `LOG` |
## Get started
-### 1. Initialize your project
+### 1. Initialize the template
Use the CRE CLI to scaffold a new project using the migration template:
@@ -149,133 +159,180 @@ Use the CRE CLI to scaffold a new project using the migration template:
cre init --template=automation-migration-ts --project-name my-automation-migration --workflow-name my-workflow
```
-The template is available in the `main` branch. You can scaffold a new project using the CRE CLI:
-
-```shell
-cre init --template=automation-migration-ts
-```
-
Or browse the template directly at [`smartcontractkit/cre-templates`](https://github.com/smartcontractkit/cre-templates/tree/main/starter-templates/automation-migration).
-### 2. Deploy the Bridge
+### 2. Build and deploy the Bridge
-Deploy `AutomationReceiver.sol` (found in the template) to your target chain, passing the CRE `KeystoneForwarder` address for that network to the constructor. Find the correct forwarder address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts). This contract acts as a translation layer, allowing you to reuse your existing `checkUpkeep`, `checkLog`, and `performUpkeep` logic while moving trigger and check orchestration into CRE.
+Deploy `AutomationReceiver.sol` to your target chain. You only need to do this once to support multiple upkeeps.
-### 3. Configure and Authorize the Receiver
+The `contracts/evm` directory ships a ready-to-use [Foundry](https://book.getfoundry.sh/) project — a `foundry.toml` and a vendored copy of the only OpenZeppelin files used (`Ownable`, `Pausable`, `Context`). No `forge install` or local node is required:
-Update `my-workflow/config.test.json` in your new project with your previously deployed AutomationReceiver contract address, target contract address, migration type (`CRON`, `CUSTOM`, or `LOG`), schedule, and log filters if applicable.
+```shell
+cd contracts/evm
+forge build
+forge test # 54 tests covering the receiver + permission template
+
+# Deploy, passing the CRE Forwarder address for your DON as the constructor arg
+forge create src/AutomationReceiver.sol:AutomationReceiver \
+ --rpc-url "$RPC_URL" \
+ --private-key "$PRIVATE_KEY" \
+ --constructor-args "$FORWARDER_ADDRESS"
+```
-#### 3a. Allow Upkeep Calls
+`FORWARDER_ADDRESS` is the CRE Forwarder for your target network — it is the **only** address allowed to call `onReport`. Look it up in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts) for your network; do not guess it. A wrong value means the DON's reports are rejected. The constructor blocks `address(0)`, and even if the owner ever sets the forwarder to `address(0)` post-deployment (via `setForwarderAddress`), `AutomationReceiver._processReport` will reject all subsequent deliveries with `InvalidForwarderAddress`.
-Before your workflow can call your target contract through the receiver, you must authorize the function call using `setCallAllowed()`. This setter transaction grants the receiver permission to forward calls to your target contract with a specific function selector.
+### 3. Configure and authorize the receiver
-**Parameters:**
+The receiver rejects every outbound call until you allowlist it. You must set up both the call allowlist and the workflow identity checks.
-- **target** (address): The address of your existing Automation upkeep contract
-- **selector** (bytes4): The 4-byte function selector you want to allow (e.g., `0x4585e33b` for `performUpkeep`)
-- **allowed** (bool): Set to `true` to enable the call
+#### 3a. Allow Upkeep Calls
-**Example transaction:**
+For each migrated upkeep, allowlist the exact `(target, selector)` the workflow will invoke:
-```
-Function: setCallAllowed (0xb1e17766)
-target (address): 0x0971Ad145A5462f6Ae18b1aD2b9c9b0c6d8CC9C8
-selector (bytes4): 0x4585e33b
-allowed (bool): true
+```shell
+# Custom-logic / log-trigger upkeeps call performUpkeep(bytes)
+cast send "$RECEIVER_ADDRESS" \
+ "setCallAllowed(address,bytes4,bool)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" true \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+
+# Time-based upkeeps call your specific function, e.g. performAction(uint256)
+cast send "$RECEIVER_ADDRESS" \
+ "setCallAllowed(address,bytes4,bool)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performAction(uint256)')" true \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
-#### 3b. Set Workflow Identity Checks
+**Parameters:**
-For production deployments, configure additional authorization layers by setting expected workflow identity parameters. These optional checks ensure only authorized workflows can invoke your receiver.
+- **target** (address): The address of your existing upkeep contract. When allowing (`allowed = true`) it must be a deployed contract — `setCallAllowed` reverts with `TargetHasNoCode` if the address has no code. The code check is skipped when revoking (`allowed = false`), so an entry can always be removed even if the target has since self-destructed.
+- **selector** (bytes4): The 4-byte function selector, computed from the function signature via `cast sig`. It must match the function the workflow encodes (`performUpkeep` for `CUSTOM`/`LOG`, or your `targetFunction` for `CRON`) — a mismatch makes `onReport` revert with `CallNotAllowed`.
+- **allowed** (bool): `true` to allow, `false` to revoke.
-**Available setters:**
+#### 3b. Configure the Consumer Gas Limit (Recommended)
-- **setExpectedAuthor** — Restrict calls to workflows authored by a specific address
- - Parameter: `_author` (address)
-- **setExpectedWorkflowId** — Restrict calls to a specific workflow ID
- - Parameter: `_workflowId` (string)
-- **setExpectedWorkflowName** — Restrict calls to workflows with a specific name
- - Parameter: `_workflowName` (string)
+Set the minimum gas the receiver must have available before forwarding the call to your upkeep consumer contract. When configured, `_processReport` reverts with `InsufficientGas` — causing the CRE Forwarder to record the delivery as **failed and retryable** — if the incoming gas is below `consumerGasLimit + consumerGasLimit / 63 + 7,000` (the on-chain overhead).
-**Example transactions:**
+The limit is configured **per `(target, selector)` pair**. Zero (the default) disables the guard and preserves fire-and-forget semantics, mirroring Chainlink Automation's behavior where a failed `performUpkeep` simply ends that round.
+```shell
+# Custom-logic / log-trigger upkeeps: performUpkeep(bytes)
+cast send "$RECEIVER_ADDRESS" \
+ "setConsumerGasLimit(address,bytes4,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" "$PERFORM_GAS_LIMIT" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+
+# Time-based upkeeps: your specific function, e.g. performAction(uint256)
+cast send "$RECEIVER_ADDRESS" \
+ "setConsumerGasLimit(address,bytes4,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performAction(uint256)')" "$PERFORM_GAS_LIMIT" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
-Function: setExpectedAuthor
-_author (address): 0x5c5c48fc95d68f88b4a13e0c9b0c6d8cc9c8c8c
-Function: setExpectedWorkflowId
-_workflowId (string): 0x0971ad145a5462f6ae18b1ad2b9c9b0c6d8cc9c8
+#### 3c. Set Workflow Identity Checks
-Function: setExpectedWorkflowName
-_workflowName (string): automation-migration-test
-```
+
-### Parameters Quick Reference
+- **Option A — `workflowId`**: set the workflow ID; owner and name are not required.
+- **Option B — `workflowOwner` + `workflowName`**: set both together; either piece alone is insufficient, and `workflowId` is not required.
-#### Getting the Target Address
+```shell
+# Option A — identify by workflow ID
+cast send "$RECEIVER_ADDRESS" \
+ "setExpectedWorkflowId(bytes32)" \
+ "$WORKFLOW_ID" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+
+# Option B — identify by owner and name (both required)
+cast send "$RECEIVER_ADDRESS" \
+ "setExpectedAuthor(address)" \
+ "$WORKFLOW_OWNER_ADDRESS" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+
+cast send "$RECEIVER_ADDRESS" \
+ "setExpectedWorkflowName(string)" \
+ "$WORKFLOW_NAME" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+```
-The target address is the address of your existing **Automation upkeep contract** — the contract that currently implements `performUpkeep()`. You can find this by:
+You may also combine both options for the strongest guarantee (e.g. set all three fields). Note that `setExpectedWorkflowName` must always be paired with `setExpectedAuthor` — workflow names are unique per owner, not globally.
-- Reviewing your Automation registry registration
-- Checking your deployment scripts or configuration
-- Looking up the contract address in your blockchain explorer
+#### 3d. Emergency Pause (Optional but recommended for production)
-#### Computing the Function Selector
+The receiver includes an owner-only global emergency stop built on OpenZeppelin's `Pausable`. This is the CRE equivalent of Chainlink Automation's registry-wide pause: it halts report processing across the whole receiver (there is no per-upkeep `pauseUpkeep` equivalent).
-The function selector is the first 4 bytes of the keccak256 hash of the function signature. Use the `cast` command-line tool to compute it:
+`pause(bool retryable)` lets you decide, per pause, what happens to reports delivered while paused:
+
+- **`pause(true)` — retryable**: `_processReport` reverts with `EnforcedPause`, so the CRE Forwarder records the transmission as failed and retryable. Reports resume delivery once you `unpause()`.
+- **`pause(false)` — drop**: `_processReport` emits `ReportSkippedWhilePaused` and consumes the report. Dropped reports are not redelivered after `unpause()`.
```shell
-# For performUpkeep(bytes)
-cast sig 'performUpkeep(bytes)'
-# Output: 0x4585e33b
+# Halt processing, keeping reports retryable
+cast send "$RECEIVER_ADDRESS" "pause(bool)" true --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+
+# Halt processing, dropping reports delivered while paused
+cast send "$RECEIVER_ADDRESS" "pause(bool)" false --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
-# For custom functions
-cast sig 'myCustomFunction(uint256,address)'
-# Output: 0xabcdef12
+# Resume report processing
+cast send "$RECEIVER_ADDRESS" "unpause()" --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
-Alternatively, compute it programmatically in your test or deployment script:
+Pausing or deleting a workflow stops the DON from producing _new_ reports, but reports already signed remain permissionlessly deliverable afterward. The on-chain `pause()` closes that gap by rejecting those in-flight reports at the receiver.
-```ts
-import { getAddress, toFunctionSelector } from "viem"
+#### 3e. Configure Block Number Monotonicity / Staleness Protection (Optional)
+
+Chainlink Automation's registry rejected any report whose trigger block preceded the last performed block. CRE's delivery path does **not** preserve this ordering — the Forwarder deduplicates only exact retransmissions of the same report. If your upkeep relied on that implicit ordering (typical for conditional-trigger and log-trigger upkeeps), enable this opt-in check per `(target, selector)` pair (off by default). The workflow always stamps a block number into the report payload, so you can enable this later without any workflow change.
-const selector = toFunctionSelector("performUpkeep(bytes)")
-// Result: 0x4585e33b
+```shell
+# Enable with an explicit floor (recommended): the first accepted report must be >= this block
+cast send "$RECEIVER_ADDRESS" \
+ "setBlockNumberCheck(address,bytes4,bool,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" true "$INITIAL_BLOCK" \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
+
+# Disable the check (also clears the stored floor)
+cast send "$RECEIVER_ADDRESS" \
+ "setBlockNumberCheck(address,bytes4,bool,uint256)" \
+ "$TARGET_ADDRESS" "$(cast sig 'performUpkeep(bytes)')" false 0 \
+ --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
-### Example Transaction Outputs
+Prefer an explicit `initialBlockNumber` over passing `0` (which snapshots `block.number` at that moment) — a report generated between deployment and that snapshot could still carry a higher block number and would not be rejected. Disabling the check is not retroactive: it resets the floor to `0` so out-of-order reports execute again going forward, but it does not revive reports already skipped as stale.
-A successful `setCallAllowed` transaction on a block explorer shows:
+### 4. Configure the workflow
-```
-Transaction Type: Contract Interaction
-Function: setCallAllowed(address,bytes4,bool)
-Status: ✓ Success
-From: 0xYourAddress
-To: 0xAutomationReceiverAddress
-Gas Used: 35,400 gas
-
-Decoded Input:
-target: 0x0971Ad145A5462f6Ae18b1aD2b9c9b0c6d8CC9C8
-selector: 0x4585e33b
-allowed: true
-```
+Update `my-workflow/config.test.json`:
+
+- `receiverAddress`: Your deployed `AutomationReceiver`.
+- `targetAddress`: Your existing Automation contract.
+- `migrationType`: `CRON`, `CUSTOM`, or `LOG`.
+- `schedule`: Required for `CRON` and `CUSTOM`.
+- `targetFunction` and `targetInputs`: Required for `CRON`. These are ABI-encoded at runtime from the config string (e.g. `"performAction(uint256)"`), so unlike the `CUSTOM`/`LOG` paths they are not statically type-checked — a malformed signature or mismatched inputs fails when the workflow runs.
+- `logTriggerAddress`, `logTriggerEventSignature`, and optional `topic1`/`topic2`/`topic3`: Required for `LOG`.
-### 3c. Configure and Simulate
+
-Then install dependencies and simulate from the project root. The CRE CLI prepares the workflow build tooling during simulation.
+Install workflow dependencies once:
```shell
cd my-workflow
bun install
cd ..
-
-cre workflow simulate my-workflow --target=test-settings
```
-For log-trigger migrations, provide a transaction hash containing the event so simulation does not wait for a live event:
+### 5. Simulate
```shell
+# For CRON (time-based) or CUSTOM (custom logic) — both run on the cron scheduler
+cre workflow simulate my-workflow --target=test-settings
+
+# For Log Trigger (requires a transaction hash containing the event)
cre workflow simulate my-workflow \
--target=test-settings \
--non-interactive \
@@ -284,7 +341,7 @@ cre workflow simulate my-workflow \
--evm-event-index=0
```
-### 4. Deploy
+### 6. Deploy
Once verified, deploy your workflow to the CRE DON:
@@ -294,16 +351,69 @@ cre workflow deploy my-workflow --target=production-settings
For end-to-end runnable examples, see the [`smartcontractkit/cre-templates`](https://github.com/smartcontractkit/cre-templates/tree/main/starter-templates/automation-migration) repository.
+## Technical Details
+
+### Log Mapping
+
+Legacy Automation contracts expect a specific `Log` struct in `checkLog`. The template includes a utility (`mapLogToAutomation`) that maps the CRE `EVMLog` into that struct so your existing on-chain decoding logic keeps working.
+
+The CRE log does not carry the block timestamp, so `log.timestamp` is always mapped to `0`. If your `checkLog` relies on `log.timestamp`, read it from another source (e.g. an EVM read of the block) instead.
+
+### Read Finality
+
+The generated `checkUpkeep` / `checkLog` bindings read at the **last finalized block** so every DON node observes the same state and reaches consensus deterministically. This differs from Automation, which simulates against the chain head. On Ethereum mainnet finality lags the head by ~13 minutes; on most L2s it is seconds. Account for this latency when migrating time-sensitive interval checks.
+
+### Gas Limit
+
+`writeGasLimit` (default `"500000"`) caps the on-chain execution gas forwarded to `onReport`. The on-chain guard formula is `consumerGasLimit + consumerGasLimit/63 + 7,000`, but on the **first** delivery to a `(target, selector)` pair an additional ~17,500 gas is consumed before that check point (cold `SLOAD`s for identity checks, the allowlist lookup, the gas-limit read, and `paused()`). After slots warm, pre-guard overhead drops to ~5,500 gas. With the block-number monotonicity check enabled, budget a further ~10,000 gas. Set `writeGasLimit` to at least your `performGasLimit` plus ~30,000 (or ~40,000 with the monotonicity check enabled). If deliveries still fail with `InsufficientGas`, increase `writeGasLimit` in 10,000-gas increments until they pass.
+
+## Compatibility
+
+Migrating **without redeploying** your upkeep contract requires that contract to let you re-point who is authorized to call it:
+
+- Contracts that expose a setter for the Automation Forwarder / caller (the recommended Automation pattern) can simply point it at the deployed `AutomationReceiver`.
+- Contracts that **hardcode** the Automation registry/forwarder with no setter, or that have an immutable role for it, cannot be migrated in place and must be redeployed.
+
+After migration, the `msg.sender` your target sees is the `AutomationReceiver` address (not the Automation Forwarder), so authorize that address in whatever permission check your contract uses.
+
+## Out of Scope
+
+Off-chain `offchainConfig` / gas-price-threshold controls are out of scope for this template. In CRE these are expressed in the workflow, not on the registry.
+
+
+
## Troubleshooting
### CallNotAllowed Error
If your workflow execution fails with a `CallNotAllowed` error, verify the following:
-1. **Function selector mismatch** — Ensure the selector you configured in `setCallAllowed()` matches the function your workflow is attempting to call. Use `cast sig 'functionName(types)'` to compute the correct selector. For more information, see the [Function Selector Reference](https://rareskills.io/post/function-selector).
-
+1. **Function selector mismatch** — Ensure the selector you configured in `setCallAllowed()` matches the function your workflow is attempting to call. Use `cast sig 'functionName(types)'` to compute the correct selector.
2. **Permission not set** — Confirm that `setCallAllowed()` was called with `allowed: true` for your target contract and function selector.
+3. **Target has no code** — `setCallAllowed` reverts with `TargetHasNoCode` if `target` isn't a deployed contract when `allowed` is `true`.
+
+### WorkflowIdentityNotConfigured Error
+
+The receiver rejects all reports until at least one complete workflow identity option is set: `setExpectedWorkflowId`, or both `setExpectedAuthor` and `setExpectedWorkflowName` together. See [3c. Set Workflow Identity Checks](#3c-set-workflow-identity-checks).
+
+### InsufficientGas Error
+
+The gas forwarded with the report fell below `consumerGasLimit + consumerGasLimit / 63 + 7,000` for that `(target, selector)` pair. Increase `writeGasLimit` in your workflow config — see [Gas Limit](#gas-limit).
+
+### EnforcedPause Error / ReportSkippedWhilePaused Event
+
+The receiver is paused. Check `paused()` and `retryableWhilePaused()`, and call `unpause()` to resume processing.
+
+### Forwarder mismatch
+
+Verify that the `KeystoneForwarder` address passed to the `AutomationReceiver` constructor matches the address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts).
-3. **Workflow authorization** — If you set workflow identity checks (`setExpectedAuthor`, `setExpectedWorkflowId`, or `setExpectedWorkflowName`), ensure the workflow deploying the call matches those values.
+## Resources
-4. **Forwarder mismatch** — Verify that the `KeystoneForwarder` address passed to the `AutomationReceiver` constructor matches the address for your chain in the [Forwarder Directory](/cre/guides/workflow/using-evm-client/forwarder-directory-ts).
+- [Chainlink Automation Documentation](https://docs.chain.link/automation)
+- [CRE Getting Started Guide](/cre)
+- [Legacy Migration Toolkit](https://github.com/smartcontractkit/cla-cre-migration) (alternative manual-encoding approach)