Skip to content
Open
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: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/workflows/ @smartcontractkit/foundations @smartcontractkit/core @smartcontractkit/op-tooling
/billing/ @smartcontractkit/cre-business @smartcontractkit/core @smartcontractkit/op-tooling
/storage-service/ @smartcontractkit/dev-services @smartcontractkit/op-tooling
/.github/workflows/cre-validations.yml @smartcontractkit/keystone @smartcontractkit/op-tooling
/.github/workflows/cre-*.yml @smartcontractkit/keystone @smartcontractkit/op-tooling
/linking-service/ @smartcontractkit/cre-business @smartcontractkit/core @smartcontractkit/op-tooling

# Data
Expand Down
139 changes: 139 additions & 0 deletions .github/workflows/cre-add-evm-chain.yml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to "scope" these workflows to the CRE team.

Could you please:

  1. change the filenames to cre-add-evm-chain.yml and cre-notify-sdk-go.yml
  2. update CODEOWNERS so that the cre team owns .github/workflows/cre-*.yml

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. renamed with cre-prefix
  2. updated codeowners with .github/workflows/cre-*.yml

Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Adds a new EVM chain to client.proto and creates a PR.
#
# Usage:
# 1. Manual: Actions tab → "Add EVM Chain" → Enter selector → Run
# 2. From another workflow:
# jobs:
# add-chain:
# uses: ./.github/workflows/add-evm-chain.yml
# with:
# selector: "5009297550715157269"
# 3. Via API:
# curl -X POST -H "Authorization: token $TOKEN" \
# -H "Accept: application/vnd.github.v3+json" \
# https://api.github.com/repos/OWNER/REPO/dispatches \
# -d '{"event_type":"add-evm-chain","client_payload":{"selector":"5009297550715157269"}}'
#
# Outputs:
# - Creates a PR adding the chain to cre/capabilities/blockchain/evm/v1alpha/client.proto
# - Annotation with chain name and PR link
# - Warning annotation if chain already exists
name: Add EVM Chain

on:
# Manual trigger from GitHub Actions UI
workflow_dispatch:
inputs:
selector:
description: 'Chain selector (uint64), e.g. 5009297550715157269 for ethereum-mainnet'
required: true
type: string

# Allows this workflow to be called from other workflows
workflow_call:
inputs:
selector:
description: 'Chain selector (uint64), e.g. 5009297550715157269 for ethereum-mainnet'
required: true
type: string

# Allows triggering via GitHub API with event_type "add-evm-chain"
repository_dispatch:
types: [add-evm-chain]

permissions:
contents: write
pull-requests: write

jobs:
add-chain:
runs-on: ubuntu-latest
# Resolve selector from inputs (workflow_dispatch/workflow_call) or client_payload (repository_dispatch)
env:
CHAIN_SELECTOR: ${{ inputs.selector || github.event.client_payload.selector }}
steps:
# Clone the repository so we can modify files and create a PR
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

# Parse .tool-versions file to extract version numbers for Go, protoc, etc.
- name: Set tool versions
id: tool-versions
uses: smartcontractkit/tool-versions-to-env-action@aabd5efbaf28005284e846c5cf3a02f2cba2f4c2 # v1.0.8

# Install Go runtime using the version specified in .tool-versions
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.0.0
with:
go-version: ${{ steps.tool-versions.outputs.golang_version }}

# Install asdf version manager and all plugins defined in .tool-versions
- name: Install asdf
uses: asdf-vm/actions/plugins-add@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v3.0.2
with:
asdf_branch: v0.16.7

# Add asdf binaries and shims to PATH so installed tools are available
- name: Setup asdf environment
run: |
echo "$HOME/.asdf/bin" >> $GITHUB_PATH
echo "$HOME/.asdf/shims" >> $GITHUB_PATH

# Install the protobuf compiler using the version from .tool-versions
- name: Install protoc
run: asdf install protoc

# Install the Go protobuf code generator plugin for protoc
- name: Install protoc-gen-go
run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v${{ steps.tool-versions.outputs.protoc-gen-go_version }}

# Fetch the latest chain-selectors library which contains chain name mappings
- name: Update chain-selectors and download dependencies
working-directory: cre/go
run: |
go get github.com/smartcontractkit/chain-selectors@latest
go mod tidy

