Stellar PHP SDK API Documentation

XdrJsonHelper
in package

FinalYes

Primitive SEP-51 (XDR-JSON) helper methods.

This class provides low-level encoding and decoding utilities for the SEP-0051 standard mapping between Stellar XDR structures and their JSON representations. Higher-level toJson/fromJson methods on individual XDR classes delegate to these primitives for the byte-level and numeric operations defined in the spec.

All methods are stateless and operate only on their arguments.

Tags
see
https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0051.md

Table of Contents

Methods

bytesToHex()  : string
SEP-51 Opaque type: encode raw bytes as a lowercase hex string.
canonicalJson()  : string
Canonical JSON normalisation for structural JSON comparison.
decodeText()  : mixed
Decode a SEP-0051 JSON document from its text form.
escapeString()  : string
SEP-51 String type: escape bytes per the SEP-51 String escape ladder.
hexToBytes()  : string
Inverse of bytesToHex: decode a lowercase hex string back to raw bytes.
int128PartsToString()  : string
Assemble a signed 128-bit integer from hi/lo limbs and return as a base-10 string.
int256PartsToString()  : string
Assemble a signed 256-bit integer from four limbs and return as a base-10 string.
int64ToString()  : string
64-bit signed integer to base-10 string (SEP-51 Hyper Integer encoding).
ksortRecursive()  : mixed
Recursively sort object property names for canonical JSON normalisation.
normalizeFieldAlias()  : array<string|int, mixed>
Fold a field's input alias into the field's canonical key.
rejectDuplicateKeys()  : void
Reject a JSON document in which any single object repeats a key.
rejectUnknownFields()  : void
Reject a struct-object JSON input that carries keys the type does not declare.
safePreview()  : string
Return a safe, bounded preview of user-supplied input for use in exception messages.
stringToInt128Parts()  : array{hi: string, lo: string}
Decompose a signed 128-bit integer string into hi/lo limbs.
stringToInt256Parts()  : array{hiHi: string, hiLo: string, loHi: string, loLo: string}
Decompose a signed 256-bit integer string into four limbs.
stringToInt64()  : int
Parse a base-10 integer string (or a PHP int) to a 64-bit signed int.
stringToUint128Parts()  : array{hi: string, lo: string}
Decompose an unsigned 128-bit integer string into hi/lo uint64 parts.
stringToUint256Parts()  : array{hiHi: string, hiLo: string, loHi: string, loLo: string}
Decompose an unsigned 256-bit integer string into four uint64 limbs.
stringToUint64()  : int
Parse a base-10 unsigned-integer string (or a PHP int) to a uint64 value.
uint128PartsToString()  : string
Assemble an unsigned 128-bit integer from hi/lo uint64 parts and return as a base-10 string.
uint256PartsToString()  : string
Assemble an unsigned 256-bit integer from four uint64 limbs and return as a base-10 string.
uint64ToString()  : string
64-bit unsigned integer to base-10 string (SEP-51 Unsigned Hyper Integer encoding).
unescapeString()  : string
Inverse of escapeString: decode a SEP-51 escaped-ASCII string back to raw bytes.
wrapUnsignedToSignedInt()  : int
Wrap an unsigned uint64 base-10 string into the matching PHP signed-int representation used by the Parts struct fields.

Methods

bytesToHex()

SEP-51 Opaque type: encode raw bytes as a lowercase hex string.

public static bytesToHex(string $bytes) : string

Per SEP-0051 the Opaque types (both fixed-length and variable-length) are represented as hexadecimal strings. An empty byte sequence encodes as an empty string "" (not "0" and not "00").

Parameters
$bytes : string

Raw binary input (any length, including zero).

Return values
string

Lowercase hex string (even length; two hex chars per byte).

canonicalJson()

Canonical JSON normalisation for structural JSON comparison.

public static canonicalJson(string $json) : string

