Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
39de546
starting on solana binding generator
yashnevatia Oct 14, 2025
27d0599
added write methods
yashnevatia Oct 15, 2025
4de931c
adding changes
yashnevatia Oct 22, 2025
a0dd2e6
adding anchor fork
yashnevatia Oct 24, 2025
f0a9fd1
Adding anchor required
yashnevatia Oct 24, 2025
129b125
changes
yashnevatia Oct 27, 2025
d81e3aa
adding jen
yashnevatia Oct 27, 2025
e2a36eb
moving anchor go into repo and adding gen command
yashnevatia Oct 27, 2025
a25841c
only keep data storage contract
yashnevatia Nov 4, 2025
63c04e3
fake protos and add tests
yashnevatia Nov 5, 2025
e7eae87
Adding support for generate-bindings-solana
yashnevatia Nov 5, 2025
b1ed440
Cleaning up code and moving to a cre file
yashnevatia Nov 5, 2025
8433a4b
remove some files
yashnevatia Nov 5, 2025
7bb2b70
docs
yashnevatia Nov 5, 2025
885eaa6
merging with main
yashnevatia Nov 25, 2025
032670f
using real sdk
yashnevatia Nov 26, 2025
8762f30
removing most mocks on cre-cli side
yashnevatia Nov 26, 2025
52b542d
temp adding flakes
yashnevatia Feb 10, 2026
0a1e5af
Merge branch 'main' of ssh://github.com/smartcontractkit/cre-cli into…
yashnevatia Feb 10, 2026
c3d1481
rename evm and solana
yashnevatia Feb 10, 2026
f4d6f46
polish
yashnevatia Feb 10, 2026
48026d2
clean
yashnevatia Feb 10, 2026
9baa17c
clean
yashnevatia Feb 10, 2026
81c78f7
comments
yashnevatia Feb 10, 2026
4bc5212
removing flakes
yashnevatia Feb 10, 2026
fa2fa52
doc
yashnevatia Feb 10, 2026
380a340
tidy
yashnevatia Feb 10, 2026
5d47f61
lint
yashnevatia Feb 10, 2026
ff17844
lint
yashnevatia Feb 10, 2026
dee9c11
lint
yashnevatia Feb 10, 2026
8588df8
lint
yashnevatia Feb 10, 2026
031c754
lint
yashnevatia Feb 10, 2026
7975669
clean up
yashnevatia Feb 10, 2026
51026c7
lint
yashnevatia Feb 10, 2026
fe9ad9f
tidy
yashnevatia Feb 10, 2026
28eb4ab
lint
yashnevatia Feb 11, 2026
6b2439a
tidy
yashnevatia Feb 11, 2026
aefab46
generate-bindings <chain_family>
yashnevatia Feb 11, 2026
8d1566a
docs
yashnevatia Feb 11, 2026
a517372
revert
yashnevatia Feb 11, 2026
3fbd5eb
adding back handler
yashnevatia Feb 11, 2026
eaf4b4f
lint
yashnevatia Feb 11, 2026
672edb3
changing signing/hashing algo
yashnevatia Feb 16, 2026
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
9 changes: 5 additions & 4 deletions cmd/creinit/go_module_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
)

const (
SdkVersion = "v1.2.0"
EVMCapabilitiesVersion = "v1.0.0-beta.5"
HTTPCapabilitiesVersion = "v1.0.0-beta.0"
CronCapabilitiesVersion = "v1.0.0-beta.0"
SdkVersion = "v1.2.0"
EVMCapabilitiesVersion = "v1.0.0-beta.5"
SolanaCapabilitiesVersion = "v0.1.1-0.20260210120110-1f2d5201a23f"
HTTPCapabilitiesVersion = "v1.0.0-beta.0"
CronCapabilitiesVersion = "v1.0.0-beta.0"
)

func initializeGoModule(logger *zerolog.Logger, workingDirectory, moduleName string) error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ that lets you generate Go bindings for your smart contracts using a custom templ
### Programmatic API

