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
5 changes: 5 additions & 0 deletions .changeset/rich-pumas-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink-deployments-framework": patch
---

add Stellar support for mcms adapters, RPC provider, chain config, and blockchain
22 changes: 19 additions & 3 deletions .github/workflows/pull-request-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ jobs:
use-go-cache: true
artifact-name: provider-tests-tron

ci-test-provider-stellar:
name: Provider Tests - Stellar
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
id-token: write
contents: read
actions: read
steps:
- name: Build and test stellar provider packages
uses: smartcontractkit/.github/actions/ci-test-go@dfcba48f05933158428bce867d790e3d5a9baa6b # ci-test-go@1.1.0
with:
go-test-cmd: go test -race -coverprofile=coverage.txt ./chain/stellar/provider/...
use-go-cache: true
artifact-name: provider-tests-stellar

ci-test-provider-fast:
name: Provider Tests - Others
runs-on: ubuntu-latest
Expand All @@ -130,8 +146,8 @@ jobs:
uses: smartcontractkit/.github/actions/ci-test-go@dfcba48f05933158428bce867d790e3d5a9baa6b # ci-test-go@1.1.0
with:
# -p 2 -parallel 3 = 2 packages, 3 tests max = 6 containers max
# Run all provider tests EXCEPT slow ones (aptos, canton, ton, tron) which have dedicated jobs
go-test-cmd: go test -race -p 2 -parallel 3 -coverprofile=coverage.txt $(go list ./... | grep '/provider' | grep -E -v '/(aptos|canton|ton|tron)/provider')
# Run all provider tests EXCEPT slow ones (aptos, canton, ton, tron, stellar) which have dedicated jobs
go-test-cmd: go test -race -p 2 -parallel 3 -coverprofile=coverage.txt $(go list ./... | grep '/provider' | grep -E -v '/(aptos|canton|ton|tron|stellar)/provider')
use-go-cache: true
artifact-name: provider-tests-others

Expand Down Expand Up @@ -203,7 +219,7 @@ jobs:
name: Sonar Scan
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
needs: [ci-test, ci-test-provider-aptos, ci-test-provider-canton, ci-test-provider-ton, ci-test-provider-tron, ci-test-provider-fast, ci-test-catalog-remote, ci-lint-misc, ci-lint]
needs: [ci-test, ci-test-provider-aptos, ci-test-provider-canton, ci-test-provider-ton, ci-test-provider-tron, ci-test-provider-stellar, ci-test-provider-fast, ci-test-catalog-remote, ci-lint-misc, ci-lint]
permissions:
contents: read
actions: read
Expand Down
7 changes: 7 additions & 0 deletions chain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/smartcontractkit/chainlink-deployments-framework/chain/canton"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/solana"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/stellar"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/sui"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/ton"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/tron"
Expand All @@ -27,6 +28,7 @@ var _ BlockChain = sui.Chain{}
var _ BlockChain = ton.Chain{}
var _ BlockChain = tron.Chain{}
var _ BlockChain = canton.Chain{}
var _ BlockChain = stellar.Chain{}

// NetworkType represents the type of network, which can either be mainnet or testnet.
type NetworkType string
Expand Down Expand Up @@ -165,6 +167,11 @@ func (b BlockChains) CantonChains() map[uint64]canton.Chain {
return getChainsByType[canton.Chain, *canton.Chain](b)
}

// StellarChains returns a map of all Stellar chains with their selectors.
func (b BlockChains) StellarChains() map[uint64]stellar.Chain {
return getChainsByType[stellar.Chain, *stellar.Chain](b)
}

// ChainSelectorsOption defines a function type for configuring ChainSelectors
type ChainSelectorsOption func(*chainSelectorsOptions)

Expand Down
48 changes: 38 additions & 10 deletions chain/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/smartcontractkit/chainlink-deployments-framework/chain/canton"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/solana"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/stellar"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/sui"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/ton"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/tron"
Expand All @@ -27,6 +28,7 @@ var suiChain1 = sui.Chain{ChainMetadata: sui.ChainMetadata{Selector: chainsel.SU
var tonChain1 = ton.Chain{ChainMetadata: ton.ChainMetadata{Selector: chainsel.TON_LOCALNET.Selector}}
var tronChain1 = tron.Chain{ChainMetadata: tron.ChainMetadata{Selector: chainsel.TRON_MAINNET.Selector}}
var cantonChain1 = canton.Chain{ChainMetadata: canton.ChainMetadata{Selector: chainsel.CANTON_LOCALNET.Selector}}
var stellarChain1 = stellar.Chain{ChainMetadata: stellar.ChainMetadata{Selector: chainsel.STELLAR_LOCALNET.Selector}}

func TestNewBlockChains(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -165,6 +167,7 @@ func TestBlockChainsAllChains(t *testing.T) {
solanaChain1.Selector, aptosChain1.Selector,
suiChain1.Selector, tonChain1.Selector,
tronChain1.Selector, cantonChain1.Selector,
stellarChain1.Selector,
}

assert.Len(t, allChains, len(expectedSelectors))
Expand Down Expand Up @@ -297,6 +300,21 @@ func TestBlockChainsGetters(t *testing.T) {
}
},
},
{
name: "StellarChains",
runTest: func(t *testing.T, chains chain.BlockChains) {
t.Helper()
stellarChains := chains.StellarChains()
expectedSelectors := []uint64{stellarChain1.Selector}
assert.Len(t, stellarChains, len(expectedSelectors), "unexpected number of Stellar chains")

for _, selector := range expectedSelectors {
_, exists := stellarChains[selector]
assert.True(t, exists, "expected Stellar chain with selector %d", selector)
}
},
description: "expected Stellar chain selectors",
},
}

