Look Up Transaction Results
To use the XAG Ledger effectively, you need to be able to understand transaction outcomes: did the transaction succeed? What did it accomplish? If it failed, why?
The XAG Ledger is a shared system, with all data recorded publicly and carefully, securely updated with each new ledger version. Anyone can look up the exact outcome of any transaction and read the transaction metadata to see what it did.
This document describes, at a low level, how to know why a transaction reached the outcome it did.
Prerequisites
To understand the outcome of a transaction as described in these instructions, you must:
- Know which transaction you want to understand. If you know the transaction's identifying hash, you can look it up that way. You can also look at transactions that executed in a recent ledger or the transactions that most recently affected a given account.
- Have access to a
rippled
server that provides reliable information and has the necessary history for when the transaction was submitted.- For looking up the outcomes of transactions you've recently submitted, the server you submitted through should be sufficient, as long as it maintains sync with the network during that time.
- For outcomes of older transactions, you may want to use a full-history server.
Tip: There are other ways of querying for data on transactions from the XAG Ledger, including the Data API and other exported databases, but those interfaces are non-authoritative. This document describes how to look up data using the rippled
API directly, for the most direct and authoritative results possible.
1. Get Transaction Status
Knowing whether a transaction succeeded or failed is a two-part question:
- Was the transaction included in a validated ledger?
- If so, what changes to the ledger state occurred as a result?
To know whether a transaction was included in a validated ledger, you usually need access to all the ledgers it could possibly be in. The simplest, most foolproof way to do this is to look up the transaction on a full history server. Use the tx method,account_tx method, or other response from rippled
. Look for "validated": true
to indicate that this response uses a ledger version that has been validated by consensus.
- If the result does not have
"validated": true
, then the result may be tentative and you must wait for the ledger to be validated to know if the transaction's outcome is final. - If the result does not contain the transaction in question, or returns the error
txnNotFound
, then the transaction is not in any ledger that the server has in its available history. This may or may not mean that the transaction failed, depending on whether the transaction could be in a validated ledger version that the server does not have and whether it could be included in a future validated ledger. You can constrain the range of ledgers a transaction can be in by knowing:- The earliest ledger the transaction could be in, which is the first ledger to be validated after the transaction was first submitted.
- The last ledger the transaction could be in, which is defined by the transaction's
LastLedgerSequence
field.
The following example shows a successful transaction, as returned by the tx method, which is in a validated ledger version. The order of the fields in the JSON response has been rearranged, with some parts omitted, to make it easier to understand:
{
"TransactionType": "AccountSet",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Sequence": 376,
"hash": "017DED8F5E20F0335C6F56E3D5EE7EF5F7E83FB81D2904072E665EEA69402567",
... (omitted) ...
"meta": {
"AffectedNodes": [
... (omitted) ...
],
"TransactionResult": "tesSUCCESS"
},
"ledger_index": 46447423,
"validated": true
}
This example shows an AccountSet transaction sent by the account with address rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn, using Sequence number 376. The transaction's identifying hash is 017DED8F5E20F0335C6F56E3D5EE7EF5F7E83FB81D2904072E665EEA69402567
and its result is tesSUCCESS
. The transaction was included in ledger version 46447423, which has been validated, so these results are final.
Case: Not Included in a Validated Ledger
If a transaction is not included in a validated ledger, it cannot possibly have had any effect on the shared XAG Ledger state. If the transaction's failure to be included in a ledger is final, then it cannot have any future effect, either.
If the transaction's failure is not final, it may still become included in a future validated ledger. You can use the provisional results of applying the transaction to the current open ledger as a preview of the likely effects the transaction may have in a final ledger, but those results can change due to numerous factors.
Case: Included in a Validated Ledger
If the transaction is included in a validated ledger, then the transaction metadata contains a full report of all changes that were made to the ledger state as a result of processing the transaction. The metadata's TransactionResult
field contains a transaction result code that summarizes the outcome:
- The code
tesSUCCESS
indicates that the transaction was, more or less, successful. - A
tec
-class code indicates that the transaction failed, and its only effects on the ledger state are to destroy the XAG transaction cost and possibly perform some bookkeeping like removing expired Offers - No other code can appear in any ledger.
The result code is only a summary of the transaction's outcome. To understand in more detail what the transaction did, you must read the rest of the metadata in context of the transaction's instructions and the ledger state before the transaction executed.
2. Interpret Metadata
Transaction metadata describes exactly how the transaction was applied to the ledger, including the following fields:
Field | Value | Description |
---|---|---|
AffectedNodes |
Array | List of ledger objects that were created, deleted, or modified by this transaction, and specific changes to each. |
DeliveredAmount |
Currency Amount | (May be omitted) For a partial payment, this field records the amount of currency actually delivered to the destination. To avoid errors when reading transactions, instead use the delivered_amount field, which is provided for all Payment transactions, partial or not. |
TransactionIndex |
Unsigned Integer | The transaction's position within the ledger that included it. This is zero-indexed. (For example, the value 2 means it was the 3rd transaction in that ledger.) |
TransactionResult |
String | A result code indicating whether the transaction succeeded or how it failed. |
delivered_amount |
Currency Amount | (Omitted for non-Payment transactions) The Currency Amount actually received by the Destination account. Use this field to determine how much was delivered, regardless of whether the transaction is a partial payment. See this description for details. |
This example shows the full response from step 1 above. See if you can figure out what changes it made to the ledger:
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
"Flags": 2147483648,
"LastLedgerSequence": 46447424,
"Sequence": 376,
"SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
"TransactionType": "AccountSet",
"TxnSignature": "30450221009B2910D34527F4EA1A02C375D5C38CF768386ACDE0D17CDB04C564EC819D6A2C022064F419272003AA151BB32424F42FC3DBE060C8835031A4B79B69B0275247D5F4",
"date": 608257201,
"hash": "017DED8F5E20F0335C6F56E3D5EE7EF5F7E83FB81D2904072E665EEA69402567",
"inLedger": 46447423,
"ledger_index": 46447423,
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8",
"FinalFields": {
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"AccountTxnID": "017DED8F5E20F0335C6F56E3D5EE7EF5F7E83FB81D2904072E665EEA69402567",
"Balance": "396015164",
"Domain": "6D64756F31332E636F6D",
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
"Flags": 8519680,
"MessageKey": "0000000000000000000000070000000300",
"OwnerCount": 9,
"Sequence": 377,
"TransferRate": 4294967295
},
"PreviousFields": {
"AccountTxnID": "E710CADE7FE9C26C51E8630138322D80926BE91E46D69BF2F36E6E4598D6D0CF",
"Balance": "396015176",
"Sequence": 376
},
"PreviousTxnID": "E710CADE7FE9C26C51E8630138322D80926BE91E46D69BF2F36E6E4598D6D0CF",
"PreviousTxnLgrSeq": 46447387
}
}
],
"TransactionIndex": 13,
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
Example of increasing an Account's OwnerCount
:
{
"ModifiedNode": {
"FinalFields": {
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"Balance": "9999999990",
"Flags": 0,
"OwnerCount": 1,
"Sequence": 2
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05",
"PreviousFields": {
"Balance": "10000000000",
"OwnerCount": 0,
"Sequence": 1
},
"PreviousTxnID": "B24159F8552C355D35E43623F0E5AD965ADBF034D482421529E2703904E1EC09",
"PreviousTxnLgrSeq": 16154
}
}
Many transaction types create or modify DirectoryNode objects. These objects are for bookkeeping: tracking all objects owned by an account, or all Offers to exchange currency at the same exchange rate. If the transaction created new objects in the ledger, it may need to add entries to an existing DirectoryNode object, or add another DirectoryNode object to represent another page of the directory. If the transaction removed objects from the ledger, it may delete one or more DirectoryNode objects that are no longer needed.
Example of a CreatedNode representing a new Offer Directory:
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "F60ADF645E78B69857D2E4AEC8B7742FEABC8431BD8611D099B428C3E816DF93",
"NewFields": {
"ExchangeRate": "4E11C37937E08000",
"RootIndex": "F60ADF645E78B69857D2E4AEC8B7742FEABC8431BD8611D099B428C3E816DF93",
"TakerPaysCurrency": "0000000000000000000000004254430000000000",
"TakerPaysIssuer": "5E7B112523F68D2F5E879DB4EAC51C6698A69304"
}
}
},
Other things to look for when processing transaction metadata depend on the transaction type.
Payments
A Payment transaction can represent a direct XAG-to-XAG transaction, a cross-currency payment, or a direct transaction in an issued (non-XAG) currency. Anything other than a direct XAG-to-XAG transaction can be a partial payment, including issued currency to XAG or XAG to issued currency transactions.
You should always use the delivered_amount field to see how much a payment delivered.
If the payment contains a CreatedNode
of LedgerEntryType AccountRoot
, that means the payment funded a new accountin the ledger.
Issued Currency Payments
Payments involving issued currencies are a bit more complicated.
All changes in issued currency balances are reflected in RippleState objects, which represent trust lines. An increase to one party's balance on a trust line is considered to decrease the counterparty's balance by equal amount; in the metadata, this is only recorded as a single change to the shared Balance
for the RippleState object. Whether this change is recorded as an "increase" or "decrease" depends on which account has the numerically higher address.
A single payment may go across a long path consisting of several trust lines and order books. The process of changing the balances on several trust lines to connect parties indirectly is called rippling. Depending on the issuer
specified in the transaction's Amount
field, it is also possible that the amount delivered may be split between several trust lines (RippleState
accounts) connected to the destination account.
Tip: The order that modified objects are presented in the metadata does not necessarily match the order those objects were visited while processing a payment. To better understand payment execution, it may help to reorder AffectedNodes
members to reconstruct the paths the funds took through the ledger.
Offers
An OfferCreate transaction may or may not create an object in the ledger, depending on how much was matched and whether the transaction used flags such as tfImmediateOrCancel
. Look for a CreatedNode
entry with LedgerEntryType Offer
to see if the transaction added a new Offer to the ledger's order books. For example:
{
"CreatedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "F39B13FA15AD2A345A9613934AB3B5D94828D6457CCBB51E3135B6C44AE4BC83",
"NewFields": {
"Account": "rETSmijMPXT9fnDbLADZnecxgkoJJ6iKUA",
"BookDirectory": "CA462483C85A90DB76D8903681442394D8A5E2D0FFAC259C5B0C59269BFDDB2E",
"Expiration": 608427156,
"Sequence": 1082535,
"TakerGets": {
"currency": "EUR",
"issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq",
"value": "2157.825"
},
"TakerPays": "7500000000"
}
}
}
A ModifiedNode
of type Offer
indicates an Offer that was matched and partially consumed. A single transaction can consume a large number of Offers. An Offer to trade two issued currencies might also consume Offers to trade XAG because of auto-bridging. All or part of an exchange can be auto-bridged.
A DeletedNode
of LedgerEntryType Offer
can indicate a matching Offer that was fully consumed, an Offer that was found to be expired or unfunded at the time of processing, or an Offer that was canceled as part of placing a new Offer. You can recognize a canceled Offer because the Account
that placed it is the sender of the transaction that deleted it.
Example of a deleted Offer:
{
"DeletedNode": {
"FinalFields": {
"Account": "rETSmijMPXT9fnDbLADZnecxgkoJJ6iKUA",
"BookDirectory": "CA462483C85A90DB76D8903681442394D8A5E2D0FFAC259C5B0C595EDE3E1EE9",
"BookNode": "0000000000000000",
"Expiration": 608427144,
"Flags": 0,
"OwnerNode": "0000000000000000",
"PreviousTxnID": "0CA50181C1C2A4D45E9745F69B33FA0D34E60D4636562B9D9CDA1D4E2EFD1823",
"PreviousTxnLgrSeq": 46493676,
"Sequence": 1082533,
"TakerGets": {
"currency": "EUR",
"issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq",
"value": "2157.675"
},
"TakerPays": "7500000000"
},
"LedgerEntryType": "Offer",
"LedgerIndex": "9DC99BF87F22FB957C86EE6D48407201C87FBE623B2F1BC4B950F83752B55E27"
}
}
Offers can create, delete, and modify both types of DirectoryNode objects, to keep track of who placed which Offers and which Offers are available at which exchange rates. Generally, users don't need to pay close attention to this bookkeeping.
An OfferCancel transaction may have the code tesSUCCESS
even if there was no Offer to delete. Look for a DeletedNode
of LedgerEntryType Offer
to confirm that the transaction actually deleted an Offer. If not, the Offer may already have been removed by a previous transaction, or the OfferCancel transaction may have used the wrong sequence number in the OfferSequence
field.
If an OfferCreate transaction shows a CreatedNode
of type RippleState
, that indicates that the Offer created a trust line to hold an issued currency received in the trade.
TrustSet Transactions
TrustSet transactions create, modify, or delete trust lines, which are represented as RippleState
objects. A single RippleState
object contains settings for both parties involved, including their limits, rippling settings, and more. Creating and modifying trust lines can also adjust the sender's owner reserve and owner directory.
The following example shows a new trust line, where rf1BiG... is willing to hold up to 110 USD issued by rsA2Lp...:
{
"CreatedNode": {
"LedgerEntryType": "RippleState",
"LedgerIndex": "9CA88CDEDFF9252B3DE183CE35B038F57282BC9503CDFA1923EF9A95DF0D6F7B",
"NewFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
},
"Flags": 131072,
"HighLimit": {
"currency": "USD",
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"value": "110"
},
"LowLimit": {
"currency": "USD",
"issuer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"value": "0"
}
}
}
}