Skip to content

Commit 045819a

Browse files
committed
Sui support
1 parent 27b768e commit 045819a

File tree

10 files changed

+141
-31
lines changed

10 files changed

+141
-31
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- [EVM](framework/components/blockchains/evm.md)
3939
- [Solana](framework/components/blockchains/solana.md)
4040
- [Aptos](framework/components/blockchains/aptos.md)
41+
- [Sui](framework/components/blockchains/sui.md)
4142
- [Optimism Stack]()
4243
- [Arbitrum Stack]()
4344
- [Chainlink](framework/components/chainlink.md)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Sui Blockchain Client
2+
3+
API is available on [localhost:9000](http://localhost:9000)
4+
5+
## Configuration
6+
7+
```toml
8+
[blockchain_a]
9+
type = "sui"
10+
image = "mysten/sui-tools:mainnet" # if omitted default is mysten/sui-tools:devnet
11+
contracts_dir = "$your_dir"
12+
```
13+
14+
## Usage
15+
16+
```golang
17+
package examples
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"github.com/block-vision/sui-go-sdk/models"
23+
"github.com/block-vision/sui-go-sdk/signer"
24+
"github.com/block-vision/sui-go-sdk/sui"
25+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
26+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
27+
"github.com/stretchr/testify/require"
28+
"testing"
29+
)
30+
31+
type CfgSui struct {
32+
BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"`
33+
}
34+
35+
func TestSuiSmoke(t *testing.T) {
36+
in, err := framework.Load[CfgSui](t)
37+
require.NoError(t, err)
38+
39+
bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA)
40+
require.NoError(t, err)
41+
42+
// network is already funded, here are the keys
43+
_ = bc.NetworkSpecificData.SuiAccount.Mnemonic
44+
_ = bc.NetworkSpecificData.SuiAccount.PublicBase64Key
45+
_ = bc.NetworkSpecificData.SuiAccount.SuiAddress
46+
47+
// execute any additional commands, to deploy contracts or set up
48+
_, err = framework.ExecContainer(bc.ContainerName, []string{"ls", "-lah"})
49+
require.NoError(t, err)
50+
51+
t.Run("test something", func(t *testing.T) {
52+
// use internal URL to connect Chainlink nodes
53+
_ = bc.Nodes[0].DockerInternalHTTPUrl
54+
// use host URL to interact
55+
_ = bc.Nodes[0].HostHTTPUrl
56+
57+
cli := sui.NewSuiClient(bc.Nodes[0].HostHTTPUrl)
58+
59+
signerAccount, err := signer.NewSignertWithMnemonic(bc.NetworkSpecificData.SuiAccount.Mnemonic)
60+
require.NoError(t, err)
61+
rsp, err := cli.SuiXGetAllBalance(context.Background(), models.SuiXGetAllBalanceRequest{
62+
Owner: signerAccount.Address,
63+
})
64+
require.NoError(t, err)
65+
fmt.Printf("My funds: %v\n", rsp)
66+
})
67+
}
68+
```
69+
70+
## Test Private Keys
71+
72+
Since Sui doesn't have official local development chain we are using real node and generating mnemonic at start then funding that account through internal faucet, see
73+
```
74+
// network is already funded, here are the keys
75+
_ = bc.NetworkSpecificData.SuiAccount.Mnemonic
76+
_ = bc.NetworkSpecificData.SuiAccount.PublicBase64Key
77+
_ = bc.NetworkSpecificData.SuiAccount.SuiAddress
78+
```

framework/.changeset/v0.4.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add Sui network support

framework/components/blockchain/blockchain.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ type Input struct {
3030

3131
// Output is a blockchain network output, ChainID and one or more nodes that forms the network
3232
type Output struct {
33-
UseCache bool `toml:"use_cache"`
34-
Family string `toml:"family"`
35-
ContainerName string `toml:"container_name"`
36-
GeneratedData *GeneratedData `toml:"generated_data"`
37-
Container testcontainers.Container `toml:"-"`
38-
ChainID string `toml:"chain_id"`
39-
Nodes []*Node `toml:"nodes"`
33+
UseCache bool `toml:"use_cache"`
34+
Family string `toml:"family"`
35+
ContainerName string `toml:"container_name"`
36+
NetworkSpecificData *NetworkSpecificData `toml:"network_specific_data"`
37+
Container testcontainers.Container `toml:"-"`
38+
ChainID string `toml:"chain_id"`
39+
Nodes []*Node `toml:"nodes"`
4040
}
4141

42-
type GeneratedData struct {
43-
Mnemonic string `toml:"mnemonic"`
42+
type NetworkSpecificData struct {
43+
SuiAccount *SuiWalletInfo
4444
}
4545

4646
// Node represents blockchain node output, URLs required for connection locally and inside docker network

framework/components/blockchain/sui.go

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ import (
1515
"time"
1616
)
1717

18-
type SuiKeyInfo struct {
18+
const (
19+
DefaultFaucetPort = "9123/tcp"
20+
DefaultFaucetPortNum = "9123"
21+
DefaultSuiNodePort = "9000"
22+
)
23+
24+
// SuiWalletInfo info about Sui account/wallet
25+
type SuiWalletInfo struct {
1926
Alias *string `json:"alias"` // Alias key name, usually "null"
2027
Flag int `json:"flag"` // Flag is an integer
2128
KeyScheme string `json:"keyScheme"` // Key scheme is a string
@@ -25,8 +32,11 @@ type SuiKeyInfo struct {
2532
SuiAddress string `json:"suiAddress"` // Sui address is a 0x prefixed hex string
2633
}
2734

35+
// funds provided key using local faucet
36+
// we can't use the best client available - block-vision/sui-go-sdk for that, since some versions have old API and it is hardcoded
37+
// https://github.com/block-vision/sui-go-sdk/blob/main/sui/faucet_api.go#L16
2838
func fundAccount(url string, address string) error {
29-
r := resty.New().SetBaseURL(url).EnableTrace().SetDebug(true)
39+
r := resty.New().SetBaseURL(url)
3040
b := &models.FaucetRequest{
3141
FixedAmountRequest: &models.FaucetFixedAmountRequest{
3242
Recipient: address,
@@ -40,18 +50,21 @@ func fundAccount(url string, address string) error {
4050
return nil
4151
}
4252

43-
func generateKeyData(containerName string, keyCipherType string) (*SuiKeyInfo, error) {
53+
// generateKeyData generates a wallet and returns all the data
54+
func generateKeyData(containerName string, keyCipherType string) (*SuiWalletInfo, error) {
4455
cmdStr := []string{"sui", "keytool", "generate", keyCipherType, "--json"}
4556
keyOut, err := framework.ExecContainer(containerName, cmdStr)
4657
if err != nil {
4758
return nil, err
4859
}
60+
// formatted JSON with, no plain --json version, remove special symbols
4961
cleanKey := strings.ReplaceAll(keyOut, "\x00", "")
5062
cleanKey = strings.ReplaceAll(cleanKey, "\x01", "")
5163
cleanKey = strings.ReplaceAll(cleanKey, "\x02", "")
5264
cleanKey = strings.ReplaceAll(cleanKey, "\n", "")
53-
var key *SuiKeyInfo
54-
if err := json.Unmarshal([]byte("{"+cleanKey[2:]), &key); err != nil {
65+
cleanKey = "{" + cleanKey[2:]
66+
var key *SuiWalletInfo
67+
if err := json.Unmarshal([]byte(cleanKey), &key); err != nil {
5568
return nil, err
5669
}
5770
framework.L.Info().Interface("Key", key).Msg("Test key")
@@ -63,9 +76,9 @@ func defaultSui(in *Input) {
6376
in.Image = "mysten/sui-tools:devnet"
6477
}
6578
if in.Port != "" {
66-
framework.L.Warn().Msg("'port' field is set but only default port can be used: 9000")
79+
framework.L.Warn().Msgf("'port' field is set but only default port can be used: %s", DefaultSuiNodePort)
6780
}
68-
in.Port = "9000"
81+
in.Port = DefaultSuiNodePort
6982
}
7083

7184
func newSui(in *Input) (*Output, error) {
@@ -82,15 +95,15 @@ func newSui(in *Input) (*Output, error) {
8295

8396
req := testcontainers.ContainerRequest{
8497
Image: in.Image,
85-
ExposedPorts: []string{in.Port, "9123/tcp"},
98+
ExposedPorts: []string{in.Port, DefaultFaucetPort},
8699
Name: containerName,
87100
Labels: framework.DefaultTCLabels(),
88101
Networks: []string{framework.DefaultNetworkName},
89102
NetworkAliases: map[string][]string{
90103
framework.DefaultNetworkName: {containerName},
91104
},
92105
HostConfigModifier: func(h *container.HostConfig) {
93-
h.PortBindings = framework.MapTheSamePort(bindPort, "9123/tcp")
106+
h.PortBindings = framework.MapTheSamePort(bindPort, DefaultFaucetPort)
94107
},
95108
ImagePlatform: "linux/amd64",
96109
Env: map[string]string{
@@ -109,7 +122,7 @@ func newSui(in *Input) (*Output, error) {
109122
},
110123
},
111124
// we need faucet for funding
112-
WaitingFor: wait.ForListeningPort("9123/tcp").WithStartupTimeout(10 * time.Second).WithPollInterval(200 * time.Millisecond),
125+
WaitingFor: wait.ForListeningPort(DefaultFaucetPort).WithStartupTimeout(10 * time.Second).WithPollInterval(200 * time.Millisecond),
113126
}
114127

115128
c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
@@ -123,19 +136,18 @@ func newSui(in *Input) (*Output, error) {
123136
if err != nil {
124137
return nil, err
125138
}
126-
keyData, err := generateKeyData(containerName, "ed25519")
139+
suiAccount, err := generateKeyData(containerName, "ed25519")
127140
if err != nil {
128141
return nil, err
129142
}
130-
err = fundAccount(fmt.Sprintf("http://%s:%s", "127.0.0.1", "9123"), keyData.SuiAddress)
131-
if err != nil {
143+
if err := fundAccount(fmt.Sprintf("http://%s:%s", "127.0.0.1", DefaultFaucetPortNum), suiAccount.SuiAddress); err != nil {
132144
return nil, err
133145
}
134146
return &Output{
135-
UseCache: true,
136-
Family: "sui",
137-
ContainerName: containerName,
138-
GeneratedData: &GeneratedData{Mnemonic: keyData.Mnemonic},
147+
UseCache: true,
148+
Family: "sui",
149+
ContainerName: containerName,
150+
NetworkSpecificData: &NetworkSpecificData{SuiAccount: suiAccount},
139151
Nodes: []*Node{
140152
{
141153
HostHTTPUrl: fmt.Sprintf("http://%s:%s", host, in.Port),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[blockchain_a]
22
type = "sui"
3+
image = "mysten/sui-tools:mainnet"

framework/examples/myproject/smoke_sui_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ func TestSuiSmoke(t *testing.T) {
2323
bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA)
2424
require.NoError(t, err)
2525

26-
// execute any additional commands, to deploy contracts or set up
2726
// network is already funded, here are the keys
28-
_ = bc.GeneratedData.Mnemonic
27+
_ = bc.NetworkSpecificData.SuiAccount.Mnemonic
28+
_ = bc.NetworkSpecificData.SuiAccount.PublicBase64Key
29+
_ = bc.NetworkSpecificData.SuiAccount.SuiAddress
2930

31+
// execute any additional commands, to deploy contracts or set up
3032
_, err = framework.ExecContainer(bc.ContainerName, []string{"ls", "-lah"})
3133
require.NoError(t, err)
3234

@@ -35,14 +37,17 @@ func TestSuiSmoke(t *testing.T) {
3537
_ = bc.Nodes[0].DockerInternalHTTPUrl
3638
// use host URL to interact
3739
_ = bc.Nodes[0].HostHTTPUrl
38-
cli := sui.NewSuiClient("http://localhost:9000")
3940

40-
signerAccount, err := signer.NewSignertWithMnemonic(bc.GeneratedData.Mnemonic)
41+
cli := sui.NewSuiClient(bc.Nodes[0].HostHTTPUrl)
42+
43+
signerAccount, err := signer.NewSignertWithMnemonic(bc.NetworkSpecificData.SuiAccount.Mnemonic)
4144
require.NoError(t, err)
4245
rsp, err := cli.SuiXGetAllBalance(context.Background(), models.SuiXGetAllBalanceRequest{
4346
Owner: signerAccount.Address,
4447
})
4548
require.NoError(t, err)
4649
fmt.Printf("My funds: %v\n", rsp)
50+
fmt.Printf("url1: %s\n", bc.Nodes[0].HostHTTPUrl)
51+
fmt.Printf("url2: %s\n", bc.Nodes[0].DockerInternalHTTPUrl)
4752
})
4853
}

framework/examples/myproject_cll/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ require (
3434
github.com/aws/aws-sdk-go-v2/service/sts v1.33.0 // indirect
3535
github.com/aws/smithy-go v1.22.1 // indirect
3636
github.com/bits-and-blooms/bitset v1.13.0 // indirect
37+
github.com/block-vision/sui-go-sdk v1.0.6 // indirect
3738
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
3839
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
3940
github.com/consensys/bavard v0.1.13 // indirect
@@ -100,6 +101,9 @@ require (
100101
github.com/sirupsen/logrus v1.9.3 // indirect
101102
github.com/supranational/blst v0.3.13 // indirect
102103
github.com/testcontainers/testcontainers-go v0.35.0 // indirect
104+
github.com/tidwall/gjson v1.14.4 // indirect
105+
github.com/tidwall/match v1.1.1 // indirect
106+
github.com/tidwall/pretty v1.2.0 // indirect
103107
github.com/tklauser/go-sysconf v0.3.12 // indirect
104108
github.com/tklauser/numcpus v0.6.1 // indirect
105109
github.com/yusufpapurcu/wmi v1.2.3 // indirect

framework/examples/myproject_cll/go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4242
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
4343
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
4444
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
45+
github.com/block-vision/sui-go-sdk v1.0.6 h1:FysCc4TJC8v4BEBbCjJPDR4iR5eKqJT1dxGwsT67etg=
46+
github.com/block-vision/sui-go-sdk v1.0.6/go.mod h1:FyK1vGE8lWm9QA1fdQpf1agfXQSMbPT8AV1BICgx6d8=
4547
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
4648
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
4749
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
@@ -282,6 +284,12 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70
282284
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
283285
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
284286
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
287+
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
288+
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
289+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
290+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
291+
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
292+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
285293
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
286294
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
287295
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=

framework/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ replace github.com/smartcontractkit/chainlink-testing-framework/seth => ../seth
77
require (
88
github.com/aws/aws-sdk-go-v2/config v1.27.39
99
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.33.3
10+
github.com/block-vision/sui-go-sdk v1.0.6
1011
github.com/charmbracelet/huh v0.6.0
1112
github.com/charmbracelet/huh/spinner v0.0.0-20241028115900-20a4d21717a8
1213
github.com/davecgh/go-spew v1.1.1
@@ -48,7 +49,6 @@ require (
4849
github.com/aws/smithy-go v1.21.0 // indirect
4950
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
5051
github.com/bits-and-blooms/bitset v1.13.0 // indirect
51-
github.com/block-vision/sui-go-sdk v1.0.6 // indirect
5252
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
5353
github.com/bytedance/sonic v1.12.3 // indirect
5454
github.com/bytedance/sonic/loader v0.2.0 // indirect

0 commit comments

Comments
 (0)