Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions starter-templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ They are more comprehensive than **building-blocks**, and can be adapted into yo
6. **Circuit Breaker** — [`./circuit-breaker`](./circuit-breaker)
Monitor on-chain events for anomalies, automatically pause contracts when price deviation thresholds are breached. Dual-trigger pattern combining LogTrigger (event-driven) with Cron (periodic health checks).

7. **Event Reactor** — [`./event-reactor`](./event-reactor)
Listen for on-chain events (LogTrigger), read contract state, and respond on-chain. Demonstrates event-driven workflows with typed event decoding via generated bindings.

> Each subdirectory includes its own README with template-specific steps and example logs.

## License
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
kind: starter-template
id: event-reactor-ts
projectDir: .
title: "Event Reactor (TypeScript)"
description: "Listen for on-chain events, fetch off-chain context, and respond on-chain."
language: typescript
category: workflow
tags:
- event
- log-trigger
- http
- on-chain-write
- compliance
workflows:
- dir: my-workflow
postInit: |
Demo contracts are pre-deployed on Sepolia. See README.md for details.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.env
168 changes: 168 additions & 0 deletions starter-templates/event-reactor/event-reactor-ts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Event Reactor — CRE Starter Template (TypeScript)

Listen for on-chain events, read contract state, and respond on-chain.

**⚠️ DISCLAIMER**

This template is an educational example to demonstrate how to interact with Chainlink systems, products, and services. It is provided **"AS IS"** and **"AS AVAILABLE"** without warranties of any kind, has **not** been audited, and may omit checks or error handling for clarity. **Do not use this code in production** without performing your own audits and applying best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs generated due to errors in code.

---

## Overview

This template demonstrates the **event -> read -> decide -> write** pattern using Chainlink CRE (Compute Runtime Environment). Unlike the cron-based Keeper Bot and Vault Harvester templates, this one uses a **LogTrigger** — it reacts to on-chain events in real-time.

### Use Cases

- **Large transfer monitoring**: Detect large transfers, check if sender is already flagged, flag on-chain
- **Compliance checks**: Token transfer event -> check compliance rules -> approve or reject
- **Governance reactor**: Proposal created on-chain -> read vote data -> submit result
- **Price deviation response**: Price update event -> check against threshold -> flag deviation
- **Agent response**: Agent emits request event -> CRE reads state -> writes response on-chain

## Architecture

```
┌──────────────┐
│ Monitored │ emits LargeTransfer(from, to, amount, timestamp)
│ Contract │──────────────────────────────────┐
└──────────────┘ │
v
┌─────────────────────────────────────────────────────────────┐
│ CRE DON │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ LogTrigger │-->│ Decode Log │-->│ Check State │ │
│ │ (event) │ │ (typed via │ │ (already │ │
│ │ │ │ bindings) │ │ flagged?) │ │
│ └──────────────┘ └──────────────┘ └───────┬────────┘ │
│ │ │
│ ┌──────────v────────┐ │
│ │ Decision Logic │ │
│ │ not flagged -> │ │
│ │ flag on-chain │ │
│ │ already flagged ->│ │
│ │ skip │ │
│ └──────────┬────────┘ │
└─────────────────────────────────────────────────┼───────────┘
┌──────────v──────────┐
│ KeystoneForwarder │
│ -> Reactor._process │
└─────────────────────┘
```

## Components

### CRE Workflow (`my-workflow/`)

The TypeScript workflow runs off-chain inside CRE DON:

1. **LogTrigger** fires when `MonitoredToken` emits a `LargeTransfer` event
2. **Decodes** the event using generated typed bindings (from, to, amount, timestamp)
3. **Reads** `ReactorConsumer` state to check if sender is already flagged
4. **If not flagged**: sends a signed report to flag the address on-chain
5. **If already flagged**: logs and skips

### Contracts

**MonitoredToken** (`contracts/evm/src/MonitoredToken.sol`):
- Emits `LargeTransfer(from, to, amount, timestamp)` events
- `simulateLargeTransfer()` — test function to emit events

**ReactorConsumer** (`contracts/evm/src/ReactorConsumer.sol`):
- `flaggedAddresses(address)` — check if an address is flagged
- `totalFlags()` — total number of flagged addresses
- `_processReport(bytes)` — decodes action type, target, reason and flags the address

## Getting Started

Demo contracts are pre-deployed on Sepolia with a test event already emitted — this template works out of the box.

### Prerequisites

