.X files
Data structures in XDR are specified in.x files. These files only contain data structure definitions, no operations or executable code. The .x files for the XDR structures used on the Stellar Network are available on GitHub.
More About XDR
XDR is similar to tools like Protocol Buffers or Thrift. XDR provides a few important features:- It is very compact, so it can be transmitted quickly and stored with minimal disk space.
- Data encoded in XDR is reliably and predictably stored. Fields are always in the same order, which makes cryptographically signing and verifying XDR messages simple.
- XDR definitions include rich descriptions of data types and structures, which is not possible in simpler formats like JSON, TOML, or YAML.
Parsing XDR
Since XDR is a binary format and not as widely known as simpler formats like JSON, the Stellar SDKs all include tools for parsing XDR and will do so automatically when retrieving data. The XDR data is still included (encoded as a base64 string) inside the JSON in case you need direct access to it.Digging into XDR structures
Since the XDR format is a fundamental underpinning of the Stellar Network, we often find it necessary to dig into these raw binary data structures for clients to interact with things like transactions and authorization entries. It can be hard to understand because of its structure, but this section will aim to enlighten you on the ways to interact with it in a handful of popular languages. The Protocol’s schema is defined in thestellar/stellar-xdr repository, though that contains far more than what we’ll need for our purposes.
XDR’s Common Forms
In the Stellar Protocol, XDR takes a handful of different forms that are important to understand and distinguish:Basic Primitives
These are the easiest to understand for anyone familiar with a programming language and includes things like integers, strings, and arrays of bytes. These are fairly straightforward to interact with, though things can get more complicated when you have aliases. For example, you have theUint64 alias in the Stellar Protocol (defined Stellar-types.x), defined for readability instead of XDR’s native “unsigned hyper” value. You can treat it exactly as you’d expect:
BigInt or even a string type:
Uint64 in the definition for UnsignedHyper in the TypeScript definition here: curr.d.ts. In fact, this file should be your primary guide to navigating the XDR if you’re using JavaScript.
Other languages, such as Python (see stellar-sdk’s uint64.html documentation), support arbitrarily-sized integers by default, so there are no such variations:
Unions
This is where things get a little more complicated. A union type is a generic type that switches between different “arms”, each of which is a different internal type. When parsing an XDR union type, it’s important to determine which arm is being used before you try parsing what’s inside. Let’s take anSCAddress, for example. From the type definition, we can see that it’s either an account (i.e. SC_ADDRESS_TYPE_ACCOUNT) or the hash of a contract (SC_ADDRESS_TYPE_CONTRACT). Right above that definition, you can see that these are constants of the SCAddressType enum defined as 0 or 1, respectively.
In general, unions follow a particular pattern: switch on the type and match the appropriate enum value. So when you have one of these unions, you need to figure out which one it is:
.name in JS and str(...) in Python): this is marginally better for readability but worse for performance since you’re doing string comparisons. It’s very useful for logs, user-facing rendering, or other debugging, though.
Unions appear in many places throughout the XDR and they can be just one arm or a whole bunch of arms. For example, the smart contract value ScVal has 22 possible values since it’s the universal representation for a value inside of a smart contract. You can see all of them being inspected in the JavaScript utility scValToNative.
Remember, XDR is the lowest level of communication on the Stellar Network. Generally speaking, there are high-level abstractions that should help with these structures. For example, the above parsing routine is exactly the purpose of the Address.fromScAddress and Address.from_xdr_sc_address abstractions in the JavaScript and Python SDKs, respectively. This greatly simplifies your code:
Structures
Like in any C-like language, a structure is a packed object containing a bunch of fields of arbitrary types. Structures can themselves contain structures, so be sure you traverse the entire tree of fields when you’re constructing one. Let’s take, for example, theContractEvent structure. Its definition is a little complicated, so let’s break it down:
- An
ExtensionPointis a way for the protocol to be extended while maintaining backwards binary compatibility.
SorobanTransactionMeta structure is a great example of this in action: When the structure was initially added to the protocol, it just contained the details of an invocation (events and a return value). Later, we needed more details about the invocation in the resulting metadata, so SorobanTransactionMetaExt was injected into the ExtensionPoint with a V1 variant containing metadata about fees charged as a result of the invocation.
- The
contractIDshould be self-explanatory: it’s the hash of the contract ID that caused this event. It’s a pointer, though, because not all events are related to a specific contract. Some may come from the system, diagnostics, etc.
Hash: even though at the ecosystem layer we refer to contracts by their “strkey” or string representation (in the form C...), these are actually “friendlier” representations of a raw SHA-256 hash. This is common at the protocol layer: the simplest, most compact representation will always be used. There are generally SDK tools to make translating these into more user-friendly types (as demonstrated in the example below).
-
The
ContractTypeis essentially a way to differentiate which of the fields in the structure you should expect to be present and isn’t worth elaborating on. -
Finally, we have the curious
vunion field.
humanizeEvents helper method in the JavaScript SDK. Specifically, we’ll look at the details of the extractEvent subroutine, replicated here for convenience:
- By differentiating on whether or not the
contractIdreturns a value or not (it’s a pointer, remember?), we decide whether or not we want to turn the raw hash into an ecosystem-compatible contract ID. - We access the
.namefield of the event type, which turns it into a human-readable string rather than an opaque structure, just like we discussed in Unions, earlier. - We inspect deeply into the
bodymember to extract thetopicsanddatafields separately, converting each of those opaqueScValues into JavaScript-friendly equivalents.
body union we discussed: there is only the initial version (an arm with v == 0), so we can use the .value() accessor to give us the underlying value directly rather than going through the rigamarole of differentiating the different cases. Calling .v0() would have been equivalent, but the benefit here is that if a v == 1 variation is introduced that also has a topics array and a data field (i.e. it adds additional fields rather than changing the v0 structure), this code will continue to work!
There are many cases in which the different union arms share structure, and .value() lets you take advantage of that.
Record Marking / Frames in Streams
In some applications like stellar-core, when multiple XDR objects are stored sequentially in a file or stream, each object is framed using the Record Marking Standard defined in RFC 5531 Section 11. Each record is composed of one or more fragments. Each fragment begins with a 4-byte header followed by the fragment data:- Bit 31 (high bit): Set to
1if this is the last fragment of the record,0if more fragments follow. - Bits 0-30: The length in bytes of the fragment data that follows the header.
- History archives: Bucket files and checkpoint ledger files written and read by stellar-core.
- Streaming LedgerCloseMeta: The LedgerCloseMeta stream output by stellar-core.
--input stream-framed.
This overview should give you a strong baseline on understanding how to inspect XDR in your respective SDK to dig into the fields that you’re interested in. Specifics will, of course, be language dependent, but if you start at the foundation—that being the raw .x files themselves—you will get an understanding of the structure itself and should be able to access that same structure directly in your language of choice, as we’ve outlined here.