Skip to content
Draft
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Small, focused examples. Each directory includes its own README.

- **`building-blocks/kv-store`** – Read/modify/write a value in **AWS S3** using SigV4-signed HTTP requests, CRE secrets, and a **consensus read → single write** flow.
- **`building-blocks/read-data-feeds`** – Read `decimals()` and `latestAnswer()` from **Chainlink Data Feeds** on a schedule; includes ABI/bindings and RPC config examples.
- **`building-blocks/webhook-alerting`** – Read a **Chainlink Data Feed** on-chain and trigger a **PagerDuty alert** via ConfidentialHTTPClient; demonstrates **VaultDON secret injection** with `{{.secretName}}` body templates.
- **`building-blocks/webhook-notification`** – Read a **Chainlink Data Feed** on-chain and send a price notification to **Slack** or **Telegram** via webhook; demonstrates **enclave privacy** for URL-embedded credentials.

### Starter Templates
More complex, end-to-end workflows. Each directory includes its own README (some marked **WIP**).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
kind: building-block
id: webhook-alerting-go
projectDir: .
title: "Webhook Alerting (Go)"
description: "Read a Chainlink Data Feed on-chain and trigger a PagerDuty alert via ConfidentialHTTPClient; demonstrates VaultDON secret injection with {{.secretName}} body templates."
language: go
category: notifications
tags:
- data-feeds
- webhook
- alerting
- confidential-http
- secrets
workflows:
- dir: my-workflow
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.env
135 changes: 135 additions & 0 deletions building-blocks/webhook-alerting/webhook-alerting-go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<div style="text-align:center" align="center">
<a href="https://chain.link" target="_blank">
<img src="https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/docs/logo-chainlink-blue.svg" width="225" alt="Chainlink logo">
</a>

[![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/smartcontractkit/cre-templates/blob/main/LICENSE)
[![CRE Home](https://img.shields.io/static/v1?label=CRE\&message=Home\&color=blue)](https://chain.link/chainlink-runtime-environment)
[![CRE Documentation](https://img.shields.io/static/v1?label=CRE\&message=Docs\&color=blue)](https://docs.chain.link/cre)

</div>

## Webhook Alerting (Go)

Read a Chainlink Data Feed on-chain and trigger a **PagerDuty alert** via the ConfidentialHTTPClient.

This building block demonstrates **VaultDON secret injection** — the PagerDuty routing key is referenced as `{{.pagerdutyRoutingKey}}` in the JSON request body and resolved at runtime by the secure enclave (from env vars during simulation, from VaultDON in production).

> **Looking for Slack/Telegram notifications?** See the sibling [`webhook-notification`](../webhook-notification/) building block, which demonstrates enclave privacy for URL-embedded credentials.

### Capabilities used

- **EVM Client** — read on-chain price feed data
- **Confidential HTTP Client** — POST the alert with secret injection (`VaultDonSecrets`)
- **Cron Scheduler** — fire on a configurable schedule

## Quick start

### 1) Add the ABI

The ABI is already included at:

```
contracts/evm/src/abi/PriceFeedAggregator.abi
```

### 2) Generate bindings

From your **project root** (where `project.yaml` lives):

```bash
cre generate-bindings evm
```

This creates Go bindings under:

```
contracts/evm/src/generated/price_feed_aggregator/...
```

After generation, if your module picked up new deps, run:

```bash
go mod tidy
```

### 3) Configure RPC in `project.yaml`

Add an RPC for the chain you want to read from. For Arbitrum One mainnet:

```yaml
rpcs:
- chain-name: ethereum-mainnet-arbitrum-1
url: <YOUR_ARBITRUM_MAINNET_RPC_URL>
```

### 4) Set your PagerDuty routing key

Add your routing key to `.env`:

```
PAGERDUTY_ROUTING_KEY=YOUR_PAGERDUTY_ROUTING_KEY_HERE
```

The `secrets.yaml` file maps the vault secret name `pagerdutyRoutingKey` to the `PAGERDUTY_ROUTING_KEY` env var. During simulation the CLI reads the value from `.env`; in production the enclave fetches it from VaultDON.

### 5) Configure the workflow

Update `my-workflow/config.production.json` with your settings:

```json
{
"schedule": "0 */10 * * * *",
"chainName": "ethereum-mainnet-arbitrum-1",
"feed": {
"name": "ETH/USD",
"address": "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612"
},
"endpoint": "https://events.pagerduty.com/v2/enqueue",
"severity": "critical",
"source": "cre-workflow"
}
```

**Configuration fields:**

| Field | Description |
|---|---|
| `schedule` | 6-field cron expression (e.g., every 10 minutes at second 0) |
| `chainName` | Must match the RPC entry in `project.yaml` |
| `feed.name` | Human-readable feed name (e.g., `"ETH/USD"`) |
| `feed.address` | Chainlink Data Feed proxy address on the target chain |
| `endpoint` | PagerDuty Events API v2 URL (or `https://httpbin.org/post` for testing) |
| `severity` | PagerDuty severity: `"critical"`, `"error"`, `"warning"`, or `"info"` |
| `source` | Source identifier included in the alert payload |

### 6) Run a local simulation

The staging config uses `https://httpbin.org/post` as a test echo endpoint:

```bash
cre workflow simulate my-workflow
```

You should see output similar to:

```
Workflow compiled
[SIMULATION] Simulator Initialized

[SIMULATION] Running trigger trigger=cron-trigger@1.0.0
[USER LOG] msg="Data feed read" chain=ethereum-mainnet-arbitrum-1 feed=ETH/USD address=0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612 decimals=8 latestAnswerRaw=378968000000 latestAnswerScaled=3789.68
[USER LOG] msg="Sending PagerDuty alert" endpoint=https://httpbin.org/post
[USER LOG] msg="Alert response" statusCode=200

Workflow Simulation Result:
"{\"feed\":\"ETH/USD\",\"address\":\"0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612\",\"decimals\":8,...}"
```

### How secret injection works

1. `secrets.yaml` declares `pagerdutyRoutingKey` mapped to the `PAGERDUTY_ROUTING_KEY` env var.
2. The workflow builds a JSON body containing the literal string `{{.pagerdutyRoutingKey}}`.
3. The `ConfidentialHTTPClient.SendRequest()` call includes `VaultDonSecrets` with the key `"pagerdutyRoutingKey"`.
4. Before sending the request, the enclave resolves `{{.pagerdutyRoutingKey}}` in the body with the actual secret value.
5. The secret never appears in logs or leaves the enclave boundary.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"_aggregator","type":"address"},{"internalType":"address","name":"_accessController","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"int256","name":"current","type":"int256"},{"indexed":true,"internalType":"uint256","name":"roundId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"updatedAt","type":"uint256"}],"name":"AnswerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"roundId","type":"uint256"},{"indexed":true,"internalType":"address","name":"startedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"startedAt","type":"uint256"}],"name":"NewRound","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"accessController","outputs":[{"internalType":"contract AccessControllerInterface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aggregator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_aggregator","type":"address"}],"name":"confirmAggregator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"description","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_roundId","type":"uint256"}],"name":"getAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"_roundId","type":"uint80"}],"name":"getRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_roundId","type":"uint256"}],"name":"getTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"","type":"uint16"}],"name":"phaseAggregators","outputs":[{"internalType":"contract AggregatorV2V3Interface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"phaseId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_aggregator","type":"address"}],"name":"proposeAggregator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proposedAggregator","outputs":[{"internalType":"contract AggregatorV2V3Interface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"_roundId","type":"uint80"}],"name":"proposedGetRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposedLatestRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_accessController","type":"address"}],"name":"setController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
Loading
Loading