Skip to content

Add evm chain capability draft #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
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: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.63.0
github.com/shopspring/decimal v1.4.0
github.com/smartcontractkit/chainlink-common v0.7.1-0.20250509155341-2b5a5170a351
github.com/smartcontractkit/chainlink-common v0.7.1-0.20250512133850-3a916a35f999

Check failure on line 23 in go.mod

View workflow job for this annotation

GitHub Actions / Validate go.mod dependencies

[./go.mod] dependency github.com/smartcontractkit/[email protected] not on default branch (main). Version(commit): 3a916a35f999 Tree: https://github.com/smartcontractkit/chainlink-common/tree/3a916a35f999 Commit: https://github.com/smartcontractkit/chainlink-common/commit/3a916a35f999
github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250408161305-721208f43882
github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20250509160340-4f4a9265e657
github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20250502210357-2df484128afa
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,9 @@ github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJV
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartcontractkit/chainlink-common v0.7.1-0.20250509155341-2b5a5170a351 h1:luR7oS01qzkw8anxnSYaXurzKzMdgl5ROKFm1/I6llY=
github.com/smartcontractkit/chainlink-common v0.7.1-0.20250509155341-2b5a5170a351/go.mod h1:uNF6+noody47ZdmRwymDZAnQ7eKTXLzMKvl41LA63lo=
github.com/smartcontractkit/chainlink-common v0.7.1-0.20250512133850-3a916a35f999 h1:3CPuQP8HmJ9cyZeVPBJIQfLBGgZiBZKIv4FaXZ5rIds=
github.com/smartcontractkit/chainlink-common v0.7.1-0.20250512133850-3a916a35f999/go.mod h1:uNF6+noody47ZdmRwymDZAnQ7eKTXLzMKvl41LA63lo=
github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250408161305-721208f43882 h1:teDwTZ0GXlxQ65lgVbB44ffbIHlEh4N8wW7zav4lt9c=
github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250408161305-721208f43882/go.mod h1:NVoJQoPYr6BorpaXTusoIH1IYTySCmanQ8Q1yv3mNh4=
github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20250509160340-4f4a9265e657 h1:CK4v+IXsvYfzwHbZakJt/RpiWNkkiVIJ6ynGGOo7buc=
Expand Down
4 changes: 4 additions & 0 deletions pkg/service/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package service

type Config struct {
}
291 changes: 291 additions & 0 deletions pkg/service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package service

import (
"context"
"fmt"
"math/big"

"google.golang.org/protobuf/types/known/emptypb"

"github.com/smartcontractkit/chainlink-common/pkg/capabilities"
evmcap "github.com/smartcontractkit/chainlink-common/pkg/loop/chain-capabilities/evm"
evmcapserver "github.com/smartcontractkit/chainlink-common/pkg/loop/chain-capabilities/evm/server"
"github.com/smartcontractkit/chainlink-common/pkg/types"
evmservicetypes "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm"
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
"github.com/smartcontractkit/chainlink-common/pkg/values/pb"
)

const (
CapabilityName = "evm"
)

type evmCapability struct {
capabilities.CapabilityInfo
types.EVMService
// TODO ?
//evmcapserver.UnimplementedEVMCapabilityServer
// TODO
//config Config
}

func (e evmCapability) CallContract(ctx context.Context, _ capabilities.RequestMetadata, input *evmcap.CallContractRequest) (*evmcap.CallContractReply, error) {
callMsg, err := parseCallMsg(input.Call)
if err != nil {
return &evmcap.CallContractReply{}, err
}

blockNumber := &big.Int{}
blockNumber, isOK := blockNumber.SetString(input.BlockNumber.String(), 10)
if !isOK {
return &evmcap.CallContractReply{}, fmt.Errorf("failed to parse block number: %s", input.BlockNumber.String())
}

response, err := e.EVMService.CallContract(ctx, &callMsg, blockNumber)
if err != nil {
return &evmcap.CallContractReply{}, err
}

return &evmcap.CallContractReply{Data: &evmcap.ABIPayload{Abi: response}}, nil
}

func (e evmCapability) FilterLogs(ctx context.Context, _ capabilities.RequestMetadata, input *evmcap.FilterLogsRequest) (*evmcap.FilterLogsReply, error) {
evmCapFq := input.GetFilterQuery()

fromBlock := &big.Int{}
fromBlock, isOK := fromBlock.SetString(evmCapFq.FromBlock.String(), 10)
if !isOK {
return nil, fmt.Errorf("failed to parse from block number: %s", evmCapFq.FromBlock.String())
}

toBlock := &big.Int{}
toBlock, isOK = toBlock.SetString(evmCapFq.ToBlock.String(), 10)
if !isOK {
return nil, fmt.Errorf("failed to parse to block number: %s", evmCapFq.ToBlock.String())
}

fq := evmservicetypes.FilterQuery{
BlockHash: evmservicetypes.Hash(evmCapFq.BlockHash.Hash),
FromBlock: fromBlock,
ToBlock: toBlock,
}

for _, address := range evmCapFq.Addresses {
fq.Addresses = append(fq.Addresses, evmservicetypes.Address(address.Address))
}

// TODO fix topic type
//for _, topic := range evmCapFq.Topics {
// fq.Addresses = append(fq.Addresses, evmservicetypes.Address(topic.Topic))
//}

logs, err := e.EVMService.FilterLogs(ctx, fq)
if err != nil {
return nil, err
}

return &evmcap.FilterLogsReply{Logs: parseLogs(logs)}, nil
}