The algorithm:

  1. Decode with assoc=false (stdClass mode) so the empty-object / empty-array distinction is preserved: json_decode('}') -> stdClass, json_decode('[]') -> [].
  2. Recurse via ksortRecursive: sort object property names lexicographically; preserve list order for indexed arrays.
  3. Re-encode with JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE so that forward slashes and non-ASCII codepoints are not double-escaped.

The result is a deterministic, whitespace-free JSON string. Two JSON values that are semantically equal (same structure, same key order after sorting, same values) will produce byte-identical canonical forms.

Note: this method does not impose an input-size limit. Callers are responsible for bounding $json length before invoking this method; an HTTP layer or file reader should apply a size limit appropriate to its threat model.

Parameters
$json : string

Any valid JSON string.

Tags
throws
JsonException

On malformed input JSON.

Return values
string

Canonical (sorted-keys, compact) JSON string.

decodeText()

Decode a SEP-0051 JSON document from its text form.

public static decodeText(string $json) : mixed

Every fromJson(string $json) entry point routes through here, so the text-level input rules hold uniformly across all XDR types: the document is scanned for duplicate object keys and then parsed.

The duplicate-key scan has to happen on the text because json_decode cannot report duplicates — it resolves them last-value-wins, which would silently discard the earlier value from a document that is not valid SEP-0051 input. See self::rejectDuplicateKeys.

The tree entry points (fromJsonValue, taking an already-decoded PHP value) need no such check: a PHP array cannot hold two identical string keys, so a duplicate is unrepresentable by the time a value reaches them.

Parameters
$json : string

The raw JSON document.

Tags
throws
InvalidArgumentException

When an object in $json repeats a key.

JsonException

When $json is not syntactically valid JSON.

see
https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0051.md
Return values
mixed

The decoded value, with objects as associative arrays.

escapeString()

SEP-51 String type: escape bytes per the SEP-51 String escape ladder.

public static escapeString(string $bytes) : string

Escaping rules (applied byte-by-byte on the raw binary input):

  • 0x00 (NUL) -> \0
  • 0x09 (TAB) -> \t
  • 0x0A (LF) -> \n
  • 0x0D (CR) -> \r
  • 0x5C (BS) -> \
  • 0x20..0x7E (printable ASCII, excluding backslash) -> verbatim
  • all other bytes (0x01..0x08, 0x0B, 0x0C, 0x0E..0x1F, 0x7F, 0x80..0xFF) -> \xNN (lowercase hex, exactly two digits)

The input is treated as a raw byte string (not UTF-8); strlen() and ord() are used throughout — never mb_strlen() or mb_substr().

When this output is subsequently stored inside a JSON string literal the JSON encoder will escape each backslash a second time, producing the double-escaped form the spec specifies (e.g. "\xc3" in JSON text).

Parameters
$bytes : string

Raw binary input (any byte sequence).

Return values
string

Escaped ASCII output (only printable ASCII bytes).

hexToBytes()

Inverse of bytesToHex: decode a lowercase hex string back to raw bytes.

public static hexToBytes(string $hex) : string

Strict mode is enforced: only lowercase hexadecimal characters [0-9a-f] are accepted. Uppercase hex is rejected; SEP-0051 §Opaque specifies lowercase hex output, and the decoder mirrors that constraint so that round-trips remain canonical.

Parameters
$hex : string

Lowercase hex string (even length; zero length allowed).

Tags
throws
InvalidArgumentException

On odd-length input, uppercase characters, or non-hex characters.

Return values
string

Raw binary bytes.

int128PartsToString()

Assemble a signed 128-bit integer from hi/lo limbs and return as a base-10 string.

public static int128PartsToString(string $hi, string $lo) : string

Per XDR Int128Parts (verified against XdrInt128Parts.php encode path): hi is signed int64; lo is unsigned uint64.

result = hi_signed * 2^64 + lo_unsigned

lo_unsigned is obtained by treating the PHP int stored in $lo as an unsigned 64-bit value (i.e. adding 2^64 if negative). GMP is used throughout to handle the full range.