// Run tests for both value and pointer chains
Expand Down Expand Up @@ -337,6 +355,7 @@ func TestBlockChainsListChainSelectors(t *testing.T) {
solanaChain1.ChainSelector(), aptosChain1.ChainSelector(),
suiChain1.ChainSelector(), tonChain1.ChainSelector(),
tronChain1.ChainSelector(), cantonChain1.ChainSelector(),
stellarChain1.ChainSelector(),
},
description: "expected all chain selectors",
},
Expand Down Expand Up @@ -388,12 +407,18 @@ func TestBlockChainsListChainSelectors(t *testing.T) {
expectedIDs: []uint64{evmChain1.Selector, evmChain2.Selector, solanaChain1.Selector},
description: "expected EVM and Solana chain selectors",
},
{
name: "with family filter - Stellar",
options: []chain.ChainSelectorsOption{chain.WithFamily(chainsel.FamilyStellar)},
expectedIDs: []uint64{stellarChain1.Selector},
description: "expected Stellar chain selectors",
},
{
name: "with exclusion",
options: []chain.ChainSelectorsOption{chain.WithChainSelectorsExclusion(
[]uint64{evmChain1.Selector, aptosChain1.Selector}),
},
expectedIDs: []uint64{evmChain2.Selector, solanaChain1.Selector, suiChain1.Selector, tonChain1.Selector, tronChain1.Selector, cantonChain1.Selector},
expectedIDs: []uint64{evmChain2.Selector, solanaChain1.Selector, suiChain1.Selector, tonChain1.Selector, tronChain1.Selector, cantonChain1.Selector, stellarChain1.Selector},
description: "expected chain selectors excluding 1 and 4",
},
{
Expand All @@ -418,17 +443,18 @@ func TestBlockChainsListChainSelectors(t *testing.T) {
}

// buildBlockChains creates a new BlockChains instance with the test chains.
// 2 evm chains, 1 solana chain, 1 aptos chain, 1 sui chain, 1 ton chain, 1 tron chain.
// 2 evm chains, 1 solana chain, 1 aptos chain, 1 sui chain, 1 ton chain, 1 tron chain, 1 canton chain, 1 stellar chain.
func buildBlockChains() chain.BlockChains {
chains := chain.NewBlockChains(map[uint64]chain.BlockChain{
evmChain1.ChainSelector(): evmChain1,
solanaChain1.ChainSelector(): solanaChain1,
evmChain2.ChainSelector(): evmChain2,
aptosChain1.ChainSelector(): aptosChain1,
suiChain1.ChainSelector(): suiChain1,
tonChain1.ChainSelector(): tonChain1,
tronChain1.ChainSelector(): tronChain1,
cantonChain1.ChainSelector(): cantonChain1,
evmChain1.ChainSelector(): evmChain1,
solanaChain1.ChainSelector(): solanaChain1,
evmChain2.ChainSelector(): evmChain2,
aptosChain1.ChainSelector(): aptosChain1,
suiChain1.ChainSelector(): suiChain1,
tonChain1.ChainSelector(): tonChain1,
tronChain1.ChainSelector(): tronChain1,
cantonChain1.ChainSelector(): cantonChain1,
stellarChain1.ChainSelector(): stellarChain1,
})

return chains
Expand All @@ -454,6 +480,8 @@ func buildBlockChainsPointers() chain.BlockChains {
pointerChains[selector] = &c
case canton.Chain:
pointerChains[selector] = &c
case stellar.Chain:
pointerChains[selector] = &c
default:
continue // skip unsupported chains
}
Expand Down
13 changes: 13 additions & 0 deletions chain/mcms/adapters/chain_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
solrpc "github.com/gagliardetto/solana-go/rpc"
"github.com/smartcontractkit/mcms/sdk/evm"
mcmssui "github.com/smartcontractkit/mcms/sdk/sui"
"github.com/stellar/go-stellar-sdk/clients/rpcclient"
"github.com/xssnick/tonutils-go/ton"

"github.com/smartcontractkit/chainlink-deployments-framework/chain"
cldfaptos "github.com/smartcontractkit/chainlink-deployments-framework/chain/aptos"
cldfevm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
cldfsol "github.com/smartcontractkit/chainlink-deployments-framework/chain/solana"
cldfstellar "github.com/smartcontractkit/chainlink-deployments-framework/chain/stellar"
cldfsui "github.com/smartcontractkit/chainlink-deployments-framework/chain/sui"
cldfton "github.com/smartcontractkit/chainlink-deployments-framework/chain/ton"
)
Expand All @@ -23,6 +25,7 @@ type ChainsFetcher interface {
AptosChains() map[uint64]cldfaptos.Chain
SuiChains() map[uint64]cldfsui.Chain
TonChains() map[uint64]cldfton.Chain
StellarChains() map[uint64]cldfstellar.Chain
}

// ChainAccessAdapter adapts CLDF's chain.BlockChains into a selector + lookup style API.
Expand Down Expand Up @@ -90,3 +93,13 @@ func (a *ChainAccessAdapter) TonClient(selector uint64) (*ton.APIClient, bool) {

return ch.Client, true
}

// StellarClient returns the Stellar RPC client for the given selector.
func (a *ChainAccessAdapter) StellarClient(selector uint64) (*rpcclient.Client, bool) {
ch, ok := a.inner.StellarChains()[selector]
if !ok {
return nil, false
}

return ch.Client, true
}
19 changes: 13 additions & 6 deletions chain/mcms/adapters/chain_access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/smartcontractkit/chainlink-deployments-framework/chain/aptos"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/solana"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/stellar"
chainsui "github.com/smartcontractkit/chainlink-deployments-framework/chain/sui"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/ton"
)
Expand Down Expand Up @@ -45,11 +46,12 @@ func TestChainAccess_SelectorsAndLookups(t *testing.T) {
t.Parallel()

const (
evmSel = uint64(111)
solSel = uint64(222)
aptosSel = uint64(333)
suiSel = uint64(444)
tonSel = uint64(555)
evmSel = uint64(111)
solSel = uint64(222)
aptosSel = uint64(333)
suiSel = uint64(444)
tonSel = uint64(555)
stellarSel = uint64(666)
)

evmOnchain := evm.NewMockOnchainClient(t)
Expand All @@ -66,7 +68,8 @@ func TestChainAccess_SelectorsAndLookups(t *testing.T) {
Client: nil,
Signer: suiSigner,
},
tonSel: ton.Chain{ChainMetadata: ton.ChainMetadata{Selector: tonSel}, Client: nil},
tonSel: ton.Chain{ChainMetadata: ton.ChainMetadata{Selector: tonSel}, Client: nil},
stellarSel: stellar.Chain{ChainMetadata: stellar.ChainMetadata{Selector: stellarSel}, Client: nil},
})

