Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

#### xrpl

- `EncodeMPTokenMetadata`, `DecodeMPTokenMetadata` and `ValidateMPTokenMetadata` utils to encode, decode and validate MPTokenMetadata as per XLS-89 standard.
- `AuthorizeChannel` to authorize a payment channel.
- Added `Loan` and `LoanBroker` ledger entry types for the lending protocol.
- Added loan transaction types:
Expand Down
6 changes: 6 additions & 0 deletions pkg/typecheck/typecheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ func IsStringNumericUint(s string, base, bitSize int) bool {
return err == nil
}

// IsMap checks if the given interface is a map.
func IsMap(m any) bool {
_, ok := m.(map[string]any)
return ok
}

// xrplNumberPattern matches optional sign, digits, optional decimal, optional exponent (scientific).
// Allows leading zeros; rejects empty string, lone sign, or missing digits.
var xrplNumberPattern = regexp.MustCompile(`^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$`)
Expand Down
31 changes: 31 additions & 0 deletions pkg/typecheck/typecheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,37 @@ func TestIsStringNumericUint(t *testing.T) {
}
}

func TestIsMap(t *testing.T) {
tests := []struct {
name string
m interface{}
want bool
}{
{
name: "pass - Valid map",
m: map[string]interface{}{},
want: true,
},
{
name: "pass - Invalid map",
m: "Invalid map",
want: false,
},
{
name: "pass - Invalid map (2)",
m: int(2),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsMap(tt.m); got != tt.want {
t.Errorf("IsMap(%v) = %v, want %v", tt.m, got, tt.want)
}
})
}
}