Parameters
$hi : string

Signed int64 as base-10 string (range [-2^63, 2^63-1]).

$lo : string

Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).

Tags
throws
InvalidArgumentException

If hi or lo are not valid integer strings.

Return values
string

Base-10 decimal string of the assembled 128-bit signed integer.

int256PartsToString()

Assemble a signed 256-bit integer from four limbs and return as a base-10 string.

public static int256PartsToString(string $hiHi, string $hiLo, string $loHi, string $loLo) : string

Per XDR Int256Parts (verified against XdrInt256Parts.php encode path):

  • hiHi: signed int64 (most significant)
  • hiLo: unsigned uint64
  • loHi: unsigned uint64
  • loLo: unsigned uint64 (least significant)

All limb strings may be supplied in either unsigned form ("18446744073709551615") or in PHP's signed two's-complement wrap form ("-1") — the internal uint64HalfToGmp helper handles both representations for lossless assembly.

Parameters
$hiHi : string

Signed int64 as base-10 string (range [-2^63, 2^63-1]).

$hiLo : string

Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).

$loHi : string

Unsigned uint64 as base-10 string (same note as hiLo).

$loLo : string

Unsigned uint64 as base-10 string (same note as hiLo).

Tags
throws
InvalidArgumentException

If any limb string is invalid.

Return values
string

Base-10 decimal string of the assembled 256-bit signed integer.

int64ToString()

64-bit signed integer to base-10 string (SEP-51 Hyper Integer encoding).

public static int64ToString(int $value) : string
Parameters
$value : int

A PHP int (64-bit signed on 64-bit systems).

Return values
string

Base-10 decimal string representation.

ksortRecursive()

Recursively sort object property names for canonical JSON normalisation.

public static ksortRecursive(mixed $value[, int $depth = 512 ]) : mixed

Walk rules:

  • stdClass (object): cast to array, ksort by string key, recurse into values, cast back to object. This preserves the } vs [] distinction in re-encoding.
  • Indexed array (list-shape): preserve element order; recurse into elements.
  • Associative array: ksort and recurse (defensive; should not occur in stdClass-mode decode but covered for callers using this method directly on mixed structures).
  • Scalar / null: return as-is.

The $depth parameter bounds recursion depth, matching json_decode's own default of 512. Callers constructing deeply-nested PHP arrays by hand and passing them directly here (rather than via canonicalJson) should pass an appropriate depth bound. canonicalJson starts the recursion at depth=512 which aligns with json_decode's own limit.

Parameters
$value : mixed

Any decoded JSON value.

$depth : int = 512

Remaining recursion depth. Throws when it reaches zero.

Tags
throws
InvalidArgumentException

When $depth is exhausted (nesting too deep).

Return values
mixed

The same structure with all object property keys sorted recursively.

normalizeFieldAlias()

Fold a field's input alias into the field's canonical key.

public static normalizeFieldAlias(array<string|int, mixed> $value, string $canonicalKey, string $aliasKey, string $typeName) : array<string|int, mixed>

A struct field may accept a second spelling of its key on input while always emitting the canonical one. Rewriting the alias to the canonical key before any other check means the declared-key closure, the required-field check and the field read all work from the one spelling the type emits.

The two spellings name one field, so supplying both is a duplicate rather than two values: it is rejected instead of one silently winning.

Parameters
$value : array<string|int, mixed>

The decoded JSON object.

$canonicalKey : string

The key the type emits.

$aliasKey : string

The additional key accepted on input.

$typeName : string

The PHP class the object is being decoded into.

Tags
throws
InvalidArgumentException

When both spellings are present.

see
https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0051.md
Return values
array<string|int, mixed>

$value with any alias entry moved to $canonicalKey.

rejectDuplicateKeys()

Reject a JSON document in which any single object repeats a key.

public static rejectDuplicateKeys(string $json) : void

