Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
9 changes: 5 additions & 4 deletions bindings/bind/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/block-vision/sui-go-sdk/transaction"

bindutils "github.com/smartcontractkit/chainlink-sui/bindings/utils"
"github.com/smartcontractkit/chainlink-sui/codec"
"github.com/smartcontractkit/chainlink-sui/relayer/client"
)

Expand All @@ -32,10 +33,10 @@ type transactionObjectChangesClient interface {
GetTransactionChangedObjects(ctx context.Context, digest string) ([]*suirpcv2.ChangedObject, error)
}

type Object struct {
Id string
InitialSharedVersion *uint64
}
// Object is an alias for codec.Object for backward compatibility.
//
// Deprecated: use codec.Object directly.
type Object = codec.Object

type EmptyMoveStructWitness struct{}

Expand Down
36 changes: 7 additions & 29 deletions bindings/bind/json_decoder.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,24 @@
package bind

import (
"encoding/json"
"errors"
"fmt"
"math/big"
"reflect"
"strings"

"github.com/mitchellh/mapstructure"

"github.com/smartcontractkit/chainlink-sui/codec"
)

// DecodeJSONReturn decodes gRPC/JSON Move return values into the provided target.
//
// Deprecated: use codec.DecodeJSONReturn directly.
func DecodeJSONReturn(data any, target any) error {
if target == nil {
return errors.New("target cannot be nil")
}

if raw, ok := data.(json.RawMessage); ok {
var intermediate any
if err := json.Unmarshal(raw, &intermediate); err != nil {
return fmt.Errorf("json unmarshal failed: %w", err)
}

return DecodeJSONReturn(intermediate, target)
}

if reflect.TypeOf(data) == reflect.TypeOf(target).Elem() {
reflect.ValueOf(target).Elem().Set(reflect.ValueOf(data))
return nil
}

targetType := reflect.TypeOf(target).Elem()

bigPtrT := reflect.TypeOf((*big.Int)(nil))
bigValT := bigPtrT.Elem()
if targetType == bigValT || targetType == bigPtrT {
return decodeBigInt(data, target)
}

return decodeWithMapstructure(data, target)
return codec.DecodeJSONReturn(data, target)
}

