Skip to content

Feature: sui keystore #17618

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 25 commits into
base: ccip-aptos-codec
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d771b7
add and update chainlink-aptos and aptos-go-sdk
cfal May 21, 2025
9b49a7b
basic config for sui keystore
faisal-chainlink May 1, 2025
90ec151
add commands
faisal-chainlink May 1, 2025
a1f6031
keystore edits
faisal-chainlink May 1, 2025
0baeac5
edit cltest
faisal-chainlink May 1, 2025
341db0d
add testutils
faisal-chainlink May 1, 2025
c4a366d
add controller
faisal-chainlink May 1, 2025
d0b5ab4
add routes & update key / key test
faisal-chainlink May 2, 2025
edba46b
web resolvers
faisal-chainlink May 2, 2025
aa295e6
services updates
faisal-chainlink May 2, 2025
11f0e4d
interoperators, relay factory and env
faisal-chainlink May 2, 2025
4f75b7a
CLI command / entrypoint for sui
faisal-chainlink May 7, 2025
227a55f
sui mocks
faisal-chainlink May 7, 2025
1548010
logging
JoaoVasques May 7, 2025
6eab994
drop logging
JoaoVasques May 7, 2025
d41fabf
sample sui config
JoaoVasques May 7, 2025
7505f07
add sui config parser entrypoint
faisal-chainlink May 8, 2025
df17a58
remove sui-config.toml
faisal-chainlink May 8, 2025
20e21e4
Add sui private plugin
JoaoVasques May 8, 2025
48a5dfd
Add changeset for Sui keystore
JoaoVasques May 9, 2025
43ab194
core/services/keystore/keys/ocr2key: rename AptosKeyring to Ed25519Ke…
cfal May 21, 2025
dec71d4
core/services/keystore: add Sui to keystore
cfal May 21, 2025
fd149b3
core/capabilities/ccip/ccipaptos/pluginconfig.go: register plugin for…
cfal May 21, 2025
2a6e8b8
deployment/environment/devenv/environment.go: add nil SuiChains arg
cfal May 21, 2025
468f0df
integration-tests/smoke/ccip/ccip_sui_messaging_test.go: WIP add EVM2…
cfal May 21, 2025
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
5 changes: 5 additions & 0 deletions .changeset/twelve-socks-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

Add Sui keystore and relayer plugin basic integration
31 changes: 17 additions & 14 deletions core/capabilities/ccip/ccipaptos/pluginconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ import (
)