A repeated key has no SEP-0051 meaning: the document names one field twice with two values, and no rule says which one the type takes. PHP's json_decode silently keeps the last, so without this scan the first value would vanish from a document that is not valid input in the first place.

Scope is per object. The same key in sibling objects, or in an object nested inside another that already uses that key, is ordinary JSON and is accepted; only a repeat within one set of braces is a duplicate.

Keys are compared after their JSON escapes are resolved, so "a" and "a" are the same key. Escape resolution produces UTF-8 bytes, which makes "é" and a literal é the same key as well.

The walk is a single iterative pass over the raw bytes, with an explicit frame stack rather than recursion, so nesting depth cannot exhaust the PHP call stack. String tokens are consumed whole, so braces, commas and colons inside string content never disturb the structure being tracked.

This is a structural scan, not a JSON validator. On a structural fault it cannot navigate past — an unbalanced brace, an unterminated string, an unresolvable escape, nesting beyond self::DECODE_MAX_DEPTH — it stops and returns, leaving json_decode to report the syntax error. It does not inspect scalar values, so a document that is both duplicate-keyed and otherwise malformed may surface either error depending on which fault the scan reaches first; both outcomes reject the document.

Parameters
$json : string

The raw JSON document.

Tags
throws
InvalidArgumentException

When an object in $json repeats a key.

see
https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0051.md

rejectUnknownFields()

Reject a struct-object JSON input that carries keys the type does not declare.

public static rejectUnknownFields(array<string|int, mixed> $value, array<int, string> $declaredKeys, string $typeName) : void

SEP-0051 defines the JSON encoding of a struct as an object whose keys are exactly the struct's fields. A key outside that set is not part of the type's encoding, so accepting it would silently discard data the caller believes was decoded. Every declared key remains required; this method only constrains what may appear in addition to them.

The one key an object may carry beyond its declared fields is $schema, which SEP-0051 says any object SHOULD allow for tooling annotation. Callers strip it before reaching this method, so it never appears in $value.

Keys are echoed through self::safePreview because they originate in caller-supplied JSON; the reported list is capped so a document carrying many unknown keys cannot inflate the message.

Parameters
$value : array<string|int, mixed>

The decoded JSON object, $schema already removed.

$declaredKeys : array<int, string>

The type's declared field keys, in IDL order.

$typeName : string

The PHP class the object is being decoded into.

Tags
throws
InvalidArgumentException

When $value holds any key outside $declaredKeys.

see
https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0051.md

safePreview()

Return a safe, bounded preview of user-supplied input for use in exception messages.

public static safePreview(string $s[, int $max = 80 ]) : string

Embedding unbounded user input in exception messages can cause log-amplification denial-of-service when the message is logged. This helper caps the preview at $max bytes and appends "..." when the string is truncated, keeping messages informative without being exploitable.

Parameters
$s : string

The user-supplied string.

$max : int = 80

Maximum bytes before truncation (must be > 3).

Return values
string

A safe, bounded preview.

stringToInt128Parts()

Decompose a signed 128-bit integer string into hi/lo limbs.

public static stringToInt128Parts(string $value) : array{hi: string, lo: string}

Per XDR Int128Parts (verified against XdrInt128Parts.php decode path): hi is signed int64; lo is unsigned uint64.

Returns an associative array with:

  • 'hi': signed int64 base-10 string (range [-2^63, 2^63-1])
  • 'lo': unsigned uint64 base-10 string (range [0, 2^64-1])
Parameters
$value : string

Base-10 decimal string of a signed 128-bit integer.

Tags
throws
InvalidArgumentException

If $value is not a valid int128 string.

Return values
array{hi: string, lo: string}

stringToInt256Parts()

Decompose a signed 256-bit integer string into four limbs.

public static stringToInt256Parts(string $value) : array{hiHi: string, hiLo: string, loHi: string, loLo: string}

