Radix Core API (1.0.0)

Download OpenAPI specification:Download

This API provides endpoints from a node for integration with the Radix ledger.

Overview

WARNING

The Core API is NOT intended to be available on the public web. It is designed to be accessed in a private network.

The Core API is separated into three:

  • The Data API is a read-only api which allows you to view and sync to the state of the ledger.
  • The Construction API allows you to construct and submit a transaction to the network.
  • The Key API allows you to use the keys managed by the node to sign transactions.

The Core API is a low level API primarily designed for network integrations such as exchanges, ledger analytics providers, or hosted ledger data dashboards where detailed ledger data is required and the integrator can be expected to run their node to provide the Core API for their own consumption.

For a higher level API, see the Gateway API.

For node monitoring, see the System API.

Rosetta

The Data API and Construction API is inspired from Rosetta API most notably:

  • Use of a JSON-Based RPC protocol on top of HTTP Post requests
  • Use of Operations, Amounts, and Identifiers as universal language to express asset movement for reading and writing

There are a few notable exceptions to note:

  • Fetching of ledger data is through a Transaction stream rather than a Block stream
  • Use of EntityIdentifier rather than AccountIdentifier
  • Use of OperationGroup rather than related_operations to express related operations
  • Construction endpoints perform coin selection on behalf of the caller. This has the unfortunate effect of not being able to support high frequency transactions from a single account. This will be addressed in future updates.
  • Construction endpoints are online rather than offline as required by Rosetta

Future versions of the api will aim towards a fully-compliant Rosetta API.

Enabling Endpoints

All endpoints are enabled when running a node with the exception of two endpoints, each of which need to be manually configured to access:

  • /transactions endpoint must be enabled with configuration api.transaction.enable=true. This is because the transactions endpoint requires additional database storage which may not be needed for users who aren't using this endpoint
  • /key/sign endpoint must be enable with configuration api.sign.enable=true. This is a potentially dangerous endpoint if accessible publicly so it must be enabled manually.

Client Code Generation

We have found success with generating clients against the api.yaml specification. See https://openapi-generator.tech/ for more details.

The OpenAPI generator only supports openapi version 3.0.0 at present, but you can change 3.1.0 to 3.0.0 in the first line of the spec without affecting generation.

Data API Flow

The Data API can be used to synchronize a full or partial view of the ledger, transaction by transaction.

Data API Flow

Construction API Flow

The Construction API can be used to construct and submit transactions to the network.

Construction API Flow

Unlike the Rosetta Construction API specification, this Construction API selects UTXOs on behalf of the caller. This has the unfortunate side effect of not being able to support high frequency transactions from a single account due to UTXO conflicts. This will be addressed in a future release.

Entities

Entities represent something that can hold resource balances and data objects. For example, a public key account is an entity which can hold token balances on behalf of a private key holder.

Entity

An Operation is an update which occurs on an Entity. There are two types of Operations:

  • A Resource Operation which is a balance change on an Entity
  • A Data Operation which is a data object update on an Entity

Operation

Structure

There are different types of entities on Radix, each of which can contain certain resources and data objects.

The following table describes which resources and data objects are allowed for a given type of entity.

Structure

Overall, there are four main types of entities on Radix:

  • Account is a regular public key account which can contain token balances and contain StakeUnits, which represents a delegated amount of stake to a specific validator. Subentities under an account-address contain assets which the account-address owns but is currently locked by the system. An account is represented by its Bech32 account address.
  • Validator is an entity representing a validating node on the network. The subentity system holds XRD tokens which have been staked to this validator. A validator is represented by its Bech32 validator address.
  • Token is an entity representing a Token Definition, represented by its Radix Resource Identifier ("RRI").
  • System is an entity representing system level data such as current epoch and round, represented by the string "system".

If an entity identifier references a sub-entity, it should be considered a separate entity to the primary entity. For example, for tracking the balance of an Account entity, any operations against an entity identifier with a non-empty entity_identifier.sub_entity should be ignored, even if the entity_identifier.address matches.

Addressing

Account addresses, Validator addresses and Token RRIs are all Bech32 encoded.

A Bech32 encoding consists of a prefix which is an ASCII human readable part "HRP", followed by the character "1", followed by an encoded data payload, encoded as 32 values (5 bits) per character.

