Skip to main content

Common mistakes

Connecting to a Block Explorer or External Node

Any implementation of the Rosetta APIs must connect exclusively to a running blockchain node in the same Dockerfile. It is not ok to connect to some external service that preprocesses blockchain data or to a blockchain node running elsewhere.

Populating Operation.Amount with Balance instead of Balance Changes

Some blockchains prefer to surface balance changes by returning the balance of an account after an operation is applied instead of returning how much an operation changes an account's balance.

In Rosetta, the Operation.Amount.Value field should always be populated with the balance change (or "delta"), not the new balance of an account after an operation is applied (sometimes called "state streaming").

If you would still like to surface the balance of an account after an operation is applied because it is expected by users of the blockchain you are implementing, you should add it to the Operation.Metadata field (along with any other useful metadata you want to return). It is not required to surface the balance of an account after an operation is applied.

Incorrect Example: Operation Amount is New Balance

In the following example, account A and account B each have 100 CURR prior to the transaction. In the transaction, A transfers 50 CURR to B.

Operation.Amount.Value is populated with the new balance of each account instead of the balance change that occurs in the transaction.

{
"transaction_identifier": {
"hash": "TX_HASH"
},
"operations": [
{
"operation_identifier": {
"index": 0
},
"type": "transfer",
"status": "success",
"account": {
"address": "A"
},
"amount": {
"value": "50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
},
{
"operation_identifier": {
"index": 1
},
"related_operations": [
{
"index": 0
}
],
"type": "transfer",
"status": "success",
"account": {
"address": "B"
},
"amount": {
"value": "150",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
}
]
}

Correct Example: Operation Amount is Balance Change

In the following example, account A and account B each have 100 CURR prior to the transaction. In the transaction, A transfers 50 CURR to B.

In this example, Operation.Amount.Value is populated with the balance change of each account involved in the transaction instead of the new balance of each account involved in the transaction. The new balance of each account involved in the transaction is added to Operation.Metadata (optional).

{
"transaction_identifier": {
"hash": "TX_HASH"
},
"operations": [
{
"operation_identifier": {
"index": 0
},
"type": "transfer",
"status": "success",
"account": {
"address": "A"
},
"amount": {
"value": "-50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
},
"metadata": {
"new_balance": "50"
}
},
{
"operation_identifier": {
"index": 1
},
"related_operations": [
{
"index": 0
}
],
"type": "transfer",
"status": "success",
"account": {
"address": "B"
},
"amount": {
"value": "50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
},
"metadata": {
"new_balance": "150"
}
}
]
}

Using Swagger/OpenAPI 2 Tooling

In 2017, the OpenAPI Initiative published the OpenAPI 3.0 standard as a successor to the Swagger Specification. The Swagger Specification was then renamed to OpenAPI 2.0.

Not all tools developed for Swagger/OpenAPI 2.0 are compatible with this OpenAPI 3.0 format. Be sure to double check that any code you utilize to power your implementation is compatible.

OpenAPI 3.0 Rosetta API Spec

Malformed Genesis Block

When populating the genesis block, it may be unclear which index and hash to use in the ParentBlockIdentifier. It is recommended to use the the genesis BlockIdentifier for both block_identifier and parent_block_identifier.

Incorrect Example: Negative Block Index

{
"block": {
"block_identifier": {
"index": 0,
"hash": "0x3a065000ab4183C6BF581Dc1E55A605455FC6D61"
},
"parent_block_identifier": {
"index": -1,
"hash": "0x3a065000ab4183C6BF581Dc1E55A605455FC6D61"
},
"timestamp": 1535390993
}
}

Incorrect Example: No ParentBlockIdentifier

{
"block": {
"block_identifier": {
"index": 0,
"hash": "0x3a065000ab4183C6BF581Dc1E55A605455FC6D61"
},
"timestamp": 1535390993
}
}

Correct Example

{
"block": {
"block_identifier": {
"index": 0,
"hash": "0x3a065000ab4183C6BF581Dc1E55A605455FC6D61"
},
"parent_block_identifier": {
"index": 0,
"hash": "0x3a065000ab4183C6BF581Dc1E55A605455FC6D61"
},
"timestamp": 1535390993
}
}

Return Empty Value or Null for Optional Fields

When a field is optional and not populated, it should not be in the returned object. Otherwise, clients could attempt to interpret the returned value.

Incorrect Example: SubAccountIdentifier Empty Address

{
"account_identifier": {
"address": "0x5be1bfc0b1f01f32178d46abf70bb5ff5c4e425a",
"sub_account": {
"address": ""
}
}
}

Correct Example

{
"account_identifier": {
"address": "0x5be1bfc0b1f01f32178d46abf70bb5ff5c4e425a"
}
}

Return Transactions to Fetch in Block Transactions

If transactions must be retrieved using the /block/transaction endpoint, they must be returned in other_transactions. Recall, this endpoint is used when the /block endpoint does not populate all transactions (it is optional).

Incorrect Example: Populate Transactions

{
"block": {
"block_identifier": {
"index": 0,
"hash": "0xa10ea72d4dfc3f09ff89d8a864069115208e75c77f210900054eecf2626ad610"
},
"parent_block_identifier": {
"index": -1,
"hash": "0xa10ea72d4dfc3f09ff89d8a864069115208e75c77f210900054eecf2626ad610"
},
"timestamp": 1535390993,
"transactions": [
{
"transaction_identifier": {
"hash": "0xa10ea72d4dfc3f09ff89d8a864069115208e75c77f210900054eecf2626ad610"
},
"operations": null
}
]
}
}

Correct Example

{
"block": {
"block_identifier": {
"index": 1000,
"hash": "0x445b5c8af787a213f9b0d59c3e2d22a33502d2d030802f1abb4a495f3fc6dad9"
},
"parent_block_identifier": {
"index": 999,
"hash": "0xaa1df7734723058084a7f88b337291a881642d822a5edd7e2ec6c0c8cd029845"
},
"timestamp": 1535390993,
"other_transactions": [
{
"hash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
}
]
}
}

Incorrect Transaction Submission Response

Incorrect Status

The /construction/submit endpoint must only return a 200 status if the submitted transaction could be included in the mempool. If a submission could not be included (ex: invalid signature), it must return an error.

Blocking on Submission

Any request to the /construction/submit endpoint must not block on the transaction being included in a block. It must return immediately with an indication of whether or not the transaction was included in the mempool. Handling transaction rebroadcast is a requirement placed on the client.

Unretrievable AccountIdentifier

Any AccountIdentifier returned in a block could be used as an input to an /account/balance request. Ensure your Rosetta Server can gracefully handle any request containing an AccountIdentifier found in a block.

Populating Amount without Populating Account in an Operation

In Rosetta, all operations that contain an amount must also contain a populated account (that can be retrieved using the /account/balance endpoint). "Dangling" balance changes (unattributable balance changes) can create insurmountable data or auditing challenges for some institutional blockchain users, which could prevent them from integrating with your implementation.

It is important to note that this does not mean that every operation with a populated account must have an amount. In fact, this is allowed and is a popular pattern to represent on-chain activity that does not involve a balance change (where operation metadata is populated with on-chain data).

Incorrect Example: Account-less Amount

{
"transaction_identifier": {
"hash": "TX_HASH"
},
"operations": [
{
"operation_identifier": {
"index": 0
},
"type": "create",
"status": "success",
"amount": {
"value": "-50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
}
]
}

Correct Example

{
"transaction_identifier": {
"hash": "TX_HASH"
},
"operations": [
{
"operation_identifier": {
"index": 0
},
"type": "create",
"status": "success",
"account": {
"address": "A"
},
"amount": {
"value": "-50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
}
]
}

Using Only Positive Amounts in Operations

Rosetta uses signed operation amounts. Any account balance decrease must be represented by a negative amount and any balance increase must be represented by a positive amount. Because of this design decision, users of the Rosetta API do not need to parse network-specific types to understand how to interpret an amount.

Incorrect Example: Operation Type Specifies Signedness

{
"transaction_identifier": {
"hash": "TX_HASH"
},
"operations": [
{
"operation_identifier": {
"index": 0
},
"type": "send_coins",
"status": "success",
"account": {
"address": "A"
},
"amount": {
"value": "50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
},
{
"operation_identifier": {
"index": 1
},
"related_operations": [
{
"index": 0
}
],
"type": "receive_coins",
"status": "success",
"account": {
"address": "B"
},
"amount": {
"value": "50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
}
]
}

Correct Example

{
"transaction_identifier": {
"hash": "TX_HASH"
},
"operations": [
{
"operation_identifier": {
"index": 0
},
"type": "transfer",
"status": "success",
"account": {
"address": "A"
},
"amount": {
"value": "-50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
},
{
"operation_identifier": {
"index": 1
},
"related_operations": [
{
"index": 0
}
],
"type": "transfer",
"status": "success",
"account": {
"address": "B"
},
"amount": {
"value": "50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
}
]
}

Including Transactions in Blocks Before Execution

Transactions in some blockchains are executed (i.e. change state) after the block they were included in (instead of in the block). In other words, each transaction in block N is applied by block N + 1 instead of in N. In Rosetta, transactions must be included in the block in which they are executed (regardless of when they are first included in a block).

If this applies to your implemenation, it is highly recommended to indicate where the transaction first appeared in the transaction metadata:

{
"transaction_identifier": {
"hash": "TX_HASH"
},
"operations": [
{
"operation_identifier": {
"index": 0
},
"type": "transfer",
"status": "success",
"account": {
"address": "A"
},
"amount": {
"value": "-50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
},
{
"operation_identifier": {
"index": 1
},
"related_operations": [
{
"index": 0
}
],
"type": "transfer",
"status": "success",
"account": {
"address": "B"
},
"amount": {
"value": "50",
"currency": {
"symbol": "CURR",
"decimals": 0
}
}
}
],
"metadata": {
"included_in":{
"index": 100,
"hash": "block 100"
}
}
}

Was this helpful?