Per XDR Int256Parts (verified against XdrInt256Parts.php decode path): only hiHi is signed int64; hiLo, loHi, loLo are unsigned uint64. Returns an associative array with:

  • 'hiHi': signed int64 base-10 string (range [-2^63, 2^63-1])
  • 'hiLo': unsigned uint64 base-10 string (range [0, 2^64-1])
  • 'loHi': unsigned uint64 base-10 string (range [0, 2^64-1])
  • 'loLo': unsigned uint64 base-10 string (range [0, 2^64-1])
Parameters
$value : string

Base-10 decimal string of a signed 256-bit integer.

Tags
throws
InvalidArgumentException

If $value is not a valid int256 string.

Return values
array{hiHi: string, hiLo: string, loHi: string, loLo: string}

stringToInt64()

Parse a base-10 integer string (or a PHP int) to a 64-bit signed int.

public static stringToInt64(int|string $value) : int

Accepts int|string for compatibility with JSON producers that emit numbers for 64-bit integers in addition to the spec-required strings.

Strict validation rules:

  • Empty string is rejected.
  • Leading/trailing whitespace is rejected.
  • Scientific notation ("1e10") is rejected.
  • Decimal points ("1.0") are rejected.
  • Hex notation ("0x10") is rejected.
  • Leading "+" is rejected.
  • Only an optional leading "-" followed by one or more decimal digits is accepted.
  • Leading zeros are accepted (SEP-0051 does not forbid them in 64-bit string-encoded integers).
  • The resulting value must be in [-2^63, 2^63-1].
  • intval() is explicitly NOT used because intval("abc") silently returns 0.
Parameters
$value : int|string

The value to parse.

Tags
throws
InvalidArgumentException

On any validation failure.

Return values
int

The parsed 64-bit signed integer.

stringToUint128Parts()

Decompose an unsigned 128-bit integer string into hi/lo uint64 parts.

public static stringToUint128Parts(string $value) : array{hi: string, lo: string}

Returns an associative array with keys 'hi' and 'lo', both as base-10 strings representing unsigned uint64 values in [0, 2^64-1].

Parameters
$value : string

Base-10 decimal string of an unsigned 128-bit integer.

Tags
throws
InvalidArgumentException

If $value is not a valid uint128 string.

Return values
array{hi: string, lo: string}

stringToUint256Parts()

Decompose an unsigned 256-bit integer string into four uint64 limbs.

public static stringToUint256Parts(string $value) : array{hiHi: string, hiLo: string, loHi: string, loLo: string}

Returns an associative array with keys 'hiHi', 'hiLo', 'loHi', 'loLo', all as base-10 strings representing unsigned uint64 values in [0, 2^64-1].

Parameters
$value : string

Base-10 decimal string of an unsigned 256-bit integer.

Tags
throws
InvalidArgumentException

If $value is not a valid uint256 string.

Return values
array{hiHi: string, hiLo: string, loHi: string, loLo: string}

stringToUint64()

Parse a base-10 unsigned-integer string (or a PHP int) to a uint64 value.

public static stringToUint64(int|string $value) : int

Accepts int|string for compatibility with JSON producers that emit numbers for 64-bit integers in addition to the spec-required strings.

For uint64 values above PHP_INT_MAX (i.e. 9223372036854775808..18446744073709551615) a string must be provided; those values cannot be expressed as a PHP native int. The method uses GMP for range validation against [0, 2^64-1] and returns the value as a PHP int, reinterpreting the upper-half as a negative signed int (two's complement) — this matches how the XDR parts structs store uint64 limbs.

Strict validation: same rules as stringToInt64 except no leading minus is allowed.

Parameters
$value : int|string

The value to parse.

Tags
throws
InvalidArgumentException

On any validation failure including values < 0 or > 2^64-1.

Return values
int

The uint64 value stored as a PHP int (may be negative for values > PHP_INT_MAX).

uint128PartsToString()

Assemble an unsigned 128-bit integer from hi/lo uint64 parts and return as a base-10 string.

public static uint128PartsToString(string $hi, string $lo) : string

For unsigned 128-bit (UInt128Parts) both the hi and lo limbs are unsigned uint64 values stored as PHP signed ints (two's-complement). GMP is used to reconstruct the full value.

Parameters
$hi : string

Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).

$lo : string

Unsigned uint64 as base-10 string. May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).

