diff --git a/common/program_id.go b/common/program_id.go index e74968d7..c7b3f67a 100644 --- a/common/program_id.go +++ b/common/program_id.go @@ -9,4 +9,5 @@ var ( Secp256k1ProgramID = PublicKeyFromString("KeccakSecp256k11111111111111111111111111111") TokenProgramID = PublicKeyFromString("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") SPLAssociatedTokenAccountProgramID = PublicKeyFromString("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + SPLNameServiceProgramID = PublicKeyFromString("namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX") ) diff --git a/program/nsprog/state.go b/program/nsprog/state.go new file mode 100644 index 00000000..1e3f1df1 --- /dev/null +++ b/program/nsprog/state.go @@ -0,0 +1,26 @@ +package nsprog + +import ( + "fmt" + + "github.com/portto/solana-go-sdk/common" +) + +type NameRecordHeader struct { + ParentName common.PublicKey + Owner common.PublicKey + Class common.PublicKey + Data []byte +} + +func NameRecordHeaderFromData(data []byte) (NameRecordHeader, error) { + if len(data) < 96 { + return NameRecordHeader{}, fmt.Errorf("data length should bigger than 96") + } + return NameRecordHeader{ + ParentName: common.PublicKeyFromBytes(data[:32]), + Owner: common.PublicKeyFromBytes(data[32:64]), + Class: common.PublicKeyFromBytes(data[64:96]), + Data: data[96:], + }, nil +} diff --git a/program/nsprog/state_test.go b/program/nsprog/state_test.go new file mode 100644 index 00000000..1fd4fbb6 --- /dev/null +++ b/program/nsprog/state_test.go @@ -0,0 +1,48 @@ +package nsprog + +import ( + "fmt" + "testing" + + "github.com/portto/solana-go-sdk/common" + "github.com/stretchr/testify/assert" +) + +func TestNameRecordHeaderFromData(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + args args + want NameRecordHeader + err error + }{ + { + args: args{ + data: []byte{0x3d, 0x53, 0xc2, 0x4b}, + }, + want: NameRecordHeader{}, + err: fmt.Errorf("data length should bigger than 96"), + }, + { + args: args{ + data: []byte{0x3d, 0x53, 0xc2, 0x4b, 0x38, 0x36, 0xe, 0xd3, 0x81, 0x3a, 0x23, 0xdf, 0xb2, 0xdf, 0xd8, 0x20, 0xab, 0x58, 0x21, 0xcb, 0x79, 0x29, 0xa3, 0x8d, 0x2e, 0xaa, 0xb2, 0x52, 0xe8, 0x38, 0x25, 0x95, 0x58, 0x7f, 0x6a, 0x3d, 0xab, 0x65, 0xe7, 0x3e, 0x12, 0xde, 0x67, 0xbc, 0x31, 0x73, 0x2d, 0xa0, 0x4e, 0xea, 0xfb, 0x12, 0x83, 0xdd, 0x21, 0x10, 0x82, 0x5c, 0xcb, 0x1e, 0xdf, 0x79, 0xa2, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x52, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6a, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x6d, 0x37, 0x37, 0x32, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x61, 0x6c, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + }, + want: NameRecordHeader{ + ParentName: common.PublicKeyFromString("58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx"), + Class: common.PublicKey{}, + Owner: common.PublicKeyFromString("6xTZhtNA8aaipc2hHFP616gFvDcvWmYMGsDFHwrsF3m1"), + Data: []byte{0x52, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6a, 0x61, 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x6d, 0x37, 0x37, 0x32, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x61, 0x6c, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + }, + err: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NameRecordHeaderFromData(tt.args.data) + assert.Equal(t, tt.err, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/program/nsprog/utils.go b/program/nsprog/utils.go new file mode 100644 index 00000000..71facfd6 --- /dev/null +++ b/program/nsprog/utils.go @@ -0,0 +1,39 @@ +package nsprog + +import ( + "crypto/sha256" + + "github.com/portto/solana-go-sdk/common" +) + +var TwitterVerificationAuthority = common.PublicKeyFromString("FvPH7PrVrLGKPfqaf3xJodFTjZriqrAXXLTVWEorTFBi") +var TwitterRootParentRegisteryKey = common.PublicKeyFromString("4YcexoW3r78zz16J2aqmukBLRwGq6rAvWzJpkYAXqebv") +var SolTldAuthority = common.PublicKeyFromString("58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx") + +const HashPrefix = "SPL Name Service" + +// GetHashName ... +func GetHashName(name string) []byte { + h := sha256.Sum256([]byte(HashPrefix + name)) + return h[:] +} + +// GetNameAccountKey return the pubkey correspond to name +func GetNameAccountKey(hashName []byte, nameClass, nameParent common.PublicKey) common.PublicKey { + seed := [][]byte{ + hashName, + nameClass.Bytes(), + nameParent.Bytes(), + } + pubkey, _, _ := common.FindProgramAddress(seed, common.SPLNameServiceProgramID) + return pubkey +} + +// GetTwitterRegistryKey return the pubkey corespond to twitter handle +func GetTwitterRegistryKey(twitterHandle string) common.PublicKey { + return GetNameAccountKey( + GetHashName(twitterHandle), + common.PublicKey{}, + TwitterRootParentRegisteryKey, + ) +} diff --git a/program/nsprog/utils_test.go b/program/nsprog/utils_test.go new file mode 100644 index 00000000..1c277896 --- /dev/null +++ b/program/nsprog/utils_test.go @@ -0,0 +1,96 @@ +package nsprog + +import ( + "reflect" + "testing" + + "github.com/portto/solana-go-sdk/common" + "github.com/stretchr/testify/assert" +) + +func TestGetTwitterRegistryKey(t *testing.T) { + type args struct { + twitterHandle string + } + tests := []struct { + name string + args args + want common.PublicKey + }{ + { + args: args{ + twitterHandle: "gghost07114721", + }, + want: common.PublicKeyFromString("5r2pKbCFibGZp18u51tcvzQpsNsA98TyCF1UbDmbSUk5"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, GetTwitterRegistryKey(tt.args.twitterHandle)) + }) + } +} + +func TestGetHashName(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want []byte + }{ + { + args: args{ + name: "blocto", + }, + want: []byte{0x62, 0x94, 0x3a, 0xc2, 0x9e, 0x7b, 0x9d, 0x4e, 0x38, 0x53, 0xb2, 0x84, 0xdd, 0x7f, 0x1, 0x66, 0xeb, 0x5f, 0x0, 0xe3, 0x1f, 0x25, 0x53, 0x51, 0x83, 0x61, 0x38, 0x33, 0xcd, 0xc5, 0xf9, 0x3}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetHashName(tt.args.name); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetHashName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetNameAccountKey(t *testing.T) { + type args struct { + hashName []byte + nameClass common.PublicKey + nameParent common.PublicKey + } + tests := []struct { + name string + args args + want common.PublicKey + }{ + { + name: "domain: blocto.sol", + args: args{ + hashName: GetHashName("blocto"), + nameClass: common.PublicKey{}, + nameParent: SolTldAuthority, + }, + want: common.PublicKeyFromString("6yAP2rFW7wQiqVmySE4DTfQSWmp6fR1geGyWx6SQMAhS"), + }, + { + name: "domain yihau.blocto.sol", + args: args{ + hashName: GetHashName("\x00yihau"), + nameClass: common.PublicKey{}, + nameParent: GetNameAccountKey(GetHashName("blocto"), common.PublicKey{}, SolTldAuthority), + }, + want: common.PublicKeyFromString("5Cjg2Xah4Cc24yM7zsfbyBuXKZ6Wm9ZJqHa5n47vnvNz"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetNameAccountKey(tt.args.hashName, tt.args.nameClass, tt.args.nameParent); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetNameAccountKey() = %v, want %v", got, tt.want) + } + }) + } +}