- [Bun](https://bun.sh/) runtime installed
- [CRE CLI](https://docs.chain.link/cre) installed

### 1. Install Dependencies

```bash
cd my-workflow && bun install && cd ..
cd contracts && bun install && cd ..
```

### 2. Run Tests

```bash
cd my-workflow && bun test
```

### 3. Simulate

LogTrigger simulation requires a real transaction hash that emitted the event:

```bash
# Use the pre-emitted event
cre workflow simulate my-workflow --target staging-settings --non-interactive \
--trigger-index 0 \
--evm-tx-hash 0xe5ac97df3f93c5fdc89690a71d68a8e5ebd35ec8ba9c6cb5d1069f5818222553 \
--evm-event-index 0
```

### 4. Emit Your Own Event

To generate a fresh `LargeTransfer` event on the pre-deployed demo contract:

```bash
cast send 0x805A04e5C8b2dcb2B26C9e9C9aa12ce34374A35b \
"simulateLargeTransfer(address,uint256)" \
0x000000000000000000000000000000000000dEaD 2000000000000000000000 \
--private-key $CRE_ETH_PRIVATE_KEY \
--rpc-url https://ethereum-sepolia-rpc.publicnode.com
```

Copy the `transactionHash` from the output and simulate with it:

```bash
cre workflow simulate my-workflow --target staging-settings --non-interactive \
--trigger-index 0 \
--evm-tx-hash <YOUR_TX_HASH> \
--evm-event-index 0
```

### 5. Deploy Your Own Contracts (Optional)

Deploy both contracts to Sepolia using Foundry:

**MonitoredToken** (no constructor args):
```bash
forge create src/MonitoredToken.sol:MonitoredToken --broadcast --private-key <KEY> --rpc-url <RPC>
```

**ReactorConsumer** (forwarder address):
```bash
forge create src/ReactorConsumer.sol:ReactorConsumer --broadcast --private-key <KEY> --rpc-url <RPC> \
--constructor-args 0x15fc6ae953e024d975e77382eeec56a9101f9f88
```

Then emit an event to get a tx hash for simulation:
```bash
cast send <MONITORED_TOKEN> "simulateLargeTransfer(address,uint256)" 0x000000000000000000000000000000000000dEaD 2000000000000000000000 \
--private-key <KEY> --rpc-url <RPC>
```

## Customization

- **Change the monitored event**: Modify `MonitoredToken.sol` or point to any contract that emits events
- **Change the reaction logic**: Modify `onLargeTransfer` in `workflow.ts` and `_processReport` in `ReactorConsumer.sol`
- **Add off-chain API calls**: Use `cre.capabilities.HTTPClient` to fetch external data before deciding

## Security

- The contracts are **demos** — audit and customize before production use
- `ReceiverTemplate` validates that only CRE Forwarder can call `onReport()`
- Never commit `.env` files or secrets

## License

MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { parseAbi } from "viem"

export const MonitoredTokenAbi = parseAbi([
"event LargeTransfer(address indexed from, address indexed to, uint256 amount, uint256 timestamp)",
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { parseAbi } from "viem"

export const ReactorConsumerAbi = parseAbi([
"function flaggedAddresses(address) view returns (bool)",
"function totalFlags() view returns (uint256)",
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ReactorConsumer'
export * from './MonitoredToken'
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(
bytes4 interfaceId
) external view returns (bool);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC165} from "./IERC165.sol";

/// @title IReceiver - receives keystone reports
/// @notice Implementations must support the IReceiver interface through ERC165.
interface IReceiver is IERC165 {
/// @notice Handles incoming keystone reports.
/// @dev If this function call reverts, it can be retried with a higher gas
/// limit. The receiver is responsible for discarding stale reports.
/// @param metadata Report's metadata.
/// @param report Workflow report.
function onReport(
bytes calldata metadata,
bytes calldata report
) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/**
* @title MonitoredToken
* @notice A simple contract that emits LargeTransfer events when transfers
* exceed a threshold. CRE watches these events via LogTrigger.
*
* In production, this would be your actual token or protocol contract.
*/
contract MonitoredToken {
event LargeTransfer(
address indexed from,
address indexed to,
uint256 amount,
uint256 timestamp
);

uint256 public largeTransferThreshold = 1000 ether;

/// @notice Simulate a large transfer that triggers CRE event monitoring
function simulateLargeTransfer(address to, uint256 amount) external {
if (amount >= largeTransferThreshold) {
emit LargeTransfer(msg.sender, to, amount, block.timestamp);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {ReceiverTemplate} from "./ReceiverTemplate.sol";

/**
* @title ReactorConsumer
* @notice Receives CRE reports and takes action (e.g., flag an address).
* A monitored contract emits LargeTransfer events; CRE workflow
* checks off-chain data and writes back a flagging action.
*
* Customize _processReport() with your own reaction logic.
*/
contract ReactorConsumer is ReceiverTemplate {
mapping(address => bool) public flaggedAddresses;
uint256 public totalFlags;

event AddressFlagged(address indexed target, string reason, uint256 timestamp);
event ActionTaken(uint8 indexed actionType, address indexed target, uint256 timestamp);

constructor(address forwarder) ReceiverTemplate(forwarder) {}

/// @notice Called by CRE Forwarder via ReceiverTemplate after metadata validation
/// @param report ABI-encoded (uint8 actionType, address target, string reason)
function _processReport(bytes calldata report) internal override {
(uint8 actionType, address target, string memory reason) = abi.decode(
report, (uint8, address, string)
);

if (actionType == 1) {
// FLAG action
flaggedAddresses[target] = true;
totalFlags++;
emit AddressFlagged(target, reason, block.timestamp);
}

emit ActionTaken(actionType, target, block.timestamp);
}
}
Loading
Loading