# Run the add-evm-chain tool to append the new chain entry to client.proto
- name: Add chain to proto
id: add-chain
working-directory: cre/go
run: |
OUTPUT=$(go run ./tools/add-evm-chain -selector $CHAIN_SELECTOR 2>&1)
echo "$OUTPUT"
# Extract chain name from "added <name> (selector: ...)" or "chain <name> already exists"
CHAIN_NAME=$(echo "$OUTPUT" | sed -n 's/^added \([^ ]*\) .*/\1/p; s/^chain \([^ ]*\) already exists/\1/p')
echo "chain_name=$CHAIN_NAME" >> $GITHUB_OUTPUT
# Warn if chain already exists
if echo "$OUTPUT" | grep -q "already exists"; then
echo "::warning title=Chain Already Exists::$CHAIN_NAME (selector: $CHAIN_SELECTOR) already exists"
fi

# Run go generate to update embedded_gen.go with the modified proto content
- name: Regenerate embedded files
working-directory: cre/go
run: go generate ./...

# Commit all changes and open a pull request for review
- name: Create PR
id: create-pr
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "feat(cre): add EVM chain"
title: "feat(cre): add EVM chain (selector: ${{ env.CHAIN_SELECTOR }})"
body: "Adds EVM chain with selector `${{ env.CHAIN_SELECTOR }}` to client.proto"
branch: add-evm-chain/${{ env.CHAIN_SELECTOR }}