func TestIsXRPLNumber(t *testing.T) {
tests := []struct {
name string
Expand Down
129 changes: 128 additions & 1 deletion xrpl/transaction/types/errors.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
//revive:disable:var-naming
package types

import "errors"
import (
"errors"
"fmt"
"strings"
)

var (
// xchain bridge
Expand Down Expand Up @@ -55,4 +59,127 @@ var (
ErrInvalidCredentialCount = errors.New("accepted credentials list must contain at least one and no more than the maximum allowed number of items")
// ErrDuplicateCredentials is returned when duplicate credentials are present in the list.
ErrDuplicateCredentials = errors.New("credentials list cannot contain duplicate elements")

// mptoken metadata

// ErrInvalidMPTokenMetadataHex is returned when the MPTokenMetadata field is not a hex string.
ErrInvalidMPTokenMetadataHex = errors.New("mptoken metadata should be a hex string")

// ErrInvalidMPTokenMetadataJSON is returned when the MPTokenMetadata field is not a valid JSON object.
ErrInvalidMPTokenMetadataJSON = errors.New("mptoken metadata should be a valid JSON object")

// ErrInvalidMPTokenMetadataSize is returned when the MPTokenMetadata field is longer than 1024 bytes.
ErrInvalidMPTokenMetadataSize = errors.New("mptoken metadata byte length should be at most 1024 bytes")

// ErrInvalidMPTokenMetadataTicker is returned when the ticker does not match the required format (uppercase letters A-Z and digits 0-9, max 6 characters).
ErrInvalidMPTokenMetadataTicker = errors.New("mptoken metadata ticker should contain only uppercase letters (A-Z) and digits (0-9), max 6 characters")

// ErrInvalidMPTokenMetadataRWASubClassRequired is returned when the asset subclass is required when the asset class is rwa.
ErrInvalidMPTokenMetadataRWASubClassRequired = errors.New("mptoken metadata asset subclass is required when asset class is rwa")

// ErrInvalidMPTokenMetadataAdditionalInfo is returned when the additional info is not a string or a map.
ErrInvalidMPTokenMetadataAdditionalInfo = errors.New("mptoken metadata additional info must be a string or a map")

// ErrInvalidMPTokenMetadataURIs is returned when the URIs is not an array of objects each with uri/u, category/c, and title/t properties.
ErrInvalidMPTokenMetadataURIs = errors.New("mptoken metadata URIs should be an array of objects each with uri/u, category/c, and title/t properties")
)

// ErrInvalidMPTokenMetadataUnknownField is returned when a field is unknown in MPToken metadata.
type ErrInvalidMPTokenMetadataUnknownField struct {
Field string
}

// Error implements the error interface for ErrInvalidMPTokenMetadataUnknownField
func (e ErrInvalidMPTokenMetadataUnknownField) Error() string {
return fmt.Sprintf("mptoken metadata unknown field: %s", e.Field)
}

// ErrInvalidMPTokenMetadataFieldCount is returned when the MPToken metadata has an invalid field count.
type ErrInvalidMPTokenMetadataFieldCount struct {
Count int
}

// Error implements the error interface for ErrMarshalPayload
func (e ErrInvalidMPTokenMetadataFieldCount) Error() string {
return fmt.Sprintf("mptoken metadata field count should be at most %d", e.Count)
}

// ErrInvalidMPTokenMetadataFieldCollision is returned when both long and compact forms of a field are present in MPToken metadata.
type ErrInvalidMPTokenMetadataFieldCollision struct {
Long string
Compact string
}

// Error implements the error interface for ErrInvalidMPTokenMetadataFieldCollision
func (e ErrInvalidMPTokenMetadataFieldCollision) Error() string {
return fmt.Sprintf("mptoken metadata field collision: %s and %s both present", e.Long, e.Compact)
}

// ErrInvalidMPTokenMetadataMissingField is returned when a required field is missing from MPToken metadata.
type ErrInvalidMPTokenMetadataMissingField struct {
Field string
}

// Error implements the error interface for ErrInvalidMPTokenMetadataMissingField
func (e ErrInvalidMPTokenMetadataMissingField) Error() string {
return fmt.Sprintf("mptoken metadata field missing: %s", e.Field)
}

// ErrInvalidMPTokenMetadataInvalidString is returned when a string value is invalid.
type ErrInvalidMPTokenMetadataInvalidString struct {
Key string
}

// Error implements the error interface for ErrInvalidMPTokenMetadataInvalidString
func (e ErrInvalidMPTokenMetadataInvalidString) Error() string {
return fmt.Sprintf("mptoken metadata field %s must be a valid string", e.Key)
}

// ErrInvalidMPTokenMetadataEmptyString is returned when a string value is empty.
type ErrInvalidMPTokenMetadataEmptyString struct {
Key string
}

// Error implements the error interface for ErrInvalidMPTokenMetadataEmptyString
func (e ErrInvalidMPTokenMetadataEmptyString) Error() string {
return fmt.Sprintf("mptoken metadata field %s cannot be empty", e.Key)
}

// ErrInvalidMPTokenMetadataAssetClass is returned when the asset class is invalid.
type ErrInvalidMPTokenMetadataAssetClass struct {
AssetClassSet [6]string
}

// Error implements the error interface for ErrInvalidMPTokenMetadataAssetClass
func (e ErrInvalidMPTokenMetadataAssetClass) Error() string {
return fmt.Sprintf("mptoken metadata asset class should be one of: %s", strings.Join(e.AssetClassSet[:], ", "))
}

// ErrInvalidMPTokenMetadataAssetSubClass is returned when the asset subclass is invalid.
type ErrInvalidMPTokenMetadataAssetSubClass struct {
AssetSubclassSet []string
}

// Error implements the error interface for ErrInvalidMPTokenMetadataAssetSubClass
func (e ErrInvalidMPTokenMetadataAssetSubClass) Error() string {
return fmt.Sprintf("mptoken metadata asset subclass should be one of: %s", strings.Join(e.AssetSubclassSet, ", "))
}

// MPTokenMetadataValidationErrors is a custom error type that holds a list of validation failures.
// It stores actual error objects to support wrapping/unwrapping.
type MPTokenMetadataValidationErrors []error

// Error implements the error interface.
func (v MPTokenMetadataValidationErrors) Error() string {
var msgs []string
for _, err := range v {
msgs = append(msgs, err.Error())
}
return fmt.Sprintf("mptoken metadata validation failed with %d errors:\n- %s", len(v), strings.Join(msgs, "\n- "))
}

// Unwrap returns the list of errors, allowing "errors.Is" support for lists.
// Example: errors.Is(err, types.ErrInvalidMPTokenMetadataTicker)
func (v MPTokenMetadataValidationErrors) Unwrap() []error {
return v
}
Loading