// initializePluginConfig returns a PluginConfig for Aptos chains.
func initializePluginConfig(lggr logger.Logger, extraDataCodec ccipcommon.ExtraDataCodec) ccipcommon.PluginConfig {
return ccipcommon.PluginConfig{
CommitPluginCodec: NewCommitPluginCodecV1(),
ExecutePluginCodec: NewExecutePluginCodecV1(extraDataCodec),
MessageHasher: NewMessageHasherV1(lggr.Named(chainsel.FamilyAptos).Named("MessageHasherV1"), extraDataCodec),
TokenDataEncoder: NewAptosTokenDataEncoder(),
GasEstimateProvider: NewGasEstimateProvider(),
RMNCrypto: nil,
ContractTransmitterFactory: ocrimpls.NewAptosContractTransmitterFactory(extraDataCodec),
ChainRW: ChainCWProvider{},
ExtraDataCodec: ExtraDataDecoder{},
AddressCodec: AddressCodec{},
func initializePluginConfigFunc(chainselFamily string) ccipcommon.InitFunction {
return func(lggr logger.Logger, extraDataCodec ccipcommon.ExtraDataCodec) ccipcommon.PluginConfig {
return ccipcommon.PluginConfig{
CommitPluginCodec: NewCommitPluginCodecV1(),
ExecutePluginCodec: NewExecutePluginCodecV1(extraDataCodec),
MessageHasher: NewMessageHasherV1(lggr.Named(chainselFamily).Named("MessageHasherV1"), extraDataCodec),
TokenDataEncoder: NewAptosTokenDataEncoder(),
GasEstimateProvider: NewGasEstimateProvider(),
RMNCrypto: nil,
ContractTransmitterFactory: ocrimpls.NewAptosContractTransmitterFactory(extraDataCodec),
ChainRW: ChainCWProvider{},
ExtraDataCodec: ExtraDataDecoder{},
AddressCodec: AddressCodec{},
}
}
}

func init() {
// Register the Aptos plugin config factory
ccipcommon.RegisterPluginConfig(chainsel.FamilyAptos, initializePluginConfig)
// Register the Aptos and Sui plugin config factory
ccipcommon.RegisterPluginConfig(chainsel.FamilyAptos, initializePluginConfigFunc(chainsel.FamilyAptos))
ccipcommon.RegisterPluginConfig(chainsel.FamilySui, initializePluginConfigFunc(chainsel.FamilySui))
}
1 change: 1 addition & 0 deletions core/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ func NewApp(s *Shell) *cli.App {
keysCommand("Solana", NewSolanaKeysClient(s)),
keysCommand("StarkNet", NewStarkNetKeysClient(s)),
keysCommand("Aptos", NewAptosKeysClient(s)),
keysCommand("Sui", NewSuiKeysClient(s)),
keysCommand("Tron", NewTronKeysClient(s)),

initVRFKeysSubCmd(s),
Expand Down
57 changes: 57 additions & 0 deletions core/cmd/sui_key_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"github.com/smartcontractkit/chainlink-common/pkg/utils"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/suikey"
"github.com/smartcontractkit/chainlink/v2/core/web/presenters"
)

type SuiKeyPresenter struct {
JAID
presenters.SuiKeyResource
}

// RenderTable implements TableRenderer
func (p SuiKeyPresenter) RenderTable(rt RendererTable) error {
headers := []string{"ID", "Sui Public Key"}
rows := [][]string{p.ToRow()}

if _, err := rt.Write([]byte("🔑 Sui Keys\n")); err != nil {
return err
}
renderList(headers, rows, rt.Writer)

return utils.JustError(rt.Write([]byte("\n")))
}

func (p *SuiKeyPresenter) ToRow() []string {
row := []string{
p.ID,
p.PubKey,
}

return row
}

type SuiKeyPresenters []SuiKeyPresenter

// RenderTable implements TableRenderer
func (ps SuiKeyPresenters) RenderTable(rt RendererTable) error {
headers := []string{"ID", "Sui Public Key"}
rows := [][]string{}

for _, p := range ps {
rows = append(rows, p.ToRow())
}

if _, err := rt.Write([]byte("🔑 Sui Keys\n")); err != nil {
return err
}
renderList(headers, rows, rt.Writer)

return utils.JustError(rt.Write([]byte("\n")))
}

func NewSuiKeysClient(s *Shell) KeysClient {
return newKeysClient[suikey.Key, SuiKeyPresenter, SuiKeyPresenters]("Sui", s)
}
174 changes: 174 additions & 0 deletions core/cmd/sui_key_commands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package cmd_test

import (
"bytes"
"context"
"flag"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/suikey"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"

"github.com/smartcontractkit/chainlink-common/pkg/utils"
"github.com/smartcontractkit/chainlink/v2/core/cmd"
"github.com/smartcontractkit/chainlink/v2/core/internal/cltest"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/web/presenters"
)

func TestSuiKeyPresenter_RenderTable(t *testing.T) {
t.Parallel()

var (
id = "1"
pubKey = "somepubkey"
buffer = bytes.NewBufferString("")
r = cmd.RendererTable{Writer: buffer}
)

p := cmd.SuiKeyPresenter{
JAID: cmd.JAID{ID: id},
SuiKeyResource: presenters.SuiKeyResource{
JAID: presenters.NewJAID(id),
PubKey: pubKey,
},
}

// Render a single resource
require.NoError(t, p.RenderTable(r))

output := buffer.String()
assert.Contains(t, output, id)
assert.Contains(t, output, pubKey)

// Render many resources
buffer.Reset()
ps := cmd.SuiKeyPresenters{p}
require.NoError(t, ps.RenderTable(r))

output = buffer.String()
assert.Contains(t, output, id)
assert.Contains(t, output, pubKey)
}

func TestShell_SuiKeys(t *testing.T) {
app := startNewApplicationV2(t, nil)
ks := app.GetKeyStore().Sui()
cleanup := func() {
ctx := context.Background()
keys, err := ks.GetAll()
require.NoError(t, err)
for _, key := range keys {
require.NoError(t, utils.JustError(ks.Delete(ctx, key.ID())))
}
requireSuiKeyCount(t, app, 0)
}

t.Run("ListSuiKeys", func(tt *testing.T) {
defer cleanup()
ctx := testutils.Context(t)
client, r := app.NewShellAndRenderer()
key, err := app.GetKeyStore().Sui().Create(ctx)
require.NoError(t, err)
requireSuiKeyCount(t, app, 1)
assert.NoError(t, cmd.NewSuiKeysClient(client).ListKeys(cltest.EmptyCLIContext()))
require.Len(t, r.Renders, 1)
keys := *r.Renders[0].(*cmd.SuiKeyPresenters)
assert.Equal(t, key.PublicKeyStr(), keys[0].PubKey)
})

t.Run("CreateSuiKey", func(tt *testing.T) {
defer cleanup()
client, _ := app.NewShellAndRenderer()
require.NoError(t, cmd.NewSuiKeysClient(client).CreateKey(nilContext))
keys, err := app.GetKeyStore().Sui().GetAll()
require.NoError(t, err)
require.Len(t, keys, 1)
})

t.Run("DeleteSuiKey", func(tt *testing.T) {
defer cleanup()
ctx := testutils.Context(t)
client, _ := app.NewShellAndRenderer()
key, err := app.GetKeyStore().Sui().Create(ctx)
require.NoError(t, err)
requireSuiKeyCount(t, app, 1)
set := flag.NewFlagSet("test", 0)
flagSetApplyFromAction(cmd.NewSuiKeysClient(client).DeleteKey, set, "sui")

require.NoError(tt, set.Set("yes", "true"))

strID := key.ID()
err = set.Parse([]string{strID})
require.NoError(t, err)
c := cli.NewContext(nil, set, nil)
err = cmd.NewSuiKeysClient(client).DeleteKey(c)
require.NoError(t, err)
requireSuiKeyCount(t, app, 0)
})

t.Run("ImportExportSuiKey", func(tt *testing.T) {
defer cleanup()
defer deleteKeyExportFile(t)
ctx := testutils.Context(t)
client, _ := app.NewShellAndRenderer()

_, err := app.GetKeyStore().Sui().Create(ctx)
require.NoError(t, err)

keys := requireSuiKeyCount(t, app, 1)
key := keys[0]
keyName := keyNameForTest(t)

// Export test invalid id
set := flag.NewFlagSet("test Sui export", 0)
flagSetApplyFromAction(cmd.NewSuiKeysClient(client).ExportKey, set, "sui")

require.NoError(tt, set.Parse([]string{"0"}))
require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt"))
require.NoError(tt, set.Set("output", keyName))

c := cli.NewContext(nil, set, nil)
err = cmd.NewSuiKeysClient(client).ExportKey(c)
require.Error(t, err, "Error exporting")
require.Error(t, utils.JustError(os.Stat(keyName)))

// Export test
set = flag.NewFlagSet("test Sui export", 0)
flagSetApplyFromAction(cmd.NewSuiKeysClient(client).ExportKey, set, "sui")

require.NoError(tt, set.Parse([]string{key.ID()}))
require.NoError(tt, set.Set("new-password", "../internal/fixtures/incorrect_password.txt"))
require.NoError(tt, set.Set("output", keyName))

c = cli.NewContext(nil, set, nil)

require.NoError(t, cmd.NewSuiKeysClient(client).ExportKey(c))
require.NoError(t, utils.JustError(os.Stat(keyName)))

require.NoError(t, utils.JustError(app.GetKeyStore().Sui().Delete(ctx, key.ID())))
requireSuiKeyCount(t, app, 0)

set = flag.NewFlagSet("test Sui import", 0)
flagSetApplyFromAction(cmd.NewSuiKeysClient(client).ImportKey, set, "sui")

require.NoError(tt, set.Parse([]string{keyName}))
require.NoError(tt, set.Set("old-password", "../internal/fixtures/incorrect_password.txt"))
c = cli.NewContext(nil, set, nil)
require.NoError(t, cmd.NewSuiKeysClient(client).ImportKey(c))

requireSuiKeyCount(t, app, 1)
})
}

func requireSuiKeyCount(t *testing.T, app chainlink.Application, length int) []suikey.Key {
t.Helper()
keys, err := app.GetKeyStore().Sui().GetAll()
require.NoError(t, err)
require.Len(t, keys, length)
return keys
}
1 change: 1 addition & 0 deletions core/config/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type AppConfig interface {
StarkNetEnabled() bool
AptosEnabled() bool
TronEnabled() bool
SuiEnabled() bool

Validate() error
ValidateDB() error
Expand Down
1 change: 1 addition & 0 deletions core/config/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
MedianPlugin = NewPlugin("median")
MercuryPlugin = NewPlugin("mercury")
AptosPlugin = NewPlugin("aptos")
SuiPlugin = NewPlugin("sui")
CosmosPlugin = NewPlugin("cosmos")
SolanaPlugin = NewPlugin("solana")
StarknetPlugin = NewPlugin("starknet")
Expand Down
2 changes: 2 additions & 0 deletions core/internal/cltest/cltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/suikey"
"io"
"math/big"
"math/rand"
Expand Down Expand Up @@ -134,6 +135,7 @@ var (
DefaultStarkNetKey = starkkey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed))
DefaultAptosKey = aptoskey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed))
DefaultTronKey = tronkey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed))
DefaultSuiKey = suikey.MustNewInsecure(keystest.NewRandReaderFromSeed(KeyBigIntSeed))
DefaultVRFKey = vrfkey.MustNewV2XXXTestingOnly(big.NewInt(KeyBigIntSeed))
)

Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ require (
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/apache/arrow-go/v18 v18.0.0 // indirect
github.com/aptos-labs/aptos-go-sdk v1.6.3-0.20250331001805-0680b714db6d // indirect
github.com/aptos-labs/aptos-go-sdk v1.7.0 // indirect
github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/avast/retry-go/v4 v4.6.1 // indirect
Expand Down Expand Up @@ -383,7 +383,7 @@ require (
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartcontractkit/ccip-owner-contracts v0.1.0 // indirect
github.com/smartcontractkit/chain-selectors v1.0.57 // indirect
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250502091650-484cfa7ccddf // indirect
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250521100854-8a454be7edc0 // indirect
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250520123946-6aaf88e0848a // indirect
github.com/smartcontractkit/chainlink-feeds v0.1.2-0.20250227211209-7cd000095135 // indirect
github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20250514200342-5169fbe9e28d // indirect
Expand Down
8 changes: 4 additions & 4 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE=
github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw=
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
github.com/aptos-labs/aptos-go-sdk v1.6.3-0.20250331001805-0680b714db6d h1:VsrpaOlsWs+XaofivnfP9gU5aSBmRudoJEMZvXGvrok=
github.com/aptos-labs/aptos-go-sdk v1.6.3-0.20250331001805-0680b714db6d/go.mod h1:BgddSKFtfWFLK+no8l+AwCcb/Lh1lv74ybYLzeonloo=
github.com/aptos-labs/aptos-go-sdk v1.7.0 h1:4FSjePHenTWMf/285tS/Im3Xb+rXy01j4IHl6KdJGXw=
github.com/aptos-labs/aptos-go-sdk v1.7.0/go.mod h1:vYm/yHr6cQpoUBMw/Q93SRR1IhP0mPTBrEGjShwUvXc=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
Expand Down Expand Up @@ -1257,8 +1257,8 @@ github.com/smartcontractkit/ccip-owner-contracts v0.1.0 h1:GiBDtlx7539o7AKlDV+9L
github.com/smartcontractkit/ccip-owner-contracts v0.1.0/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY=
github.com/smartcontractkit/chain-selectors v1.0.57 h1:KApvb/Kpn15YalY7khFQaHH3mb7teX7JF/gwvq3ti7M=
github.com/smartcontractkit/chain-selectors v1.0.57/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8=
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250502091650-484cfa7ccddf h1:zzG5D68tGbDBW3mQW6sa50ga0kGRQp9S7F4vqZO0ZrU=
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250502091650-484cfa7ccddf/go.mod h1:yj4d1XS6lBOXM1sDMP1ULCbRJgOuBYf2V7VtQ6Qgev8=
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250521100854-8a454be7edc0 h1:si5bhKShZPBcTfMykTTytzAy29HgFr4VVOwLhhmLYco=
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250521100854-8a454be7edc0/go.mod h1:ITBdddHYp2ocxNjTgBXDGqzWop2qGXenzFK6Oek+uqc=
github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU=
github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20250515091132-6c08936b29ab h1:O8NTsAlJYAVRBOD69zlHnEmNvLkQQ3Nnzl0VAsgtFBs=
Expand Down
3 changes: 3 additions & 0 deletions core/services/chainlink/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ func NewApplication(ctx context.Context, opts ApplicationOpts) (Application, err
if cfg.TronEnabled() {
initOps = append(initOps, InitTron(relayerFactory, keyStore.Tron(), cfg.TronConfigs()))
}
if cfg.SuiEnabled() {
initOps = append(initOps, InitSui(relayerFactory, keyStore.Sui(), cfg.SuiConfigs()))
}

relayChainInterops, err := NewCoreRelayerChainInteroperators(initOps...)
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions core/services/chainlink/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type Config struct {
Aptos RawConfigs `toml:",omitempty"`

Tron RawConfigs `toml:",omitempty"`

Sui RawConfigs `toml:",omitempty"`
}

// RawConfigs is a list of RawConfig.
Expand Down Expand Up @@ -341,6 +343,8 @@ func (c *Config) setDefaults() {
c.Starknet.SetDefaults()

c.Tron.SetDefaults()

c.Sui.SetDefaults()
}

func (c *Config) SetFrom(f *Config) (err error) {
Expand Down Expand Up @@ -370,6 +374,10 @@ func (c *Config) SetFrom(f *Config) (err error) {
err = multierr.Append(err, commonconfig.NamedMultiErrorList(err6, "Tron"))
}

if err7 := c.Sui.SetFrom(f.Sui); err7 != nil {
err = multierr.Append(err, commonconfig.NamedMultiErrorList(err7, "Sui"))
}

_, err = commonconfig.MultiErrorList(err)

return err
Expand Down
Loading
Loading