Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
19 changes: 10 additions & 9 deletions packages/core-go/address/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ func Detect(addr string) (AddressKind, error) {
return "", err
}

switch versionByte {
case VersionByteG:
return KindG, nil
case VersionByteM:
return KindM, nil
case VersionByteC:
return KindC, nil
default:
return "", ErrUnknownVersionByteError
func Detect(address string) string {
if strkey.IsValidEd25519PublicKey(address) {
return "G"
}
if strkey.IsValidMuxedAccountEd25519PublicKey(address) {
return "M"
}
if _, err := strkey.Decode(strkey.VersionByteContract, address); err == nil {
return "C"
}
return "invalid"
}

14 changes: 14 additions & 0 deletions packages/core-go/address/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package address

type ParseResult struct {
Kind string
Address string
Warnings []Warning
Err *AddressError
}

type AddressError struct {
Code ErrorCode
Input string
Message string
}
9 changes: 7 additions & 2 deletions packages/core-go/go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
module github.com/stellar-address-kit/core-go

go 1.21
go 1.22

toolchain go1.22.2

require github.com/stellar/go v0.0.0-20241220220012-089553bb324a

require (
github.com/stellar/go v0.0.0-20231213180453-6a1e9a2f0e31
github.com/pkg/errors v0.9.1 // indirect
github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 // indirect
)
14 changes: 14 additions & 0 deletions packages/core-go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stellar/go v0.0.0-20241220220012-089553bb324a h1:DHSzxKJCTX1e0vtXe2pFqvDq2Pn6pENCr2xykWFciy4=
github.com/stellar/go v0.0.0-20241220220012-089553bb324a/go.mod h1:gY4J6cGScn4oPT7lDBurLUEf/ltVJfeMk8prEF6IJKo=
github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 h1:OzCVd0SV5qE3ZcDeSFCmOWLZfEWZ3Oe8KtmSOYKEVWE=
github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
22 changes: 3 additions & 19 deletions packages/core-go/muxed/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,15 @@ import (
)

func DecodeMuxed(mAddress string) (string, string, error) {
versionByte, payload, err := address.DecodeStrKey(mAddress)
muxedAccount, err := strkey.DecodeMuxedAccount(mAddress)
if err != nil {
return "", "", err
}

if versionByte != address.VersionByteM {
return "", "", ErrUnknownVersionByteError
}

// For muxed accounts, payload is: 32-byte pubkey + 8-byte memo ID
if len(payload) != 40 {
return "", "", ErrInvalidLengthError
}

// Extract the 32-byte public key
pubkey := payload[:32]

// Extract the 8-byte memo ID (big endian)
memoID := binary.BigEndian.Uint64(payload[32:40])

// Encode the public key as a G address
baseG, err := address.EncodeStrKey(address.VersionByteG, pubkey)
baseG, err := muxedAccount.AccountID()
if err != nil {
return "", "", err
}

return baseG, strconv.FormatUint(memoID, 10), nil
return baseG, strconv.FormatUint(muxedAccount.ID(), 10), nil
}
30 changes: 17 additions & 13 deletions packages/core-go/muxed/encode.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package muxed

import (
"encoding/binary"
"github.com/stellar-address-kit/core-go/address"
"fmt"
"math/big"

"github.com/stellar/go/strkey"
)

func EncodeMuxed(baseG string, id uint64) (string, error) {
versionByte, pubkey, err := address.DecodeStrKey(baseG)
if err != nil {
return "", NewInvalidGAddressError(err)
func EncodeMuxed(baseG string, id string) (string, error) {
idInt := new(big.Int)
if _, ok := idInt.SetString(id, 10); !ok {
return "", fmt.Errorf("invalid muxed account id %q", id)
}
if versionByte != address.VersionByteG {
return "", ErrInvalidGAddressError
if idInt.Sign() < 0 || idInt.BitLen() > 64 {
return "", fmt.Errorf("muxed account id %q exceeds uint64", id)
}

payload := make([]byte, 40)
copy(payload, pubkey)
binary.BigEndian.PutUint64(payload[32:], id)
var muxedAccount strkey.MuxedAccount
if err := muxedAccount.SetAccountID(baseG); err != nil {
return "", err
}

return address.EncodeStrKey(address.VersionByteM, payload)
}
muxedAccount.SetID(idInt.Uint64())
return muxedAccount.Address()
}
61 changes: 33 additions & 28 deletions packages/core-go/routing/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,23 @@ func ExtractRouting(input RoutingInput) RoutingResult {
}
}

if parsed.Kind == address.KindM {
baseG := parsed.BaseG
id := strconv.FormatUint(parsed.MuxedID, 10)
warnings := []address.Warning{}
if parsed.Kind == "M" {
baseG, id, err := muxed.DecodeMuxed(parsed.Address)
if err != nil {
return RoutingResult{
RoutingSource: "none",
Warnings: []address.Warning{},
DestinationError: &DestinationError{
Code: address.ErrUnknownPrefix,
Message: err.Error(),
},
}
}

if input.MemoType == "id" || (input.MemoType == "text" && input.MemoValue != "" && digitsOnlyRegex.MatchString(input.MemoValue)) {
warnings := append([]address.Warning{}, parsed.Warnings...)
memoValue := stringValue(input.MemoValue)

if input.MemoType == "id" || (input.MemoType == "text" && digitsOnlyRegex.MatchString(memoValue)) {
warnings = append(warnings, address.Warning{
Code: address.WarnMemoPresentWithMuxed,
Severity: "warn",
Expand All @@ -83,35 +94,21 @@ func ExtractRouting(input RoutingInput) RoutingResult {

return RoutingResult{
DestinationBaseAccount: baseG,
RoutingID: id,
RoutingID: NewRoutingID(id),
RoutingSource: "muxed",
Warnings: warnings,
}
}

if input.SourceAccount != "" {
sourceKind, err := address.Detect(input.SourceAccount)
if err == nil && sourceKind == address.KindC {
return RoutingResult{
RoutingSource: "none",
Warnings: []address.Warning{{
Code: address.WarnContractSenderDetected,
Severity: "info",
Message: "Source account is a contract address and cannot be used for routing.",
}},
}
}
}

routingID := ""
var routingID *RoutingID
routingSource := "none"
warnings := []address.Warning{}
unsupportedMemoType := normalizeUnsupportedMemoType(input.MemoType)
warnings := append([]address.Warning{}, parsed.Warnings...)
memoValue := stringValue(input.MemoValue)

if input.MemoType == "id" {
norm := NormalizeMemoTextID(input.MemoValue)
routingID = norm.Normalized
norm := NormalizeMemoTextID(memoValue)
if norm.Normalized != "" {
routingID = NewRoutingID(norm.Normalized)
routingSource = "memo"
}
warnings = append(warnings, norm.Warnings...)
Expand All @@ -123,10 +120,10 @@ func ExtractRouting(input RoutingInput) RoutingResult {
Message: "MEMO_ID was empty, non-numeric, or exceeded uint64 max.",
})
}
} else if input.MemoType == "text" && input.MemoValue != "" {
norm := NormalizeMemoTextID(input.MemoValue)
} else if input.MemoType == "text" && memoValue != "" {
norm := NormalizeMemoTextID(memoValue)
if norm.Normalized != "" {
routingID = norm.Normalized
routingID = NewRoutingID(norm.Normalized)
routingSource = "memo"
warnings = append(warnings, norm.Warnings...)
} else {
Expand Down Expand Up @@ -163,3 +160,11 @@ func ExtractRouting(input RoutingInput) RoutingResult {
Warnings: warnings,
}
}

func stringValue(s *string) string {
if s == nil {
return ""
}

return *s
}
5 changes: 1 addition & 4 deletions packages/core-go/routing/memo.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import (
)

var digitsOnly = regexp.MustCompile(`^\d+$`)
var uint64Max = func() *big.Int {
val, _ := new(big.Int).SetString("18446744073709551615", 10)
return val
}()
var uint64Max, _ = new(big.Int).SetString("18446744073709551615", 10)

type NormalizeResult struct {
Normalized string
Expand Down
71 changes: 71 additions & 0 deletions packages/core-go/routing/result.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package routing

import (
"encoding/json"
"fmt"
"strconv"

"github.com/stellar-address-kit/core-go/address"
)

type MemoType string

const (
Expand All @@ -9,3 +17,66 @@ const (
MemoTypeHash MemoType = "hash"
MemoTypeReturn MemoType = "return"
)

type RoutingID struct {
raw string
}

func (r *RoutingID) UnmarshalJSON(data []byte) error {
if r == nil {
return fmt.Errorf("routing id: UnmarshalJSON on nil receiver")
}

if string(data) == "null" {
r.raw = ""
return nil
}

var id string
if len(data) > 0 && data[0] == '"' {
if err := json.Unmarshal(data, &id); err != nil {
return fmt.Errorf("routing id: invalid quoted value: %w", err)
}
} else {
id = string(data)
}

parsed, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return fmt.Errorf("routing id: invalid uint64 value %q: %w", id, err)
}

r.raw = strconv.FormatUint(parsed, 10)
return nil
}

func (r *RoutingID) String() string {
if r == nil {
return ""
}
return r.raw
}

func (r *RoutingID) Uint64() (uint64, error) {
if r == nil {
return 0, strconv.ErrSyntax
}
return strconv.ParseUint(r.raw, 10, 64)
}

func NewRoutingID(s string) *RoutingID {
return &RoutingID{raw: s}
}

type RoutingResult struct {
DestinationBaseAccount string
RoutingID *RoutingID
RoutingSource string // "muxed" | "memo" | "none"
Warnings []address.Warning
DestinationError *DestinationError
}

type DestinationError struct {
Code address.ErrorCode
Message string
}
Loading
Loading