Tags
throws
InvalidArgumentException

If hi or lo are not valid int64-fitting strings.

Return values
string

Base-10 decimal string of the assembled 128-bit unsigned integer.

uint256PartsToString()

Assemble an unsigned 256-bit integer from four uint64 limbs and return as a base-10 string.

public static uint256PartsToString(string $hiHi, string $hiLo, string $loHi, string $loLo) : string

Per XDR UInt256Parts all four limbs are unsigned uint64 values.

Parameters
$hiHi : string

Unsigned uint64 as base-10 string (most significant limb). May be a negative base-10 string when the uint64 value exceeds PHP_INT_MAX and is stored as a signed PHP int (the negative form is reinterpreted as the unsigned bit pattern via two's-complement).

$hiLo : string

Unsigned uint64 as base-10 string (same note as hiHi).

$loHi : string

Unsigned uint64 as base-10 string (same note as hiHi).

$loLo : string

Unsigned uint64 as base-10 string (least significant limb; same note as hiHi).

Tags
throws
InvalidArgumentException

If any limb string is invalid.

Return values
string

Base-10 decimal string of the assembled 256-bit unsigned integer.

uint64ToString()

64-bit unsigned integer to base-10 string (SEP-51 Unsigned Hyper Integer encoding).

public static uint64ToString(int $value) : string

PHP's int is a 64-bit signed integer on 64-bit systems, so the valid range for the $value parameter is [0, PHP_INT_MAX] (i.e. [0, 2^63-1]). Values in the upper uint64 range (2^63..2^64-1) cannot be represented as a PHP int without overflow; callers holding such values must use stringToUint64() with a string argument instead of this method.

Parameters
$value : int

A PHP int in [0, PHP_INT_MAX].

Tags
throws
InvalidArgumentException

If $value is negative.

Return values
string

Base-10 decimal string.

unescapeString()

Inverse of escapeString: decode a SEP-51 escaped-ASCII string back to raw bytes.

public static unescapeString(string $escaped) : string

Recognised escape sequences:

  • \0 -> 0x00
  • \t -> 0x09
  • \n -> 0x0A
  • \r -> 0x0D
  • \ -> 0x5C
  • \xNN (exactly two lowercase hex digits) -> byte 0xNN

Any other character following a backslash is rejected. Unescaped printable ASCII characters (0x20..0x7E) are returned as their byte value. The input is validated strictly so that malformed sequences do not silently produce wrong output.

Parameters
$escaped : string

The escaped-ASCII string (output of escapeString).

Tags
throws
InvalidArgumentException

On unrecognised escape sequence, truncated \x, or invalid hex digits.

Return values
string

Raw binary bytes.

wrapUnsignedToSignedInt()

Wrap an unsigned uint64 base-10 string into the matching PHP signed-int representation used by the Parts struct fields.

public static wrapUnsignedToSignedInt(string $decimal) : int

The 128-bit / 256-bit Parts wrappers store every limb as a PHP signed int (XDR's hyper / unsigned hyper map onto the same 64-bit native slot on 64-bit systems). Values in [2^63, 2^64-1] therefore land in PHP as negative integers via two's-complement subtraction. This helper performs that conversion once, with full input validation, so the generated parts-decoder bodies do not each carry their own inline closure.

Accepted input: a non-empty string of decimal digits representing an unsigned integer in [0, 2^64-1]. Out-of-range values, leading signs, empty strings, and non-digit characters are rejected.

Parameters
$decimal : string

Unsigned base-10 string in [0, 2^64-1].

Tags
throws
InvalidArgumentException

On any validation failure.

Return values
int

PHP signed int (may be negative for values above PHP_INT_MAX).

On this page

Search results