```go
import "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings"
import "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm"

func main() {
err := bindings.GenerateBindings(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bindings
package evm

import (
_ "embed"
Expand All @@ -11,7 +11,7 @@ import (
"github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/crypto"

"github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings/abigen"
"github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm/abigen"
)

//go:embed sourcecre.go.tpl
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bindings_test
package evm_test

import (
"context"
Expand All @@ -20,7 +20,7 @@ import (
"github.com/smartcontractkit/cre-sdk-go/cre/testutils"
consensusmock "github.com/smartcontractkit/cre-sdk-go/internal_testing/capabilities/consensus/mock"

datastorage "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings/testdata"
datastorage "github.com/smartcontractkit/cre-cli/cmd/generate-bindings/evm/testdata"
)

const anyChainSelector = uint64(1337)
Expand Down
329 changes: 329 additions & 0 deletions cmd/generate-bindings/evm/evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
package evm

import (
"fmt"
"os"
"os/exec"
"path/filepath"

"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/smartcontractkit/cre-cli/cmd/creinit"
"github.com/smartcontractkit/cre-cli/internal/runtime"
"github.com/smartcontractkit/cre-cli/internal/validation"
)

type Inputs struct {
ProjectRoot string `validate:"required,dir" cli:"--project-root"`
Language string `validate:"required,oneof=go" cli:"--language"`
// just keeping it simple for now
AbiPath string `validate:"required,path_read" cli:"--abi"`
PkgName string `validate:"required" cli:"--pkg"`
OutPath string `validate:"required" cli:"--out"`
}

func New(runtimeContext *runtime.Context) *cobra.Command {
generateBindingsCmd := &cobra.Command{
Use: "evm",
Short: "Generate bindings from contract ABI",
Long: `This command generates bindings from contract ABI files.
Supports EVM chain family and Go language.
Each contract gets its own package subdirectory to avoid naming conflicts.
For example, IERC20.abi generates bindings in generated/ierc20/ package.`,
Example: " cre generate-bindings-evm",
RunE: func(cmd *cobra.Command, args []string) error {
handler := newHandler(runtimeContext)

inputs, err := handler.ResolveInputs(runtimeContext.Viper)
if err != nil {
return err
}
err = handler.ValidateInputs(inputs)
if err != nil {
return err
}
return handler.Execute(inputs)
},
}

generateBindingsCmd.Flags().StringP("project-root", "p", "", "Path to project root directory (defaults to current directory)")
generateBindingsCmd.Flags().StringP("language", "l", "go", "Target language (go)")
generateBindingsCmd.Flags().StringP("abi", "a", "", "Path to ABI directory (defaults to contracts/evm/src/abi/)")
generateBindingsCmd.Flags().StringP("pkg", "k", "bindings", "Base package name (each contract gets its own subdirectory)")

return generateBindingsCmd
}

type handler struct {
log *zerolog.Logger
validated bool
}

func newHandler(ctx *runtime.Context) *handler {
return &handler{
log: ctx.Logger,
validated: false,
}
}

func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) {
// Get current working directory as default project root
currentDir, err := os.Getwd()
if err != nil {
return Inputs{}, fmt.Errorf("failed to get current working directory: %w", err)
}

// Resolve project root with fallback to current directory
projectRoot := v.GetString("project-root")
if projectRoot == "" {
projectRoot = currentDir
}

contractsPath := filepath.Join(projectRoot, "contracts")
if _, err := os.Stat(contractsPath); err != nil {
return Inputs{}, fmt.Errorf("contracts folder not found in project root: %s", contractsPath)
}

// Language defaults are handled by StringP
language := v.GetString("language")

// Resolve ABI path with fallback to contracts/{chainFamily}/src/abi/
abiPath := v.GetString("abi")
if abiPath == "" {
abiPath = filepath.Join(projectRoot, "contracts", "evm", "src", "abi")
}

// Package name defaults are handled by StringP
pkgName := v.GetString("pkg")

// Output path is contracts/{chainFamily}/src/generated/ under projectRoot
outPath := filepath.Join(projectRoot, "contracts", "evm", "src", "generated")

return Inputs{
ProjectRoot: projectRoot,
Language: language,
AbiPath: abiPath,
PkgName: pkgName,
OutPath: outPath,
}, nil
}

func (h *handler) ValidateInputs(inputs Inputs) error {
validate, err := validation.NewValidator()
if err != nil {
return fmt.Errorf("failed to initialize validator: %w", err)
}

if err = validate.Struct(inputs); err != nil {
return validate.ParseValidationErrors(err)
}

// Additional validation for ABI path
if _, err := os.Stat(inputs.AbiPath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("ABI path does not exist: %s", inputs.AbiPath)
}
return fmt.Errorf("failed to access ABI path: %w", err)
}

// Validate that if AbiPath is a directory, it contains .abi files
if info, err := os.Stat(inputs.AbiPath); err == nil && info.IsDir() {
files, err := filepath.Glob(filepath.Join(inputs.AbiPath, "*.abi"))
if err != nil {
return fmt.Errorf("failed to check for ABI files in directory: %w", err)
}
if len(files) == 0 {
return fmt.Errorf("no .abi files found in directory: %s", inputs.AbiPath)
}
}

return nil
}

// contractNameToPackage converts contract names to valid Go package names
// Examples: IERC20 -> ierc20, ReserveManager -> reserve_manager, IReserveManager -> ireserve_manager
func contractNameToPackage(contractName string) string {
if contractName == "" {
return ""
}

var result []rune
runes := []rune(contractName)

for i, r := range runes {
// Convert to lowercase
if r >= 'A' && r <= 'Z' {
lower := r - 'A' + 'a'

// Add underscore before uppercase letters, but not:
// - At the beginning (i == 0)
// - If the previous character was also uppercase and this is followed by lowercase (e.g., "ERC" in "ERC20")
// - If this is part of a sequence of uppercase letters at the beginning (e.g., "IERC20" -> "ierc20")
if i > 0 {
prevIsUpper := runes[i-1] >= 'A' && runes[i-1] <= 'Z'
nextIsLower := i+1 < len(runes) && runes[i+1] >= 'a' && runes[i+1] <= 'z'

// Add underscore if:
// - Previous char was lowercase (CamelCase boundary)
// - Previous char was uppercase but this char is followed by lowercase (end of acronym)
if !prevIsUpper || (prevIsUpper && nextIsLower && i > 1) {
result = append(result, '_')
}
}

result = append(result, lower)
} else {
result = append(result, r)
}
}

return string(result)
}

func (h *handler) processAbiDirectory(inputs Inputs) error {
// Read all .abi files in the directory
files, err := filepath.Glob(filepath.Join(inputs.AbiPath, "*.abi"))
if err != nil {
return fmt.Errorf("failed to find ABI files: %w", err)
}

if len(files) == 0 {
return fmt.Errorf("no .abi files found in directory: %s", inputs.AbiPath)
}

packageNames := make(map[string]bool)
for _, abiFile := range files {
contractName := filepath.Base(abiFile)
contractName = contractName[:len(contractName)-4]
packageName := contractNameToPackage(contractName)
if _, exists := packageNames[packageName]; exists {
return fmt.Errorf("package name collision: multiple contracts would generate the same package name '%s' (contracts are converted to snake_case for package names). Please rename one of your contract files to avoid this conflict", packageName)
}
packageNames[packageName] = true
}

// Process each ABI file
for _, abiFile := range files {
// Extract contract name from filename (remove .abi extension)
contractName := filepath.Base(abiFile)
contractName = contractName[:len(contractName)-4] // Remove .abi extension

// Convert contract name to package name
packageName := contractNameToPackage(contractName)

// Create per-contract output directory
contractOutDir := filepath.Join(inputs.OutPath, packageName)
if err := os.MkdirAll(contractOutDir, 0o755); err != nil {
return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err)
}

// Create output file path in contract-specific directory
outputFile := filepath.Join(contractOutDir, contractName+".go")

fmt.Printf("Processing ABI file: %s, contract: %s, package: %s, output: %s\n", abiFile, contractName, packageName, outputFile)

err = GenerateBindings(
"", // combinedJSONPath - empty for now
abiFile,
packageName, // Use contract-specific package name
contractName, // Use contract name as type name
outputFile,
)
if err != nil {
return fmt.Errorf("failed to generate bindings for %s: %w", contractName, err)
}
}

return nil
}

func (h *handler) processSingleAbi(inputs Inputs) error {
// Extract contract name from ABI file path
contractName := filepath.Base(inputs.AbiPath)
if filepath.Ext(contractName) == ".abi" {
contractName = contractName[:len(contractName)-4] // Remove .abi extension
}

// Convert contract name to package name
packageName := contractNameToPackage(contractName)

// Create per-contract output directory
contractOutDir := filepath.Join(inputs.OutPath, packageName)
if err := os.MkdirAll(contractOutDir, 0o755); err != nil {
return fmt.Errorf("failed to create contract output directory %s: %w", contractOutDir, err)
}

// Create output file path in contract-specific directory
outputFile := filepath.Join(contractOutDir, contractName+".go")

fmt.Printf("Processing single ABI file: %s, contract: %s, package: %s, output: %s\n", inputs.AbiPath, contractName, packageName, outputFile)

return GenerateBindings(
"", // combinedJSONPath - empty for now
inputs.AbiPath,
packageName, // Use contract-specific package name
contractName, // Use contract name as type name
outputFile,
)
}

func (h *handler) Execute(inputs Inputs) error {
// Validate language
switch inputs.Language {
case "go":
// Language supported, continue
default:
return fmt.Errorf("unsupported language: %s", inputs.Language)
}

// Validate chain family and handle accordingly
// Create output directory if it doesn't exist
if err := os.MkdirAll(inputs.OutPath, 0o755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}

// Check if ABI path is a directory or file
info, err := os.Stat(inputs.AbiPath)
if err != nil {
return fmt.Errorf("failed to access ABI path: %w", err)
}

if info.IsDir() {
if err := h.processAbiDirectory(inputs); err != nil {
return err
}
} else {
if err := h.processSingleAbi(inputs); err != nil {
return err
}
}

err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go@"+creinit.SdkVersion)
if err != nil {
return err
}
err = runCommand(inputs.ProjectRoot, "go", "get", "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm@"+creinit.EVMCapabilitiesVersion)
if err != nil {
return err
}
err = runCommand(inputs.ProjectRoot, "go", "mod", "tidy")
if err != nil {
return err
}
return nil
}

// runCommand executes a command in a specified directory
func runCommand(dir string, command string, args ...string) error {
cmd := exec.Command(command, args...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run %s: %w", command, err)
}

return nil
}
Loading
Loading