Assuming an address is legal, you can extract its HRP as the string before the last '1'. This can be used to distinguish between Account, Validator and Token/RRI addresses.

  • Accounts have a fixed HRP for a given Radix network. This is rdx on mainnet. The data part encodes the account's public key.
  • Validators have a fixed HRP for a given Radix network. This is rv on mainnet. The data part encodes the validator's public key.
  • Tokens have an HRP of the token's (not necessarily unique) symbol, followed by a suffix. The suffix is fixed for a given Radix network, and is _rr on mainnet. As an example, the mainnet address of the native token, XRD, is xrd_rr1qy5wfsfh. The data part of the Bech32 encoding encodes the token's unique radix engine address on ledger. This radix engine address is hex 01 for XRD, and presently, hex 03 | SHA256(SHA256(signer_public_key_compressed_33_bytes | symbol_in_utf8_bytes) for user-created tokens, where | denotes the byte concatenation operator.

The Account and Validator HRPs and the Token HRP suffix for the current network should be fetched from the /network/configuration endpoint.

It likely will not be necessary to actually decode the data parts of the Bech32 encodings, but if you do need to, you should be warned that Radix addresses are not Segwit addresses. Many Bech32 libraries assume a Segwit encoding, so may fail to extract the data from a Radix address.

In particular, the Segwit encoding assumes the first 5-bit-character of the 5-bit-per-character encoded data is a witness version, and that this is followed by the witness programme bytes, encoded into 5-bit chunks, padded with zeroes if necessary. Radix encoding just encodes its data bytes into 5-bit chunks with padded zeroes - without an initial single character version prefix. In other words, the padding and interpretation of the encoded data differs between Radix and Segwit addresses.

Actions

There are further restrictions on which resource movement and data updates and how operations must be combined. The following table describes what user actions (ledger updates that can be submitted as a transaction) are available.

User Actions

  1. Transfer of any token resource (including the native token XRD) from an account to another account may be done by debiting an amount from the sending account and crediting the same amount to the receiving account. Minting and Burning of tokens may also be done if one has permissions to do so.
  2. Staking may be started by transferring XRD from an account to a prepared_stake entity with a specific validator. Once XRD is in this entity, at some point the system will move this XRD to a Validator entity and mint StakeUnits into the originating account.
  3. Unstaking may be started by transfering StakeUnits from an account to a prepared_unstake entity. Once StakeUnits is in this entity, at some point the system will destroy this StakeUnits and transfer XRD from the Validator entity into your exiting_stake entity. Once the unlocking period is over the system will move that XRD from the exiting_stake entity into your account.
  4. Validator Updates may be started by destroying the current data object and creating a new data object with the same type.
  5. New Token Definitions may be created by destroying an UnclaimedREAddr and creating a TokenData and TokenMetadata data object.

Objects

State Identifiers

The Radix network has no concept of blocks. Instead transactions are managed in a flat ordered list. A hash chain is formed from these transactions called a Transaction Accumulator. A Transaction Accumulator represents a point in time of the ledger with a valid state.

State Identifiers

The transaction accumulator along with the index forms a State Identifier.

state_version
required
integer <int64> (LongNumber)
transaction_accumulator
required
string

Accumulator hash representing all transactions which occurred up to state_version. Hex encoded.

{
  • "state_version": 46001,
  • "transaction_accumulator": "2892a2359c37ab02116d46f742c684234d5aa9658682815f8221fc0b613101e0"
}

Transactions

A Transaction is an atomic state update to the ledger. It consists of one or more OperationGroups, each of which consist of one or more Operations.

Transaction Operations

A Committed Transaction has additional information such as the State Identifier which results after committing the transaction.

A transaction which has been committed on ledger.

required
object (TransactionIdentifier)
required
object (StateIdentifier)
required
Array of objects (OperationGroup)

Transactions are split into operation groups which are roughly equivalent to ledger accounting entries where all credits have an equivalent debit amount.

required
object (CommittedTransactionMetadata)
{
  • "metadata": {
    },
  • "committed_state_identifier": {
    },
  • "transaction_identifier": {
    },
  • "operation_groups": [
    ]
}

Operation Groups

Similar to the Rosetta API, both reading and writing to the ledger use the same Address and Operation objects.

Every state change is expressed as an Operation which operates on a single Entity. Any state change which consists of two accounts (such as a token transfer) thus requires at least two Operations, one which debits the sender and one which credits the receiver.

Unlike Rosetta, operations are more explicitly linked via Operation Groups. Each Operation Group then represents a well formed accounting entry where credits and debits are equivalent (unless minting or burning occurred).

required
Array of objects (Operation)

A group of operations representing a complete state update.

metadata
object

Metadata for the operation group.

{
  • "metadata": {
    },
  • "operations": [
    ]
}

Operations

Operations operate on a single Entity and are associated with a single Amount update and/or a single Data update.

  • The amount property represents either a positive or negative change in balance of some token.
  • The data property represents either the creation or deletion of a data object.
type
required
string

The type of operation: Resource, Data, or ResourceAndData.

required
object (EntityIdentifier)
object (Substate)
object (ResourceAmount)
object (Data)
metadata
object

Metadata for the operation.

{
  • "metadata": {
    },
  • "amount": {
    },
  • "substate": {
    },
  • "entity_identifier": {
    },
  • "type": "Resource"
}

Entity Identifiers

An Entity Identifier uniquely describes an entity which may have arbitrary balances and arbitrary data objects. An Entity Identifier may further specify a Sub Address which should be treated as a separate unique entity.

The interpretation of Entity Identifiers is discussed in detail in the Structure section.

address
required
string

The top level identifier for an entity.

object (SubEntity)
{
  • "address": "rdx1qsp258zf47f288g4y47hm3plsp03370safcjg5x98e6j2h66p5we8ds8m7g33",
  • "sub_entity": {
    }
}

Amounts

A signed amount of a resource.

value
required
string (BigInteger) ^-?[0-9]+$
required
object (ResourceIdentifier)
{
  • "resource_identifier": {
    },
  • "value": "2375999999928000000000000000"
}

Resource Identifiers

A Resource Identifier uniquely describes either some token or stake units for a validator.

type
required
string

The type of resource.

rri
required
string

The Radix Resource Identifier "RRI" of the token.

{
  • "rri": "xrd_rr1qy5wfsfh",
  • "type": "Token"
}

Data Updates

A Data update is the creation or destruction of a data object.

At any given time, an address will have up to one created-but-not-deleted data object of each type.

action
required
string

Data action to take on data_object.

Enum: "CREATE" "DELETE"
required
object (DataObject)
{
  • "action": "CREATE",
  • "data_object": {
    }
}

Track XRD Balances

This example walks through how to track XRD balances across all accounts. To do this we need the following:

Get Transactions

Let's start by performing a call to /transactions with state_version of 0 and limit of 1000. This will retrieve the first 1000 transactions on ledger, including the genesis transaction.

Be sure to also match the network_identifier with the network the node is using.

{
  "network_identifier": {
    "network": "mainnet"
  },
  "state_identifier": {
    "state_version": 0
  },
  "limit": 1000
}

The corresponding java client code looks like:

TransactionsApi api = new TransactionsApi();
NetworkIdentifier networkIdentifier = new NetworkIdentifier().network("mainnet");

void main() {
  PartialStateIdentifier stateIdentifier = new PartialStateIdentifier().stateVersion(0L);

  CommittedTransactionsRequest committedTransactionsRequest = new CommittedTransactionsRequest()
    .networkIdentifier(networkIdentifier)
    .limit(1000L)
    .stateIdentifier(stateIdentifier);

  CommittedTransactionsResponse response = api.transactionsPost(committedTransactionsRequest);
}

Get XRD Balance changes

Let's now expand our code to extract all XRD balance changes.

To extract account token balance changes in a transaction:

  1. Flatmap to operations from all operation groups
  2. Filter for operations which concern the XRD token, that is:
    1. amount exists
    2. amount.resource_identifier.type is Token
    3. rri of the token is xrd_rr1qy5wfsfh
  3. Group by entity_identifier and sum each balance_change

This gives us the change in XRD balance in that entity due to the transaction, in units of 10^(-18).

This forms the basis of tracking account balances over time.

TransactionsApi api = new TransactionsApi();
NetworkIdentifier networkIdentifier = new NetworkIdentifier().network("mainnet");

/**
 * Returns true if the operation contains an XRD balance change, otherwise returns false
 */
boolean isXrd(Operation operation) {
  if (operation.getAmount() == null) {
    return false;
  }
  ResourceIdentifier identifier = op.getAmount().getResourceIdentifier();
  if (!(identifier instanceof TokenResourceIdentifier)) {
    return false;
  }
  TokenResourceIdentifier tokenResourceIdentifier = (TokenResourceIdentifier) identifier;
  return tokenResourceIdentifier.getRri().equals("xrd_rr1qy5wfsfh");
}

/**
 * Returns the XRD balance change which occured on every entity from a set
 * of operation groups.
 */
Map<EntityIdentifier, BigInteger> operationGroupsToBalanceChanges(Stream<OperationGroup> operationGroups) {
  return operationGroups
    .flatMap(group -> group.getOperations().stream())
    .filter(this::isXrd)
    .collect(Collectors.groupingBy(
      Operation::getEntityIdentifier,
      Collectors.mapping(
        op -> new BigInteger(op.getAmount().getValue()),
        Collectors.reducing(BigInteger.ZERO, BigInteger::add)
      )
    ));
}

void main() {
  PartialStateIdentifier stateIdentifier = new PartialStateIdentifier().stateVersion(0L);

  CommittedTransactionsRequest committedTransactionsRequest = new CommittedTransactionsRequest()
    .networkIdentifier(networkIdentifier)
    .limit(1000L)
    .stateIdentifier(stateIdentifier);

  CommittedTransactionsResponse response = api.transactionsPost(committedTransactionsRequest);

  // Extract out operation groups
  Stream<OperationGroup> operationGroups = response.getTransactions().stream()
    .flatMap(txn -> txn.getOperationGroups().stream());

  Map<EntityIdentifier, BigInteger> balanceChanges = operationGroupsToBalanceChanges(operationGroups);
}

Update Balance Store

Now that we can compute balance change we can now start updating some balance store.

In this example we simply use a hash map from entity to balance. In your system you may store these changes in a persistent database.

TransactionsApi api = new TransactionsApi();
NetworkIdentifier networkIdentifier = new NetworkIdentifier().network("mainnet");

/**
 * Our balance store
 */
Map<EntityIdentifier, BigInteger> balances = new HashMap<>();

/**
 * Returns true if the operation contains an XRD balance change, otherwise returns false
 */
boolean isXrd(Operation operation) {
  if (operation.getAmount() == null) {
    return false;
  }
  ResourceIdentifier identifier = op.getAmount().getResourceIdentifier();
  if (!(identifier instanceof TokenResourceIdentifier)) {
    return false;
  }
  TokenResourceIdentifier tokenResourceIdentifier = (TokenResourceIdentifier) identifier;
  return tokenResourceIdentifier.getRri().equals("xrd_rr1qy5wfsfh");
}

/**
 * Returns the XRD balance change which occured on every entity from a set
 * of operation groups.
 */
Map<EntityIdentifier, BigInteger> operationGroupsToBalanceChanges(Stream<OperationGroup> operationGroups) {
  return operationGroups
    .flatMap(group -> group.getOperations().stream())
    .filter(this::isXrd)
    .collect(Collectors.groupingBy(
      Operation::getEntityIdentifier,
      Collectors.mapping(
        op -> new BigInteger(op.getAmount().getValue()),
        Collectors.reducing(BigInteger.ZERO, BigInteger::add)
      )
    ));
}

/**
 * Updates the balance store.
 */
void updateStore(Map<EntityIdentifier, BigInteger> balanceChanges) {
  balanceChanges.forEach((identifier, value) -> balances.merge(identifier, value, BigInteger::add));
}

void main() throws ApiException {
  PartialStateIdentifier stateIdentifier = new PartialStateIdentifier().stateVersion(0L);

  CommittedTransactionsRequest committedTransactionsRequest = new CommittedTransactionsRequest()
    .networkIdentifier(networkIdentifier)
    .limit(1000L)
    .stateIdentifier(stateIdentifier);

  CommittedTransactionsResponse response = api.transactionsPost(committedTransactionsRequest);

  // Extract out operation groups
  Stream<OperationGroup> operationGroups = response.getTransactions().stream()
    .flatMap(txn -> txn.getOperationGroups().stream());

  Map<EntityIdentifier, BigInteger> balanceChanges = operationGroupsToBalanceChanges(operationGroups);
  updateStore(balanceChanges);
}

Continual Sync

So far we've only been requesting for the initial 1000 transactions on ledger. We will now want to continually sync as the ledger progresses.

To do this, we will keep our own current stateVersion and update it with the latest stateVersion we've seen. On a persistent database, it will be important to update the stateVersion and ledger updates atomically so that the sync process can handle process crashes gracefully and restart in a consistent manner.

We can now add continually check for updates on ledger and keep our own state up to date. This can easily look something like a cronjob process which periodically updates a local database.

TransactionsApi api = new TransactionsApi();
NetworkIdentifier networkIdentifier = new NetworkIdentifier().network("mainnet");

/**
 * Our balance store
 */
long currentStateVersion = 0;
Map<EntityIdentifier, BigInteger> balances = new HashMap<>();

/**
 * Returns true if the operation contains an XRD balance change, otherwise returns false
 */
boolean isXrd(Operation operation) {
  if (operation.getAmount() == null) {
    return false;
  }
  ResourceIdentifier identifier = op.getAmount().getResourceIdentifier();
  if (!(identifier instanceof TokenResourceIdentifier)) {
    return false;
  }
  TokenResourceIdentifier tokenResourceIdentifier = (TokenResourceIdentifier) identifier;
  return tokenResourceIdentifier.getRri().equals("xrd_rr1qy5wfsfh");
}

/**
 * Returns the XRD balance change which occured on every entity from a set
 * of operation groups.
 */
Map<EntityIdentifier, BigInteger> operationGroupsToBalanceChanges(Stream<OperationGroup> operationGroups) {
  return operationGroups
    .flatMap(group -> group.getOperations().stream())
    .filter(this::isXrd)
    .collect(Collectors.groupingBy(
      Operation::getEntityIdentifier,
      Collectors.mapping(
        op -> new BigInteger(op.getAmount().getValue()),
        Collectors.reducing(BigInteger.ZERO, BigInteger::add)
      )
    ));
}

/**
 * Retrieves the current state version
 */
long loadStateVersion() {
  return this.currentStateVersion;
}

/**
 * Updates the balance store.
 */
void updateStore(long currentStateVersion, long nextStateVersion, Map<EntityIdentifier, BigInteger> balanceChanges) {
  // Sanity check, in case we have multiple updater to our store
  assert(this.currentStateVersion == currentStateVersion);

  this.currentStateVersion = nextStateVersion;
  balanceChanges.forEach((identifier, value) -> balances.merge(identifier, value, BigInteger::add));
}

/**
 * Requests for new transactions from a node and then updates the balance store
 */
void update() throws ApiException {
  long stateVersion = loadStateVersion();
  PartialStateIdentifier stateIdentifier = new PartialStateIdentifier().stateVersion(stateVersion);

  CommittedTransactionsRequest committedTransactionsRequest = new CommittedTransactionsRequest()
    .networkIdentifier(networkIdentifier)
    .limit(1000L)
    .stateIdentifier(stateIdentifier);

  CommittedTransactionsResponse response = api.transactionsPost(committedTransactionsRequest);

  // Extract out operation groups
  Stream<OperationGroup> operationGroups = response.getTransactions().stream()
    .flatMap(txn -> txn.getOperationGroups().stream());
  Map<EntityIdentifier, BigInteger> balanceChanges = operationGroupsToBalanceChanges(operationGroups);
  long nextStateVersion = stateVersion + txns.size();

  // Update stateVersion and balances, on a database these must be updated atomically together
  updateStore(stateVersion, nextStateVersion, balanceChanges);
}

void main() throws Exception {
  while (true) {
    update();
    Thread.sleep(1000L);
  }
}

Track All Balances

Building off of the Track XRD Balances example, we can simply remove the filter on only tracking XRD and build a complete mapping of assets owned by every entity Entity -> Resource -> Amount.

TransactionsApi api = new TransactionsApi();
NetworkIdentifier networkIdentifier = new NetworkIdentifier().network("mainnet");

/**
 * Our balance store
 */
long currentStateVersion = 0;
Map<EntityIdentifier, Map<ResourceIdentifier, BigInteger>> balances = new HashMap<>();

/**
 * Returns the token balance change which occured on every entity from a set
 * of operation groups.
 */
Map<EntityIdentifier, Map<ResourceIdentifier, BigInteger>> operationGroupsToBalanceChanges(Stream<OperationGroup> operationGroups) {
  return operationGroups
    .flatMap(group -> group.getOperations().stream())
    .filter(operation -> operation.getAmount() != null)
    .collect(Collectors.groupingBy(
      Operation::getEntityIdentifier,
      Collectors.groupingBy(
        op -> op.getAmount().getResourceIdentifier(),
        Collectors.mapping(
          op -> new BigInteger(op.getAmount().getValue()),
          Collectors.reducing(BigInteger.ZERO, BigInteger::add)
        )
      )
    )
  );
}

/**
 * Retrieves the current state version
 */
long loadStateVersion() {
  return this.currentStateVersion;
}

/**
 * Updates the balance store.
 */
void updateStore(long currentStateVersion, long nextStateVersion, Map<EntityIdentifier, Map<ResourceIdentifier, BigInteger>> balanceChanges) {
  // Sanity check, in case we have multiple updater to our store
  assert(this.currentStateVersion == currentStateVersion);

  this.currentStateVersion = nextStateVersion;
  balanceChanges.forEach((identifier, balanceMap) -> {
    balanceMap.forEach((resource, value) ->
      balances.merge(identifier, Map.of(resource, value), (b0, b1) ->
        Stream.concat(b0.entrySet().stream(), b1.entrySet().stream()).collect(
          Collectors.groupingBy(
            Map.Entry::getKey,
            Collectors.mapping(Map.Entry::getValue, Collectors.reducing(BigInteger.ZERO, BigInteger::add))
          )
        )
      )
    );
  });
}


/**
 * Requests for new transactions from a node and then updates the balance store
 */
void update() throws ApiException {
  long stateVersion = loadStateVersion();
  PartialStateIdentifier stateIdentifier = new PartialStateIdentifier().stateVersion(stateVersion);

  CommittedTransactionsRequest committedTransactionsRequest = new CommittedTransactionsRequest()
    .networkIdentifier(networkIdentifier)
    .limit(1000L)
    .stateIdentifier(stateIdentifier);

  CommittedTransactionsResponse response = api.transactionsPost(committedTransactionsRequest);

  // Extract out operation groups
  Stream<OperationGroup> operationGroups = response.getTransactions().stream()
    .flatMap(txn -> txn.getOperationGroups().stream());

  Map<EntityIdentifier, Map<ResourceIdentifier, BigInteger>> balanceChanges = operationGroupsToBalanceChanges(operationGroups);
  // Update stateVersion and balances, on a database these must be updated atomically together
  updateStore(stateVersion, nextStateVersion, balanceChanges);
}

void main() throws Exception {
  while (true) {
    update();
    Thread.sleep(1000L);
  }
}

Advanced

Filtering to just account balances

The above examples pull through the balances for any Entity, but typically you'll just want to pull down usable balances in an account - that is, balances against an account entity.

To do this, we can check filter entity_identifier to those which have an address starting rdx1, and no sub_entity. We filter out sub-entities because these are separate entities/balances to the core account, used by the system for staking.

For more information, and to support non-mainnet addresses, see the Structure and Addressing sections.

Supporting networks other than mainnet

See the Addressing section for details on determining non-mainnet addresses.

Tracking UTXOs

The operation.substate.substate_identifier.identifier gives a hex-encoded identifier for the UTXO used in that transaction. If operation.substate.substate_operation is BOOTUP, the UTXO was created, and if SHUTDOWN, it was spent.

This can be used to track live UTXOs.

Consistency checks

Before saving a copy of a transaction to an index, you should check that the parent accumulator matches the accumulator you have on record. Whilst we don't anticipate a need to rollback the ledger, this accumulator check could be used to detect this (and can be used to rollback an index to the point where the accumulators agree).

If you wish to also check the consistency of this accumulator, the following should be true:

post_transaction_state_accumulator = SHA256(SHA256(pre_transaction_state_accumulator | transaction_identifier_hash))

Where | is the byte concatenation operator. Note that this uses two rounds of SHA256. The first maps from 64 bytes to 32 bytes, the second from 32 bytes to 32 bytes.

Timestamps

If you care about the timestamps that various transactions were committed, you can track the RoundData data object under the system entity.

Then, timestamp in RoundData gives a UNIX timestamp in milliseconds, derived from votes of each validator of what the current wall clock time is. It is not guaranteed to be increasing and should only be used for convenience and not as an accurate representation of the actual time of commit.

Send XRD

Let's now go through an example of how to transfer XRD using the API.

For the purpose of our example, let's say we want to:

TRANSFER 10.5 XRD FROM rdx1qspacch6qjqy7awspx304sev3n4em302en25jd87yrh4hp47grr692cm0kv88 TO rdx1qsp258zf47f288g4y47hm3plsp03370safcjg5x98e6j2h66p5we8ds8m7g33

Entity Identifiers

First we need to specify the entities identifiers to be used. In our case, we have two entity identifiers, one for the sender and one for the receiver.

Sender Entity Identifier:

EntityIdentifier sender() {
  return new EntityIdentifier()
    .address("rdx1qspacch6qjqy7awspx304sev3n4em302en25jd87yrh4hp47grr692cm0kv88");
}

Receiver Entity Identifier:

EntityIdentifier receiver() {
  return new EntityIdentifier()
    .address("rdx1qsp258zf47f288g4y47hm3plsp03370safcjg5x98e6j2h66p5we8ds8m7g33");
}

Resource Identifiers

Next we will need the Resource Identifier for the XRD Token which will be transferred. XRD is Radix ledger's native token, used for staking and to pay fees. It is the only token allowed with the symbol xrd, and has a reserved radix engine address of 1 (01 in hex).

On mainnet, its RRI is xrd_rr1qy5wfsfh and it's resource identifier is:

TokenResourceIdentifier token() {
  return new TokenResourceIdentifier()
    .rri("xrd_rr1qy5wfsfh")
    .type("Token");
}

Amounts

Given our resource identifier we can now create resource balance changes. This will be expressed as a Resource Amount. All amounts are expressed as 10^-18 subunits. Thus, since we are transferring 1.5 XRD this translates to a value of 1500000000000000000 or the following amounts:

Sender Amount:

Amount senderAmount() {
  return new Amount()
    .resourceIdentifier(token())
    .value("-1500000000000000000");
}

Receiver Amount:

Amount receiverAmount() {
  return new Amount()
    .resourceIdentifier(token())
    .value("1500000000000000000");
}

Operations

We now combine our amounts with our Entity Identifiers to create Operations. "Resource" is the type of our Operation since we are manipulating resource balances.

Sender Operation:

Operation senderOperation() {
  return new Operation()
    .entityIdentifier(sender())
    .amount(senderAmount())
    .type("Resource");
}

Receiver Operation:

Operation receiverOperation() {
  return new Operation()
    .entityIdentifier(receiver())
    .amount(receiverAmount())
    .type("Resource");
}

Operation Group

We then combine operations into a group. Note that the sender operation must be first.

OperationGroup operationGroup() {
  return new OperationGroup()
    .addOperationsItem(senderOperation())
    .addOperationsItem(receiverOperation());
}

Fee Payer

The last thing we need to add is to specify the fee payer account entity identifier. In this case it will be the same account as the sender.

We can now also add the network_identifier and with this structure we can submit this to /construction/build. This network_identifier needs to match the network identifier of the node.

ConstructionBuildRequest constructionBuildRequest() {
  return new ConstructionBuildRequest()
    .addOperationsGroupItem(operationGroup())
    .feePayer(sender())
    .networkIdentifier(new NetworkIdentifier().network("mainnet"));
}

void main() {
  ConstructionApi api = new ConstructionApi();
  ConstructionBuildRequest request = constructionBuildRequest();
  ConstructionBuildResponse response = api.constructionBuildPost(request);
}

Unsigned Transaction

After submitting to /construction/build, we can take the unsigned_transaction and parse it through /construction/parse.

ConstructionApi api = new ConstructionApi();

ConstructionParseResponse sendParseRequest(String unsignedTransactionHex) {
  ConstructionParseRequest request = new ConstructionParseRequest()
    .unsignedTransaction(unsignedTransactionHex);
  return api.constructionParsePost(request);
}

void main() {
  ConstructionBuildRequest request = constructionBuildRequest();
  ConstructionBuildResponse response = api.constructionBuildPost(request);
  String unsignedTransactionHex = response.getUnsignedTransaction();
  ConstructionParseResponse response = sendParseRequest(unsigendTransactionHex);
  System.out.println(response);
}

The response in json would look like:

{
  "metadata": {
    "fee": {
      "resource_identifier": {
        "rri": "xrd_rr1qy5wfsfh",
        "type": "Token"
      },
      "value": "72000000000000000"
    }
  },
  "operation_groups": [
    {
      "operations": [
        {
          "amount": {
            "resource_identifier": {
              "rri": "xrd_rr1qy5wfsfh",
              "type": "Token"
            },
            "value": "-96000000000000000000000000"
          },
          "substate": {
            "substate_identifier": {
              "identifier": "4db58b950fcf446140dd945c6adfd06daa0b520f7eddf0467583d96babff579d00000002"
            },
            "substate_operation": "SHUTDOWN"
          },
          "entity_identifier": {
            "address": "rdx1qspacch6qjqy7awspx304sev3n4em302en25jd87yrh4hp47grr692cm0kv88"
          },
          "type": "Resource"
        },
        {
          "metadata": {
            "substate_data_hex": "06000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab010000000000000000000000000000000000000000004f68ca6c8d0d7e082c0000"
          },
          "amount": {
            "resource_identifier": {
              "rri": "xrd_rr1qy5wfsfh",
              "type": "Token"
            },
            "value": "95999999928000000000000000"
          },
          "substate": {
            "substate_identifier": {
              "identifier": "65f61dba3ed40a438c4694e8947f73530c1c6ca6b8115371c05c6565f9fb6a4900000000"
            },
            "substate_operation": "BOOTUP"
          },
          "entity_identifier": {
            "address": "rdx1qspacch6qjqy7awspx304sev3n4em302en25jd87yrh4hp47grr692cm0kv88"
          },
          "type": "Resource"
        }
      ]
    },
    {
      "operations": [
        {
          "amount": {
            "resource_identifier": {
              "rri": "xrd_rr1qy5wfsfh",
              "type": "Token"
            },
            "value": "-95999999928000000000000000"
          },
          "substate": {
            "substate_identifier": {
              "identifier": "65f61dba3ed40a438c4694e8947f73530c1c6ca6b8115371c05c6565f9fb6a4900000000"
            },
            "substate_operation": "SHUTDOWN"
          },
          "entity_identifier": {
            "address": "rdx1qspacch6qjqy7awspx304sev3n4em302en25jd87yrh4hp47grr692cm0kv88"
          },
          "type": "Resource"
        },
        {
          "metadata": {
            "substate_data_hex": "06000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab010000000000000000000000000000000000000000004f68ca57bbfb708d160000"
          },
          "amount": {
            "resource_identifier": {
              "rri": "xrd_rr1qy5wfsfh",
              "type": "Token"
            },
            "value": "95999998428000000000000000"
          },
          "substate": {
            "substate_identifier": {
              "identifier": "65f61dba3ed40a438c4694e8947f73530c1c6ca6b8115371c05c6565f9fb6a4900000001"
            },
            "substate_operation": "BOOTUP"
          },
          "entity_identifier": {
            "address": "rdx1qspacch6qjqy7awspx304sev3n4em302en25jd87yrh4hp47grr692cm0kv88"
          },
          "type": "Resource"
        },
        {
          "metadata": {
            "substate_data_hex": "06000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b60100000000000000000000000000000000000000000000000014d1120d7b160000"
          },
          "amount": {
            "resource_identifier": {
              "rri": "xrd_rr1qy5wfsfh",
              "type": "Token"
            },
            "value": "1500000000000000000"
          },
          "substate": {
            "substate_identifier": {
              "identifier": "65f61dba3ed40a438c4694e8947f73530c1c6ca6b8115371c05c6565f9fb6a4900000002"
            },
            "substate_operation": "BOOTUP"
          },
          "entity_identifier": {
            "address": "rdx1qsp258zf47f288g4y47hm3plsp03370safcjg5x98e6j2h66p5we8ds8m7g33"
          },
          "type": "Resource"
        }
      ]
    }
  ]
}

A couple of things to note above:

  • There are two operation groups now as opposed to one, the first operation group contains the operations for the fee, which should be paid by the fee_payer passed into /construction/build
  • The second operation group contains more than the two original operations. What occurred here is the node selected specific UTXOs to destroy and create (specified by the substate property) to perform the intent of the operations initially passed in. That is, the balance changes of each entity should still be equivalent to the original operations.

Signed Transaction

Once payload_to_sign has been signed, the original payload along with signature can be sent to /construction/finalize in order to get a signed_transaction. This can then be submitted to construction/submit. You have successfully submitted an XRD transfer transaction!

ConstructionApi api = new ConstructionApi();

void main() {
  ConstructionBuildRequest request = constructionBuildRequest();
  ConstructionBuildResponse response = api.constructionBuildPost(request);
  String unsignedTransactionHex = response.getUnsignedTransaction();
  String payloadToSignHex = response.getPayloadToSign();

  Signature signature = sign(payloadToSignHex);

  ConstructionFinalizeRequest finalizeRequest = new ConstructionFinalizeRequest()
    .unsignedTransaction(unsignedTransactionHex)
    .signature(signature)
    .networkIdentifier(new NetworkIdentifier().network("mainnet"));
  ConstructionFinalizeResponse finalizeResponse = api.constructionFinalizePost(finalizeRequest);

  String signedTransactionHex = finalizeResponse.getSignedTransaction();
  ConstructionSubmitRequest submitRequest = new ConstructionSubmitRequest()
    .networkIdentifier(new NetworkIdentifier().network("mainnet"))
    .signedTransaction(signedTransactionHex);
  ConstructionSubmitResponse submitResponse = api.constructionSubmitPost(submitRequest);
}

Network

Get info about the node and network.

Get Network Configuration

Returns the network configuration of the network the node is connected to.

Request
Request Body schema: application/json
required
object (NetworkConfigurationRequest)
Responses
200

Network Configuration

500

An unexpected error

post/network/configuration
Request samples
application/json
{ }
Response samples
application/json
{
  • "network_identifier": {
    },
  • "bech32_human_readable_parts": {
    }
}

Get Network Status

Returns the current state and status of the node's copy of the ledger. If the node is syncing, the current_state_X responses may be behind the global ledger.

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
Responses
200

Network Status

500

An Unexpected Error

post/network/status
Request samples
application/json
{
  • "network_identifier": {
    }
}
Response samples
application/json
{
  • "pre_genesis_state_identifier": {
    },
  • "current_state_timestamp": 1627452363772,
  • "current_state_identifier": {
    },
  • "current_state_epoch": 1,
  • "current_state_round": 321991,
  • "genesis_state_identifier": {
    },
  • "node_identifiers": {
    },
  • "peers": [ ]
}

Entity

Get info about an entity.

Get Entity Information

Gets the balances and data objects at an entity at the current state of the ledger.

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
required
object (EntityIdentifier)
Responses
200

Entity Balances and Data

500

Unexpected error

post/entity
Request samples
application/json
{
  • "network_identifier": {
    },
  • "entity_identifier": {
    }
}
Response samples
application/json
{
  • "balances": [
    ],
  • "data_objects": [ ]
}

Mempool

Get info about the mempool

Get Mempool Transactions

Gets the transaction identifiers in the mempool

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
Responses
200

Mempool Transaction Identifiers

500

Unexpected error

post/mempool
Request samples
application/json
{
  • "network_identifier": {
    }
}
Response samples
application/json
{
  • "transaction_identifiers": [
    ]
}

Get Mempool Transaction

Gets the transaction from the mempool

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
required
object (TransactionIdentifier)
Responses
200

Mempool Transaction Identifiers

500

Unexpected error

post/mempool/transaction
Request samples
application/json
{
  • "network_identifier": {
    },
  • "transaction_identifier": {
    }
}
Response samples
application/json
{
  • "transaction": {
    }
}

Transactions

Get the stream of committed transactions.

Get Committed Transactions

Returns an ordered sublist of committed transactions. This endpoint is designed for lite clients to sync with the state of the ledger.

The example response demonstrates a transfer transaction.

There is a more detailed worked example of reading this endpoint in the examples section.

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
required
object (PartialStateIdentifier)
limit
integer <int64> (LongNumber)
Responses
200

Sublist of Committed Transactions

500

An Unexpected Error

post/transactions
Request samples
application/json
{
  • "network_identifier": {
    },
  • "state_identifier": {
    },
  • "limit": 1
}
Response samples
application/json
{
  • "state_identifier": {
    },
  • "transactions": [
    ]
}

Construction

Construct a transaction for submission.

Derive Entity Identifier

Returns the entity identifier for an account, validator, or token given a public key

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
required
object (PublicKey)
required
object (ConstructionDeriveRequestMetadata)
Responses
200

Entity Identifier

post/construction/derive
Request samples
application/json
{
  • "network_identifier": {
    },
  • "public_key": {
    },
  • "metadata": {
    }
}
Response samples
application/json
{
  • "entity_identifier": {
    }
}

Build Transaction

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
required
Array of objects (OperationGroup)

Operation groups which describe the intent of the request.

required
object (EntityIdentifier)
message
string

An optional message payload encoded in hex with the Radix message encoding scheme.

disable_resource_allocate_and_destroy
boolean

Disallow minting and burning of tokens (except for fees). Useful for verification of transactions without needing to fetch additional substate data, such as when verifying transactions in an offline environment.

Responses
200

An unsigned transaction

500

Unexpected error

post/construction/build
Request samples
application/json
{
  • "network_identifier": {
    },
  • "fee_payer": {
    },
  • "operation_groups": [
    ]
}
Response samples
application/json
{
  • "unsigned_transaction": "07ef71a9d6c63444fce6abd2df8fab2755cfb51f6794e578f60d99337193811842000000020100210000000000000000000000000000000000000000000000000000ffcb9e57d4000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329a636aa8c02c00000008000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329a636aa8c02bfa2402004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b60100000000000000000000000000000000000000000000000000000000000005dc00",
  • "payload_to_sign": "06f82577392151638e059f31b16e52b056358ff9b7b72bedef21d701dc3ffa0f"
}

Parse Transaction

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
transaction
required
string

Hex encoded transaction to parse.

signed
required
boolean

Whether the transaction is signed or not. If not signed, parsing will skip authorization checks.

Responses
200

An unsigned transaction

500

Unexpected error

post/construction/parse
Request samples
application/json
{
  • "network_identifier": {
    },
  • "transaction": "0d000107ef71a9d6c63444fce6abd2df8fab2755cfb51f6794e578f60d9933719381184200000002010021000000000000000000000000000000000000000000000000000101ed50bab1800002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329a6148f65d4e80000008000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329a6148f65d4e7f6a02004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000000000000000000000000009600",
  • "signed": false
}
Response samples
application/json
{
  • "metadata": {
    },
  • "operation_groups": [
    ]
}

Finalize Transaction

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
unsigned_transaction
required
string

Hex encoded unsigned transaction.

required
object (Signature)
Responses
200

An unsigned transaction

500

Unexpected error

post/construction/finalize
Request samples
application/json
{
  • "network_identifier": {
    },
  • "unsigned_transaction": "0d000107ef71a9d6c63444fce6abd2df8fab2755cfb51f6794e578f60d9933719381184200000002010021000000000000000000000000000000000000000000000000000101ed50bab1800002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329a6148f65d4e80000008000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329a6148f65d4e7f6a02004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000000000000000000000000009600",
  • "signature": {
    }
}
Response samples
application/json
{
  • "signed_transaction": "07030e7094728c8d065c5db696977696bea9094f67bcfd4c021f99ec784e24023b0000000c0100210000000000000000000000000000000000000000000000000000ffcb9e57d4000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007c13bc4b1c16827082c00000008000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007ad6192165e31dff02c000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329b63364718000000000b015584aed8375f30b22a2203b77dbe15e5dc0a3618fb45ea30ee54a6ebe0054b673a471ad2214b7bd06c4228083643b57e095787c9fb01443e1c3d6890d28f60cf"
}

Get Transaction Hash

Get the transaction identifier of a signed transaction

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
signed_transaction
required
string

Hex encoded signed transaction

Responses
200

An unsigned transaction

500

Unexpected error

post/construction/hash
Request samples
application/json
{
  • "network_identifier": {
    },
  • "signed_transaction": "07030e7094728c8d065c5db696977696bea9094f67bcfd4c021f99ec784e24023b0000000c0100210000000000000000000000000000000000000000000000000000ffcb9e57d4000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007c13bc4b1c16827082c00000008000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007ad6192165e31dff02c000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329b63364718000000000b015584aed8375f30b22a2203b77dbe15e5dc0a3618fb45ea30ee54a6ebe0054b673a471ad2214b7bd06c4228083643b57e095787c9fb01443e1c3d6890d28f60cf"
}
Response samples
application/json
{
  • "transaction_identifier": {
    }
}

Submit Transaction

Submit a transaction to the mempool

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
signed_transaction
required
string

Hex encoded signed transaction to be submitted.

Responses
200

An unsigned transaction

500

Unexpected error

post/construction/submit
Request samples
application/json
{
  • "network_identifier": {
    },
  • "signed_transaction": "07030e7094728c8d065c5db696977696bea9094f67bcfd4c021f99ec784e24023b0000000c0100210000000000000000000000000000000000000000000000000000ffcb9e57d4000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007c13bc4b1c16827082c00000008000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007ad6192165e31dff02c000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329b63364718000000000b015584aed8375f30b22a2203b77dbe15e5dc0a3618fb45ea30ee54a6ebe0054b673a471ad2214b7bd06c4228083643b57e095787c9fb01443e1c3d6890d28f60cf"
}
Response samples
application/json
{
  • "transaction_identifier": {
    },
  • "duplicate": false
}

Key

Sign a transaction

Get public keys

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
Responses
200

The node's public keys

500

Unexpected error

post/key/list
Request samples
application/json
{
  • "network_identifier": {
    }
}
Response samples
application/json
{
  • "public_keys": [
    ]
}

Sign transaction

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
unsigned_transaction
required
string

Byte array of the unsigned transaction encoded in hex.

required
object (PublicKey)
Responses
200

An unsigned transaction

500

Unexpected error

post/key/sign
Request samples
application/json
{
  • "network_identifier": {
    },
  • "unsigned_transaction": "string",
  • "public_key": {
    }
}
Response samples
application/json
{
  • "signed_transaction": "07030e7094728c8d065c5db696977696bea9094f67bcfd4c021f99ec784e24023b0000000c0100210000000000000000000000000000000000000000000000000000ffcb9e57d4000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007c13bc4b1c16827082c00000008000002004506000402aa1c49af92a39d15257d7dc43f805f18f9f0ea712450c53e75255f5a0d1d93b601000000000000000000000000000000000000000007ad6192165e31dff02c000002004506000403dc62fa04804f75d009a2fac32c8ceb9dc5eaccd54934fe20ef5b86be40c7a2ab0100000000000000000000000000000000000000000013da329b63364718000000000b015584aed8375f30b22a2203b77dbe15e5dc0a3618fb45ea30ee54a6ebe0054b673a471ad2214b7bd06c4228083643b57e095787c9fb01443e1c3d6890d28f60cf"
}

Vote for the candidate fork (if present)

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
Responses
200

Submitted vote transaction information

500

Unexpected error

post/key/vote
Request samples
application/json
{
  • "network_identifier": {
    }
}
Response samples
application/json
{
  • "transaction_identifier": {
    },
  • "duplicate": false
}

Withdraw the vote for the candidate fork

Request
Request Body schema: application/json
required
required
object (NetworkIdentifier)
Responses
200

Submitted vote withdrawal transaction information

500

Unexpected error

post/key/withdraw-vote
Request samples
application/json
{
  • "network_identifier": {
    }
}
Response samples
application/json
{
  • "transaction_identifier": {
    },
  • "duplicate": false
}