a := Wrap(chains)
Expand All @@ -92,4 +95,8 @@ func TestChainAccess_SelectorsAndLookups(t *testing.T) {
gotTon, ok := a.TonClient(tonSel)
require.True(t, ok)
require.Nil(t, gotTon)

gotStellar, ok := a.StellarClient(stellarSel)
require.True(t, ok)
require.Nil(t, gotStellar)
}
62 changes: 62 additions & 0 deletions chain/stellar/provider/keypair_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package provider

import (
"errors"
"fmt"

"github.com/stellar/go-stellar-sdk/keypair"

"github.com/smartcontractkit/chainlink-deployments-framework/chain/stellar"
)

// KeypairGenerator is an interface for generating Stellar keypairs.
type KeypairGenerator interface {
Generate() (stellar.StellarSigner, error)
}

// keypairFromHex is a KeypairGenerator that generates a keypair from a hex-encoded private key.
type keypairFromHex struct {
hexKey string
}

var _ KeypairGenerator = (*keypairFromHex)(nil)

// KeypairFromHex creates a KeypairGenerator that generates a keypair from a hex-encoded private key.
// The hex string can be with or without the "0x" prefix.
func KeypairFromHex(hexKey string) KeypairGenerator {
return &keypairFromHex{hexKey: hexKey}
}

// Generate generates a Stellar keypair from the hex-encoded private key.
func (k *keypairFromHex) Generate() (stellar.StellarSigner, error) {
if k.hexKey == "" {
return nil, errors.New("hex key is empty")
}

kp, err := stellar.KeypairFromHex(k.hexKey)
if err != nil {
return nil, fmt.Errorf("failed to create keypair from hex: %w", err)
}

return stellar.NewStellarKeypairSigner(kp), nil
}

// keypairRandom is a KeypairGenerator that generates a random keypair.
type keypairRandom struct{}

var _ KeypairGenerator = (*keypairRandom)(nil)

// KeypairRandom creates a KeypairGenerator that generates a random keypair.
func KeypairRandom() KeypairGenerator {
return &keypairRandom{}
}

// Generate generates a random Stellar keypair.
func (k *keypairRandom) Generate() (stellar.StellarSigner, error) {
kp, err := keypair.Random()
if err != nil {
return nil, fmt.Errorf("failed to generate random keypair: %w", err)
}

return stellar.NewStellarKeypairSigner(kp), nil
}
Loading