func (e evmCapability) BalanceAt(ctx context.Context, _ capabilities.RequestMetadata, input *evmcap.BalanceAtRequest) (*evmcap.BalanceAtReply, error) {

blockNumber := &big.Int{}
blockNumber, isOK := blockNumber.SetString(input.BlockNumber.String(), 10)
if !isOK {
return nil, fmt.Errorf("failed to parse to block number: %s", input.BlockNumber.String())
}

balance, err := e.EVMService.BalanceAt(ctx, evmservicetypes.Address(input.Account.Address), blockNumber)
if err != nil {
return nil, err
}

return &evmcap.BalanceAtReply{Balance: &pb.BigInt{AbsVal: balance.Bytes(), Sign: int64(balance.Sign())}}, nil
}

func (e evmCapability) EstimateGas(ctx context.Context, _ capabilities.RequestMetadata, input *evmcap.EstimateGasRequest) (*evmcap.EstimateGasReply, error) {
callMsg, err := parseCallMsg(input.Msg)
if err != nil {
return &evmcap.EstimateGasReply{}, err
}

estimate, err := e.EVMService.EstimateGas(ctx, &callMsg)
if err != nil {
return &evmcap.EstimateGasReply{}, err
}

return &evmcap.EstimateGasReply{Gas: estimate}, nil
}

func (e evmCapability) GetTransactionByHash(ctx context.Context, _ capabilities.RequestMetadata, input *evmcap.GetTransactionByHashRequest) (*evmcap.GetTransactionByHashReply, error) {
tx, err := e.EVMService.TransactionByHash(ctx, evmservicetypes.Hash(input.Hash.Hash))
if err != nil {
return nil, err
}

return &evmcap.GetTransactionByHashReply{Transaction: &evmcap.Transaction{
Nonce: tx.Nonce,
Gas: tx.Gas,
To: &evmcap.Address{Address: tx.To[:]},
Data: &evmcap.ABIPayload{Abi: tx.Data},
Value: &pb.BigInt{AbsVal: tx.Value.Bytes(), Sign: int64(tx.Value.Sign())},
GasPrice: &pb.BigInt{AbsVal: tx.GasPrice.Bytes(), Sign: int64(tx.GasPrice.Sign())},
Hash: &evmcap.Hash{Hash: tx.Hash[:]},
}}, nil
}

func (e evmCapability) GetTransactionReceipt(ctx context.Context, _ capabilities.RequestMetadata, input *evmcap.GetReceiptRequest) (*evmcap.GetReceiptReply, error) {
receipt, err := e.EVMService.TransactionReceipt(ctx, evmservicetypes.Hash(input.Hash.Hash))
if err != nil {
return nil, err
}

reply := &evmcap.GetReceiptReply{Receipt: &evmcap.Receipt{
Status: receipt.Status,
Logs: parseLogs(receipt.Logs),
TxHash: &evmcap.Hash{Hash: receipt.TxHash[:]},
ContractAddress: &evmcap.Address{Address: receipt.ContractAddress[:]},
GasUsed: receipt.GasUsed,
BlockHash: &evmcap.Hash{Hash: receipt.BlockHash[:]},
BlockNumber: &pb.BigInt{AbsVal: receipt.BlockNumber.Bytes(), Sign: int64(receipt.BlockNumber.Sign())},
// TODO add tx index
//TransactionIndex: tx.TransactionIndex,
EffectiveGasPrice: &pb.BigInt{AbsVal: receipt.EffectiveGasPrice.Bytes(), Sign: int64(receipt.EffectiveGasPrice.Sign())},
}}

return reply, nil
}