# Output chain and PR details as GitHub Actions annotations and job summary
- name: Output summary
run: |
echo "::notice title=Chain Added::Chain: ${{ steps.add-chain.outputs.chain_name }} (selector: $CHAIN_SELECTOR)"
echo "::notice title=Pull Request::${{ steps.create-pr.outputs.pull-request-url }}"
echo "## 🔗 EVM Chain Added" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| **Chain Name** | \`${{ steps.add-chain.outputs.chain_name }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| **Chain Selector** | \`$CHAIN_SELECTOR\` |" >> $GITHUB_STEP_SUMMARY
echo "| **Pull Request** | ${{ steps.create-pr.outputs.pull-request-url }} |" >> $GITHUB_STEP_SUMMARY
echo "| **PR Number** | #${{ steps.create-pr.outputs.pull-request-number }} |" >> $GITHUB_STEP_SUMMARY
103 changes: 103 additions & 0 deletions .github/workflows/cre-notify-cre-sdk-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Dispatches to cre-sdk-go to bump chainlink-protos version when a PR is merged.
# Uses the CRE Github Automation App (app_id: 2666290) for authentication.
#
# Required Organization Secrets:
# - CRE_GH_AUTOMATION_APP_ID: GitHub App ID
# - CRE_GH_AUTOMATION_APP_PRIVATE_KEY: GitHub App private key
name: Notify cre-sdk-go

permissions:
contents: read

on:
# Manual trigger
workflow_dispatch:
inputs:
version:
description: 'Override version (leave empty to calculate from current commit)'
required: false
type: string
skip_tagging:
description: 'Skip version tagging in cre-sdk-go'
required: false
type: boolean
default: false

pull_request:
types: [closed]
branches:
- main

jobs:
dispatch:
# Run if manually triggered OR if the PR was merged (not just closed)
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: main
fetch-depth: 1

- name: Calculate pseudo-version
id: version
run: |
# Use input version if provided, otherwise calculate from commit
if [ -n "${{ inputs.version }}" ]; then
VERSION="${{ inputs.version }}"
echo "Using provided version: ${VERSION}"
else
# Get the merge commit info
TIMESTAMP=$(TZ=UTC git log -1 --format='%cd' --date=format:%Y%m%d%H%M%S)
SHORT_SHA=$(git rev-parse --short=12 HEAD)
VERSION="v0.0.0-${TIMESTAMP}-${SHORT_SHA}"
echo "Calculated version: ${VERSION}"
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT

- name: Generate GitHub App Token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CRE_GH_AUTOMATION_APP_ID }}
private-key: ${{ secrets.CRE_GH_AUTOMATION_APP_PRIVATE_KEY }}
owner: smartcontractkit
repositories: cre-sdk-go

- name: Dispatch to cre-sdk-go
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
with:
token: ${{ steps.generate-token.outputs.token }}
repository: smartcontractkit/cre-sdk-go
event-type: bump-chainlink-protos
client-payload: |
{
"version": "${{ steps.version.outputs.version }}",
"skip_tagging": ${{ inputs.skip_tagging == true }}
}

- name: Summary
run: |
DISPATCH_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC")

echo "## 🚀 Dispatch Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Target" >> $GITHUB_STEP_SUMMARY
echo "- **Repository:** [\`smartcontractkit/cre-sdk-go\`](https://github.com/smartcontractkit/cre-sdk-go)" >> $GITHUB_STEP_SUMMARY
echo "- **Workflow:** \`bump-chainlink-protos.yml\`" >> $GITHUB_STEP_SUMMARY
echo "- **Event Type:** \`bump-chainlink-protos\`" >> $GITHUB_STEP_SUMMARY
echo "- **Dispatched at:** ${DISPATCH_TIME}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Payload" >> $GITHUB_STEP_SUMMARY
echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| version | \`${{ steps.version.outputs.version }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| skip_tagging | \`${{ inputs.skip_tagging == true }}\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### View Triggered Workflow" >> $GITHUB_STEP_SUMMARY
echo "👉 **[View bump-chainlink-protos runs](https://github.com/smartcontractkit/cre-sdk-go/actions/workflows/bump-chainlink-protos.yml?query=event%3Arepository_dispatch)**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "_Look for the run started at approximately ${DISPATCH_TIME}_" >> $GITHUB_STEP_SUMMARY


4 changes: 3 additions & 1 deletion cre/go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ go 1.24.5
require (
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/shopspring/decimal v1.4.0
github.com/stretchr/testify v1.10.0
github.com/smartcontractkit/chain-selectors v1.0.89
github.com/stretchr/testify v1.11.1
google.golang.org/protobuf v1.36.7
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 6 additions & 2 deletions cre/go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/smartcontractkit/chain-selectors v1.0.89 h1:L9oWZGqQXWyTPnC6ODXgu3b0DFyLmJ9eHv+uJrE9IZY=
github.com/smartcontractkit/chain-selectors v1.0.89/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
Expand Down
106 changes: 106 additions & 0 deletions cre/go/tools/add-evm-chain/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Package main adds a new EVM chain selector to client.proto.
package main

import (
"flag"
"fmt"
"os"
"regexp"
"sort"
"strconv"
"strings"

chain_selectors "github.com/smartcontractkit/chain-selectors"
)

const protoPath = "../capabilities/blockchain/evm/v1alpha/client.proto"

func main() {
selector := flag.Uint64("selector", 0, "chain selector value (required)")
flag.Parse()

if *selector == 0 {
fatal("selector is required")
}

// Look up chain ID from selector
chainId, err := chain_selectors.ChainIdFromSelector(*selector)
if err != nil {
fatal("selector %d not found: %v", *selector, err)
}

// Get chain name from chain ID
chainName, err := chain_selectors.NameFromChainId(chainId)
if err != nil {
fatal("failed to get chain name for chain ID %d: %v", chainId, err)
}

// Read proto file
content, err := os.ReadFile(protoPath)
if err != nil {
fatal("failed to read %s: %v", protoPath, err)
}

// Check if already exists
if strings.Contains(string(content), fmt.Sprintf(`key: "%s"`, chainName)) {
fmt.Printf("chain %s already exists\n", chainName)
return
}

// Parse, add, sort, rebuild
newContent, err := addChain(string(content), chainName, *selector)
if err != nil {
fatal("failed to add chain: %v", err)
}

if err := os.WriteFile(protoPath, []byte(newContent), 0644); err != nil {
fatal("failed to write file: %v", err)
}

fmt.Printf("added %s (selector: %d)\n", chainName, *selector)
}

func addChain(content, name string, selector uint64) (string, error) {
// Find defaults array
re := regexp.MustCompile(`defaults:\s*\[([\s\S]*?)\n\s*\]`)
match := re.FindStringSubmatch(content)
if len(match) < 2 {
return "", fmt.Errorf("defaults array not found")
}

// Parse entries
entryRe := regexp.MustCompile(`\{\s*key:\s*"([^"]+)"\s*value:\s*(\d+)\s*\}`)
entries := entryRe.FindAllStringSubmatch(match[1], -1)

type entry struct {
key string
val uint64
}
var list []entry
for _, e := range entries {
v, _ := strconv.ParseUint(e[2], 10, 64)
list = append(list, entry{e[1], v})
}
list = append(list, entry{name, selector})

sort.Slice(list, func(i, j int) bool { return list[i].key < list[j].key })

var b strings.Builder
b.WriteString("defaults: [\n")
for i, e := range list {
b.WriteString(fmt.Sprintf(" {\n key: \"%s\"\n value: %d\n }", e.key, e.val))
if i < len(list)-1 {
b.WriteString(",\n")
} else {
b.WriteString("\n")
}
}
b.WriteString(" ]")

return re.ReplaceAllString(content, b.String()), nil
}

func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, "error: "+format+"\n", args...)
os.Exit(1)
}
Loading