Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c7289e2
feat(simulate): Aptos chain family for `cre workflow simulate`
Fletch153 Apr 21, 2026
5aa7b15
test(simulate/aptos): extend scenarios from 30 to 100
Fletch153 Apr 21, 2026
642e8f0
build(deps): pin chainlink-aptos to PLEX-2751 PR head
Fletch153 Apr 21, 2026
9a529a2
test(cli): extend aptos CLI scenarios from 30 to 100
Fletch153 Apr 21, 2026
23502ee
chore(simulate/aptos): address audit feedback
Fletch153 Apr 22, 2026
26a299a
test(aptos): use /tmp for config path to satisfy 97-char limit
Fletch153 Apr 24, 2026
602264e
Merge remote-tracking branch 'origin/main' into plex-2751
Fletch153 Apr 24, 2026
1711ad3
feat(simulate/aptos): support experimental-chains + align view test e…
Fletch153 Apr 24, 2026
8b81488
refactor(settings): consolidate chain signing keys behind ChainType (…
Fletch153 Apr 24, 2026
4c6b976
refactor(settings): dispatch chain name lookup by family prefix
Fletch153 Apr 24, 2026
b444df2
chore(test): untrack local-only Aptos smoke scaffolding
Fletch153 Apr 24, 2026
7ebd892
refactor(simulate): centralize chain limits, untrack local Aptos scen…
Fletch153 Apr 24, 2026
adb0439
refactor(simulate): per-family ChainWrite limits and engine cfg parity
Fletch153 Apr 27, 2026
82ffb98
test(simulate): add per-family gas-limit getters and assertions
Fletch153 Apr 27, 2026
58a873a
test(simulate): drop redundant chain ExtractLimits override tests
Fletch153 Apr 27, 2026
d331c5a
test(simulate/aptos): align supported_chains tests with EVM parity
Fletch153 Apr 27, 2026
c28df35
test(simulate): cover zero-limit and nil sub-message passthrough
Fletch153 Apr 27, 2026
2d8f3ae
refactor(simulate/aptos): drop dead-defensive nil input guard
Fletch153 Apr 27, 2026
3c852d8
test(simulate/aptos): cover mixed known + experimental health check
Fletch153 Apr 27, 2026
b4d2025
test(simulate/aptos): align chaintype coverage with EVM
Fletch153 Apr 27, 2026
b539d72
chore(simulate/aptos): log when experimental forwarder matches supported
Fletch153 Apr 27, 2026
4354b95
refactor(simulate/aptos): delegate ParseTriggerChainSelector to share…
Fletch153 Apr 27, 2026
f02f4d0
chore(simulate/evm): wrap registry.Add error with selector context
Fletch153 Apr 27, 2026
260f19d
fix(simulate/aptos): improve Aptos config guidance
Fletch153 Apr 27, 2026
e5d7e5e
Reset gitignore
Fletch153 Apr 27, 2026
c689b1b
chore: tidy go.sum (drop stale chainlink-aptos entry)
Fletch153 Apr 27, 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
6 changes: 3 additions & 3 deletions cmd/secrets/common/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ type Handler struct {
func NewHandler(ctx *runtime.Context, secretsFilePath string) (*Handler, error) {
var pk *ecdsa.PrivateKey
var err error
if ctx.Settings.User.EthPrivateKey != "" {
pk, err = crypto.HexToECDSA(ctx.Settings.User.EthPrivateKey)
if ethKey := ctx.Settings.User.PrivateKey(settings.EVM); ethKey != "" {
pk, err = crypto.HexToECDSA(ethKey)
if err != nil {
return nil, fmt.Errorf("failed to decode the provided private key: %w", err)
}
} else {
ctx.Logger.Debug().Msg("No EthPrivateKey found in settings; assuming a multisig request.")
ctx.Logger.Debug().Msg("No EVM private key found in settings; assuming a multisig request.")

}

Expand Down
6 changes: 3 additions & 3 deletions cmd/workflow/activate/activate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand All @@ -47,7 +47,7 @@ func TestNonInteractive_WithYes_PassesGuard(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand Down Expand Up @@ -162,7 +162,7 @@ func TestWorkflowActivateCommand(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand Down
6 changes: 3 additions & 3 deletions cmd/workflow/delete/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand All @@ -47,7 +47,7 @@ func TestNonInteractive_WithYes_Proceeds(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand Down Expand Up @@ -134,7 +134,7 @@ func TestWorkflowDeleteCommand(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand Down
6 changes: 3 additions & 3 deletions cmd/workflow/pause/pause_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand All @@ -46,7 +46,7 @@ func TestNonInteractive_WithYes_PassesGuard(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand Down Expand Up @@ -140,7 +140,7 @@ func TestWorkflowPauseCommand(t *testing.T) {
ctx := simulatedEnvironment.NewRuntimeContext()
ctx.Settings = &settings.Settings{
User: settings.UserSettings{
EthPrivateKey: chainsim.TestPrivateKey,
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
},
}
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
Expand Down
79 changes: 79 additions & 0 deletions cmd/workflow/simulate/chain/aptos/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package aptos

import (
"context"
"fmt"

"github.com/aptos-labs/aptos-go-sdk"
"github.com/aptos-labs/aptos-go-sdk/crypto"

Check failure on line 9 in cmd/workflow/simulate/chain/aptos/capabilities.go

View workflow job for this annotation

GitHub Actions / ci-lint

File is not properly formatted (gci)
aptosserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/aptos/server"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink/v2/core/capabilities"

aptosfakes "github.com/smartcontractkit/chainlink-aptos/fakes"

"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain"
)

// AptosChainCapabilities holds the per-selector FakeAptosChain instances
// created for simulation.
type AptosChainCapabilities struct {
AptosChains map[uint64]*aptosfakes.FakeAptosChain
}

// NewAptosChainCapabilities builds FakeAptosChain instances for every
// (selector -> client) pair, optionally wraps them with LimitedAptosChain,
// and registers each with the capability registry.
func NewAptosChainCapabilities(
ctx context.Context,
lggr logger.Logger,
registry *capabilities.Registry,
clients map[uint64]aptosfakes.AptosClient,
forwarders map[uint64]string,
privateKey *crypto.Ed25519PrivateKey,
dryRunChainWrite bool,
limits chain.Limits,
) (*AptosChainCapabilities, error) {
chains := make(map[uint64]*aptosfakes.FakeAptosChain)
for sel, client := range clients {
fwdStr, ok := forwarders[sel]
if !ok {
lggr.Infow("Forwarder not found for chain", "selector", sel)
continue
}
var fwd aptos.AccountAddress
if err := fwd.ParseStringRelaxed(fwdStr); err != nil {
return nil, fmt.Errorf("parse forwarder for selector %d: %w", sel, err)
}
fc, err := aptosfakes.NewFakeAptosChain(lggr, client, privateKey, fwd, sel, dryRunChainWrite)
if err != nil {
return nil, fmt.Errorf("new FakeAptosChain for selector %d: %w", sel, err)
}
capability := NewLimitedAptosChain(fc, limits)
server := aptosserver.NewClientServer(capability)
if err := registry.Add(ctx, server); err != nil {
return nil, fmt.Errorf("register aptos capability for selector %d: %w", sel, err)
}
chains[sel] = fc
}
return &AptosChainCapabilities{AptosChains: chains}, nil
}

func (c *AptosChainCapabilities) Start(ctx context.Context) error {
for _, fc := range c.AptosChains {
if err := fc.Start(ctx); err != nil {
return err
}
}
return nil
}

func (c *AptosChainCapabilities) Close() error {
for _, fc := range c.AptosChains {
if err := fc.Close(); err != nil {
return err
}
}
return nil
}
196 changes: 196 additions & 0 deletions cmd/workflow/simulate/chain/aptos/chaintype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package aptos

import (
"context"
"encoding/hex"
"fmt"
"strings"

"github.com/aptos-labs/aptos-go-sdk/crypto"
"github.com/rs/zerolog"
"github.com/spf13/viper"

corekeys "github.com/smartcontractkit/chainlink-common/keystore/corekeys"
"github.com/smartcontractkit/chainlink-common/pkg/services"

aptosfakes "github.com/smartcontractkit/chainlink-aptos/fakes"

"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain"
"github.com/smartcontractkit/cre-cli/internal/settings"
"github.com/smartcontractkit/cre-cli/internal/ui"
)

const defaultSentinelAptosSeed = "0000000000000000000000000000000000000000000000000000000000000001"

func init() {
chain.Register(string(corekeys.Aptos), func(lggr *zerolog.Logger) chain.ChainType {
return &AptosChainType{log: lggr}
}, nil)
}

// AptosChainType implements chain.ChainType for Aptos.
type AptosChainType struct {
log *zerolog.Logger
aptosChains *AptosChainCapabilities
}

var _ chain.ChainType = (*AptosChainType)(nil)

func (ct *AptosChainType) Name() string { return string(corekeys.Aptos) }
func (ct *AptosChainType) SupportedChains() []chain.ChainConfig { return SupportedChains }

func (ct *AptosChainType) ResolveClients(v *viper.Viper) (chain.ResolvedChains, error) {
clients := make(map[uint64]chain.ChainClient)
forwarders := make(map[uint64]string)
experimental := make(map[uint64]bool)
for _, c := range SupportedChains {
name, err := settings.GetChainNameByChainSelector(c.Selector)
if err != nil {
ct.log.Error().Msgf("Invalid Aptos chain selector %d; skipping", c.Selector)
continue
}
rpcURL, err := settings.GetRpcUrlSettings(v, name)
if err != nil || strings.TrimSpace(rpcURL) == "" {
ct.log.Debug().Msgf("RPC not provided for %s; skipping", name)
continue
}
ct.log.Debug().Msgf("Using RPC for %s: %s", name, chain.RedactURL(rpcURL))
client, err := aptosfakes.NewAptosClient(rpcURL)
if err != nil {
ui.Warning(fmt.Sprintf("Failed to build Aptos client for %s: %v", name, err))
continue
}
clients[c.Selector] = client
if strings.TrimSpace(c.Forwarder) != "" {
forwarders[c.Selector] = c.Forwarder
}
}

expChains, err := settings.GetExperimentalChains(v)
if err != nil {
return chain.ResolvedChains{}, fmt.Errorf("failed to load experimental chains config: %w", err)
}
for _, ec := range expChains {
if !strings.EqualFold(ec.ChainType, ct.Name()) {
continue
}
if ec.ChainSelector == 0 {
return chain.ResolvedChains{}, fmt.Errorf("experimental chain missing chain-selector")
}
if strings.TrimSpace(ec.RPCURL) == "" {
return chain.ResolvedChains{}, fmt.Errorf("experimental aptos chain %d missing rpc-url", ec.ChainSelector)
}
forwarder := strings.TrimSpace(ec.Forwarder)
if forwarder == "" {
return chain.ResolvedChains{}, fmt.Errorf("experimental aptos chain %d missing forwarder", ec.ChainSelector)
}
if _, exists := clients[ec.ChainSelector]; exists {
if forwarders[ec.ChainSelector] != forwarder {
ui.Warning(fmt.Sprintf("Warning: experimental aptos chain %d overrides supported chain forwarder (supported: %s, experimental: %s)\n",
ec.ChainSelector, forwarders[ec.ChainSelector], forwarder))
forwarders[ec.ChainSelector] = forwarder
} else {
ct.log.Debug().Uint64("chain-selector", ec.ChainSelector).Msg("Experimental chain matches supported chain config")
}
continue
}
ct.log.Debug().Msgf("Using RPC for experimental aptos chain %d: %s", ec.ChainSelector, chain.RedactURL(ec.RPCURL))
client, err := aptosfakes.NewAptosClient(ec.RPCURL)
if err != nil {
return chain.ResolvedChains{}, fmt.Errorf("failed to create aptos client for experimental chain %d: %w", ec.ChainSelector, err)
}
clients[ec.ChainSelector] = client
forwarders[ec.ChainSelector] = forwarder
experimental[ec.ChainSelector] = true
ui.Dim(fmt.Sprintf("Added experimental aptos chain (chain-selector: %d)\n", ec.ChainSelector))
}

return chain.ResolvedChains{Clients: clients, Forwarders: forwarders, ExperimentalSelectors: experimental}, nil
}

func (ct *AptosChainType) ResolveKey(s *settings.Settings, broadcast bool) (interface{}, error) {
seed := strings.TrimPrefix(strings.ToLower(strings.TrimSpace(s.User.PrivateKey(settings.Aptos))), "0x")
bytes, err := hex.DecodeString(seed)
if err != nil || len(bytes) != 32 {
if broadcast {
if err != nil {
return nil, fmt.Errorf("failed to parse private key, required to broadcast. Please check CRE_APTOS_PRIVATE_KEY in your .env file or system environment: %w", err)
}
return nil, fmt.Errorf("CRE_APTOS_PRIVATE_KEY must be 32 hex bytes (64 chars); got len=%d. Please check CRE_APTOS_PRIVATE_KEY in your .env file or system environment", len(bytes))
}
bytes, _ = hex.DecodeString(defaultSentinelAptosSeed)
ui.Warning("Using default Aptos private key for chain write simulation. To use your own key, set CRE_APTOS_PRIVATE_KEY in your .env file or system environment.")
}
sentinel, _ := hex.DecodeString(defaultSentinelAptosSeed)
if broadcast && hex.EncodeToString(bytes) == hex.EncodeToString(sentinel) {
return nil, fmt.Errorf("you must configure a valid Aptos private key to perform on-chain writes. Please set CRE_APTOS_PRIVATE_KEY in your .env file or system environment before using the --broadcast flag")
}
k := &crypto.Ed25519PrivateKey{}
if err := k.FromBytes(bytes); err != nil {
return nil, fmt.Errorf("build Ed25519 key: %w", err)
}
return k, nil
}

func (ct *AptosChainType) ResolveTriggerData(_ context.Context, _ uint64, _ chain.TriggerParams) (interface{}, error) {
return nil, fmt.Errorf("aptos: no trigger surface")
}

func (ct *AptosChainType) RegisterCapabilities(ctx context.Context, cfg chain.CapabilityConfig) ([]services.Service, error) {
typedClients := make(map[uint64]aptosfakes.AptosClient, len(cfg.Clients))
for sel, c := range cfg.Clients {
ac, ok := c.(aptosfakes.AptosClient)
if !ok {
return nil, fmt.Errorf("aptos: client for selector %d is not aptosfakes.AptosClient", sel)
}
typedClients[sel] = ac
}
var pk *crypto.Ed25519PrivateKey
if cfg.PrivateKey != nil {
var ok bool
pk, ok = cfg.PrivateKey.(*crypto.Ed25519PrivateKey)
if !ok {
return nil, fmt.Errorf("aptos: private key is not *crypto.Ed25519PrivateKey")
}
}
var lim chain.Limits
if cfg.Limits != nil {
lim = ExtractLimits(cfg.Limits)
}
caps, err := NewAptosChainCapabilities(ctx, cfg.Logger, cfg.Registry, typedClients, cfg.Forwarders, pk, !cfg.Broadcast, lim)
if err != nil {
return nil, err
}
if err := caps.Start(ctx); err != nil {
return nil, fmt.Errorf("aptos: failed to start: %w", err)
}
ct.aptosChains = caps
out := make([]services.Service, 0, len(caps.AptosChains))
for _, fc := range caps.AptosChains {
out = append(out, fc)
}
return out, nil
}

func (ct *AptosChainType) ExecuteTrigger(_ context.Context, _ uint64, _ string, _ interface{}) error {
return fmt.Errorf("aptos: no trigger surface")
}

func (ct *AptosChainType) Supports(selector uint64) bool {
if ct.aptosChains == nil {
return false
}
return ct.aptosChains.AptosChains[selector] != nil
}

func (ct *AptosChainType) ParseTriggerChainSelector(triggerID string) (uint64, bool) {
return chain.ParseTriggerChainSelector(ct.Name(), triggerID)
}

func (ct *AptosChainType) RunHealthCheck(resolved chain.ResolvedChains) error {
return RunRPCHealthCheck(resolved.Clients, resolved.ExperimentalSelectors)
}

func (ct *AptosChainType) CollectCLIInputs(_ *viper.Viper) map[string]string {
return map[string]string{}
}
Loading
Loading