func (e evmCapability) LatestAndFinalizedHead(ctx context.Context, _ capabilities.RequestMetadata, _ *emptypb.Empty) (*evmcap.LatestAndFinalizedHeadReply, error) {
latest, finalized, err := e.EVMService.LatestAndFinalizedHead(ctx)
if err != nil {
return nil, err
}

return &evmcap.LatestAndFinalizedHeadReply{
Latest: &evmcap.Head{
Timestamp: latest.Timestamp,
BlockNumber: &pb.BigInt{AbsVal: latest.Number.Bytes(), Sign: int64(latest.Number.Sign())},
Hash: &evmcap.Hash{Hash: latest.Hash[:]},
ParentHash: &evmcap.Hash{Hash: latest.ParentHash[:]},
},
Finalized: &evmcap.Head{
Timestamp: finalized.Timestamp,
BlockNumber: &pb.BigInt{AbsVal: finalized.Number.Bytes(), Sign: int64(finalized.Number.Sign())},
Hash: &evmcap.Hash{Hash: finalized.Hash[:]},
ParentHash: &evmcap.Hash{Hash: finalized.ParentHash[:]},
},
}, nil
}

func (e evmCapability) QueryTrackedLogs(_ context.Context, _ capabilities.RequestMetadata, _ *evmcap.QueryTrackedLogsRequest) (*evmcap.QueryTrackedLogsReply, error) {
//TODO implement me
panic("implement me")
}

func (e evmCapability) RegisterLogTracking(_ context.Context, _ capabilities.RequestMetadata, _ *evmcap.RegisterLogTrackingRequest) (*emptypb.Empty, error) {
//TODO implement me
panic("implement me")
}

func (e evmCapability) UnregisterLogTracking(_ context.Context, _ capabilities.RequestMetadata, _ *evmcap.UnregisterLogTrackingRequest) (*emptypb.Empty, error) {
//TODO implement me
panic("implement me")
}

func (e evmCapability) RegisterTrigger(_ context.Context, _ capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) {
//TODO implement me
panic("implement me")
}

func (e evmCapability) UnregisterTrigger(_ context.Context, _ capabilities.TriggerRegistrationRequest) error {
//TODO implement me
panic("implement me")
}

func (e evmCapability) RegisterToWorkflow(_ context.Context, _ capabilities.RegisterToWorkflowRequest) error {
//TODO implement me
panic("implement me")
}

func (e evmCapability) UnregisterFromWorkflow(_ context.Context, _ capabilities.UnregisterFromWorkflowRequest) error {
//TODO implement me
panic("implement me")
}

func (e evmCapability) Initialise(_ context.Context, _ string, _ core.TelemetryService, _ core.KeyValueStore, _ core.ErrorLog, _ core.PipelineRunnerService, _ core.RelayerSet, _ core.OracleFactory) error {
return nil
}

func (e evmCapability) Start(_ context.Context) error {
//TODO Relayer already started the EVM Relayer, this is not needed atm afaik
return nil
}

func (e evmCapability) Close() error {
//TODO Relayer closes the EVM Relayer, this is not needed atm afaik
return nil
}

func (e evmCapability) HealthReport() map[string]error {
// TODO
return nil
}

func (e evmCapability) Name() string {
return CapabilityName
}

func (e evmCapability) Description() string {
return "Contains EVM chain functionalities"
}

func (e evmCapability) Ready() error {
// TODO
return nil
}

type EVMCapabilityOpts struct {
ID string
types.EVMService
// TODO a lot of things like config are handled by the Relayer indirectly, needs another passover for these detailks
}

func NewEVMCapablity(opts EVMCapabilityOpts) evmcapserver.EVMChainCapability {
capInfo := capabilities.MustNewCapabilityInfo(opts.ID, capabilities.CapabilityTypeTarget, CapabilityName)

return evmCapability{CapabilityInfo: capInfo, EVMService: opts.EVMService}
}

var _ evmcapserver.EVMChainCapability = &evmCapability{}

func parseCallMsg(callMsg *evmcap.CallMsg) (evmservicetypes.CallMsg, error) {
parsed := evmservicetypes.CallMsg{
To: evmservicetypes.Address(callMsg.To.Address),
From: evmservicetypes.Address(callMsg.From.Address),
Data: callMsg.Data.Abi,
}

return parsed, nil
}

func parseLogs(logs []*evmservicetypes.Log) []*evmcap.Log {
parsed := make([]*evmcap.Log, 0)
for _, log := range logs {
parsed = append(parsed, &evmcap.Log{
Address: &evmcap.Address{Address: log.Address[:]},
Topics: make([]*evmcap.Hash, 0),
TxHash: &evmcap.Hash{Hash: log.TxHash[:]},
BlockHash: &evmcap.Hash{Hash: log.BlockHash[:]},
Data: &evmcap.ABIPayload{Abi: log.Data},
EventSig: &evmcap.Hash{Hash: log.EventSig[:]},
BlockNumber: &pb.BigInt{AbsVal: log.BlockNumber.Bytes(), Sign: int64(log.BlockNumber.Sign())},
// TODO missing tx index from evm log
//TxIndex: log.TxIndex,
Index: log.LogIndex,
Removed: log.Removed,
})
}

return parsed
}
Loading