// decodeBigInt decodes a string into a big.Int target.
func decodeBigInt(data any, target any) error {
str, ok := data.(string)
if !ok {
Expand All @@ -67,6 +44,7 @@ func decodeBigInt(data any, target any) error {
return nil
}

// decodeWithMapstructure decodes using mapstructure with custom hooks.
func decodeWithMapstructure(data any, target any) error {
config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
Expand Down
51 changes: 9 additions & 42 deletions bindings/bind/utils.go
Original file line number Diff line number Diff line change
@@ -1,59 +1,26 @@
package bind

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

"github.com/block-vision/sui-go-sdk/models"
"github.com/block-vision/sui-go-sdk/utils"

"github.com/smartcontractkit/chainlink-sui/codec"
)

// IsSuiAddress returns true if addr is a valid Sui address/ObjectID.
// It is an improvement over the sui-go-sdk's IsValidSuiAddress function.
//
// Deprecated: use codec.IsSuiAddress directly.
func IsSuiAddress(addr string) bool {
if !(strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X")) {
return false
}

h := addr[2:]

// 1..64 hex chars
if len(h) == 0 || len(h) > 64 {
return false
}

// hex only
for _, r := range h {
if !isHexRune(r) {
return false
}
}

// hex.DecodeString requires even length; allow odd by left-padding one '0'
if len(h)%2 == 1 {
h = "0" + h
}

b, err := hex.DecodeString(h)

if err != nil {
return false
}

// must be ≤32 bytes (32 bytes == 64 hex chars)
return len(b) <= 32
return codec.IsSuiAddress(addr)
}

Comment thread
FelixFan1992 marked this conversation as resolved.
// ToSuiAddress normalizes and validates a Sui address
// ToSuiAddress normalizes and validates a Sui address.
//
// Deprecated: use codec.ToSuiAddress directly.
func ToSuiAddress(address string) (string, error) {
normalized := utils.NormalizeSuiAddress(address)
if !IsSuiAddress(string(normalized)) {
return "", fmt.Errorf("invalid sui address: %s", address)
}

return string(normalized), nil
return codec.ToSuiAddress(address)
}

func GetFailedTxError(tx *models.SuiTransactionBlockResponse) error {
Expand Down
241 changes: 241 additions & 0 deletions codec/bcs_converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package codec

import (
"fmt"
"strconv"

aptosBCS "github.com/aptos-labs/aptos-go-sdk/bcs"
)

// BCSPrimitiveHandler defines a function that reads a primitive type from a BCS deserializer
type BCSPrimitiveHandler func(*aptosBCS.Deserializer) (any, error)

// BCSVectorHandler defines a function that reads a vector type from a BCS deserializer
type BCSVectorHandler func(*aptosBCS.Deserializer, uint64) (any, error)

// BCSTypeConverter provides a registry-based approach to converting BCS types to JSON-compatible values
type BCSTypeConverter struct {
primitiveHandlers map[string]BCSPrimitiveHandler
vectorHandlers map[string]BCSVectorHandler
}

// NewBCSTypeConverter creates a new BCS type converter with all standard Sui types registered
func NewBCSTypeConverter() *BCSTypeConverter {
c := &BCSTypeConverter{
primitiveHandlers: make(map[string]BCSPrimitiveHandler),
vectorHandlers: make(map[string]BCSVectorHandler),
}

// Register primitive type handlers
c.registerPrimitiveHandlers()
c.registerVectorHandlers()

return c
}

// registerPrimitiveHandlers registers all standard Sui primitive type handlers
func (c *BCSTypeConverter) registerPrimitiveHandlers() {
// U8 - return as-is
c.RegisterPrimitive("U8", func(d *aptosBCS.Deserializer) (any, error) {
return d.U8(), nil
})
c.RegisterPrimitive("u8", func(d *aptosBCS.Deserializer) (any, error) {
return d.U8(), nil
})

// U16 - return as-is
c.RegisterPrimitive("U16", func(d *aptosBCS.Deserializer) (any, error) {
return d.U16(), nil
})
c.RegisterPrimitive("u16", func(d *aptosBCS.Deserializer) (any, error) {
return d.U16(), nil
})

// U32 - return as-is
c.RegisterPrimitive("U32", func(d *aptosBCS.Deserializer) (any, error) {
return d.U32(), nil
})
c.RegisterPrimitive("u32", func(d *aptosBCS.Deserializer) (any, error) {
return d.U32(), nil
})

// U64 - return as string for JSON compatibility
c.RegisterPrimitive("U64", func(d *aptosBCS.Deserializer) (any, error) {
value := d.U64()
return strconv.FormatUint(value, base10), nil
})
c.RegisterPrimitive("u64", func(d *aptosBCS.Deserializer) (any, error) {
value := d.U64()
return strconv.FormatUint(value, base10), nil
})

// U128 - return as string for JSON compatibility
c.RegisterPrimitive("U128", func(d *aptosBCS.Deserializer) (any, error) {
value := d.U128()
return value.String(), nil
})
c.RegisterPrimitive("u128", func(d *aptosBCS.Deserializer) (any, error) {
value := d.U128()
return value.String(), nil
})

// U256 - return as string for JSON compatibility
c.RegisterPrimitive("U256", func(d *aptosBCS.Deserializer) (any, error) {
value := d.U256()
return value.String(), nil
})
c.RegisterPrimitive("u256", func(d *aptosBCS.Deserializer) (any, error) {
value := d.U256()
return value.String(), nil
})

// Bool - return as-is
c.RegisterPrimitive("Bool", func(d *aptosBCS.Deserializer) (any, error) {
return d.Bool(), nil
})
c.RegisterPrimitive("bool", func(d *aptosBCS.Deserializer) (any, error) {
return d.Bool(), nil
})

// Address - return as byte array
c.RegisterPrimitive("Address", func(d *aptosBCS.Deserializer) (any, error) {
addressBytesLen := 32
return d.ReadFixedBytes(addressBytesLen), nil
})
c.RegisterPrimitive("address", func(d *aptosBCS.Deserializer) (any, error) {
addressBytesLen := 32
return d.ReadFixedBytes(addressBytesLen), nil
})
}

// registerVectorHandlers registers standard vector type handlers
func (c *BCSTypeConverter) registerVectorHandlers() {
// Vector<U8> - read as byte array
c.RegisterVector("U8", func(d *aptosBCS.Deserializer, length uint64) (any, error) {
bytes := make([]byte, length)
for i := range length {
bytes[i] = d.U8()
}
return bytes, nil
})
c.RegisterVector("u8", func(d *aptosBCS.Deserializer, length uint64) (any, error) {
bytes := make([]byte, length)
for i := range length {
bytes[i] = d.U8()
}
return bytes, nil
})

// Vector<U64> - read as uint64 array
c.RegisterVector("U64", func(d *aptosBCS.Deserializer, length uint64) (any, error) {
uint64s := make([]uint64, length)
for i := range length {
uint64s[i] = d.U64()
}
return uint64s, nil
})
c.RegisterVector("u64", func(d *aptosBCS.Deserializer, length uint64) (any, error) {
uint64s := make([]uint64, length)
for i := range length {
uint64s[i] = d.U64()
}
return uint64s, nil
})

// Vector<Address> - read as address array
c.RegisterVector("Address", func(d *aptosBCS.Deserializer, length uint64) (any, error) {
addresses := make([]any, length)
for i := range length {
addressBytesLen := 32
addresses[i] = d.ReadFixedBytes(addressBytesLen)
}
return addresses, nil
})
c.RegisterVector("address", func(d *aptosBCS.Deserializer, length uint64) (any, error) {
addresses := make([]any, length)
for i := range length {
addressBytesLen := 32
addresses[i] = d.ReadFixedBytes(addressBytesLen)
}
return addresses, nil
})
}

// RegisterPrimitive registers a handler for a primitive type
func (c *BCSTypeConverter) RegisterPrimitive(typeName string, handler BCSPrimitiveHandler) {
c.primitiveHandlers[typeName] = handler
}

// RegisterVector registers a handler for a vector type
func (c *BCSTypeConverter) RegisterVector(elementType string, handler BCSVectorHandler) {
c.vectorHandlers[elementType] = handler
}

// DecodePrimitive decodes a primitive type using the registered handler
func (c *BCSTypeConverter) DecodePrimitive(deserializer *aptosBCS.Deserializer, primitiveType string) (any, error) {
handler, ok := c.primitiveHandlers[primitiveType]
if !ok {
return nil, fmt.Errorf("unsupported primitive type: %s", primitiveType)
}
return handler(deserializer)
}

// DecodeVector decodes a vector type using the registered handler
func (c *BCSTypeConverter) DecodeVector(deserializer *aptosBCS.Deserializer, elementType string) (any, error) {
length := deserializer.Uleb128()

handler, ok := c.vectorHandlers[elementType]
if !ok {
// Fall back to generic primitive vector handling
primitiveHandler, primitiveOk := c.primitiveHandlers[elementType]
if !primitiveOk {
return nil, fmt.Errorf("unsupported vector element type: %s", elementType)
}

// Generic vector of primitives
result := make([]any, length)
for i := range length {
value, err := primitiveHandler(deserializer)
if err != nil {
return nil, fmt.Errorf("failed to decode vector element at index %d: %w", i, err)
}
result[i] = value
}
return result, nil
}

return handler(deserializer, uint64(length))
}

// HasPrimitiveHandler checks if a primitive type handler is registered
func (c *BCSTypeConverter) HasPrimitiveHandler(typeName string) bool {
_, ok := c.primitiveHandlers[typeName]
return ok
}

// HasVectorHandler checks if a vector type handler is registered
func (c *BCSTypeConverter) HasVectorHandler(elementType string) bool {
_, ok := c.vectorHandlers[elementType]
return ok
}

// Global BCS converter instance for package-wide use (lazy initialization)
var defaultBCSConverter *BCSTypeConverter

// getDefaultBCSConverter returns the global BCS converter, initializing it if necessary
func getDefaultBCSConverter() *BCSTypeConverter {
if defaultBCSConverter == nil {
defaultBCSConverter = NewBCSTypeConverter()
}
return defaultBCSConverter
}

// DecodeBCSPrimitive decodes a BCS primitive type using the default converter
func DecodeBCSPrimitive(deserializer *aptosBCS.Deserializer, primitiveType string) (any, error) {
return getDefaultBCSConverter().DecodePrimitive(deserializer, primitiveType)
}

// DecodeBCSVector decodes a BCS vector type using the default converter
func DecodeBCSVector(deserializer *aptosBCS.Deserializer, elementType string) (any, error) {
return getDefaultBCSConverter().DecodeVector(deserializer, elementType)
}
File renamed without changes.
Loading
Loading