Transaction submission

 
                        submitTransaction
                     ╭────────╮
         START       │        │
           ⇓         │        │
        ┌────────────┴───┐    │
 ╭──────┤      Idle      │◀───╯
 │      └────────────────┘
 │        ▲
 │        │
 ╰────────╯
    evaluateTransaction
 

Overview

Transaction submission is pretty simple & works by submitting an already serialized and signed transaction as one single message.

In case of success, Ogmios / the node returns an empty response. Otherwise, it returns an error with some details about what went wrong. Clients must thereby know how to construct valid transactions.

Disclaimer

The transaction submission protocol is the simplest one in appearance. It nevertheless requires a quite extensive knowledge of the on-chain data-types used by Cardano. Indeed, the protocol in itself is straightforward so long as you already know how to produce and sign a transaction.

This guide doesn’t cover the creation and serialization of Cardano transactions. This is a rather vast topic and there is a handful of tools out there to help on the matter already, in particular:

  • Lucid, a TypeScript / Deno package for building transaction and managing credentials. It has direct integration into Ogmios.

  • Mesh.js, a JavaScript library providing numerous tools to easily build powerful dApps on the Cardano blockchain.

  • cardano-cli which offers another command-line interface for constructing and signing transactions.

In any case, one can always refer to the source CDDL specifications to know how to construct and serialize Cardano transactions.

Providing a more user-friendly interface with regards to transactions in Ogmios is still under consideration. Yet, since in order to handle and sign transactions, one needs some knowledge about the on-chain binary format anyway, I’ve made the (effortless) choice to only treat with already serialized blobs in Ogmios. I am open to suggestions about how this could be made better, drop me a message on Github if you have ideas!

Submitting transactions

Sending a transaction through the Cardano network requires one message using the method SubmitTx, and with a single mandatory arguments with bytes, representing a serialized signed transactions with its full witness.

Note that JSON does not support embedding raw bytes in objects. Bytes needs therefore to be base16-encoded.

{
    "jsonrpc": "2.0",
    "method": "submitTransaction",
    "params": { "transaction": { "cbor": "<base16>" } }
}

The response will indicate either a success or a failure. In case of failure, Ogmios will return a list of failures reported by the underlying node. Note that, if the transaction fails to parse, Ogmios will reply with a generic error.

Transactions in Cardano are rather complicated and there are a lot of possible validation errors that can be returned. Be sure to have a look at the API reference for an exhaustive list.

Evaluating transactions

Starting from 5.2.0, Ogmios supports a modified version of the transaction submission protocol that allows to evaluate the execution units of scripts present in a given transaction, without actually submitting the transaction. This is useful for DApp developers who wants a quick-and-easy way to measure script execution costs.

The API is purposely similar to the submitTransaction method, with a few semantic changes:

  • The transaction needs not to be fully authenticated. Key witnesses may be omitted unless they are relevant to the evaluation of scripts themselves!
  • The transaction needs not to be balanced; indeed, the evaluation does not perform a full execution of all the ledger rules. So while the transaction must be well-formed, it may be invalid with regards to phase-1 validations.
  • Execution budgets assigned to redeemers are expected to be set to zero since the goal of this endpoint is to figure out these very execution budgets.

From there, the endpoint works similarly to the submission:

{
    "jsonrpc": "2.0",
    "method": "evaluateTransaction",
    "params": { "transaction": { "cbor": "<base16>" } }
}

Successful responses include a map of redeemer pointers with the corresponding execution units. A redeemer pointer is a key composed of two parts: a redeemer entity tag and a 0-based index related to that entity. There exists 4 kinds of redeemer entities: spend (for transaction inputs), certificate (for transaction certificates), mint (for transaction monetary policies) and withdrawal (for transaction’s rewards withdrawals). The index therefore refers to the position of the script-locked entity within the set of entities in the transaction.

For example spend:0 points to the first transaction input; mint:2 would point to the 3rd policy referenced in the minting map… and so forth. Here below is a JSON example of an evaluation result:

{
  "jsonrpc": "2.0",
  "method": "evaluateTransaction",
  "result": [{
    "validator": "spend:0",
    "budget": {
      "memory": 1700,
      "cpu": 476468
    }
  }]
}

See the full API reference for details about possible errors returned from this endpoint.

If you’re using typed Plutus validators (if you don’t know what that is, then it is most likely what you’re using), keep in mind that adding or removing elements to and off your transaction will change its execution cost. Indeed, the creation of the script context passed down to on-chain validators is done as part of the on-chain validator execution. Thus, larger contexts require more execution units!

This is the case for instance when you add a change output to a transaction or, a script integrity hash. A generally good way to approach this problem is to either:

1. make sure that the transaction you evaluate is as close as possible to the final transaction; that is, create dummy change outputs and script integrity hash before evaluating and fill-in their actual value once evaluated;
2. keep some safe margin from the evaluated execution units; Execution units are relatively cheap on Cardano so, an extra 5 or 10% isn’t much and saves you in most cases a lot of hassle to cope with small differences.

Additional UTXO Set

In order to construct the validator script context, Ogmios needs to resolve transaction inputs from the Cardano blockchain. In case where a submitted transaction refers to non-existing inputs, the evaluation will fail with an UnknownInputs error. This can be an obstacle during development or, in scenarios where transactions are being prepared ahead of UTXO.

In such scenarios, Ogmios gives way to provide an additional UTXO set to be used during evaluation. Note that it will still try to resolve inputs that are known, but will use the provided UTXO set as a complement for those that are unknown or yet-to-know.

The structure of the additional UTXO set is the same as UTXO sets returned in other part of the Ogmios' API; that is, an array of [OutputReference, Output] tuples.

For example:

{
    "jsonrpc": "2.0",
    "method": "evaluateTransaction",
    "params": {
      "transaction": {
        "cbor": "<base16>",
      },
      "additionalUtxoSet": [
        [
          {
            "transaction": { "id": "97b2af6dfc6a4825e934146f424cdd6ede43ff98c355d2ae3aa95b0f70b63949" },
            "output": { "index": 3 }
          },
          {
            "address": "addr_test1qp9zjnc775anpndl0jh3w7vyy25syfezf70d",
            "value": { "lovelace": 10000000 }
          }
        ]
      ]
    }
}

Full Example

For what it’s worth, here’s an example of a transaction submission to the Cardano mainnet via Ogmios. This transaction is using dummy data and will obviously fail. It is however structurally valid, so useful to test if an integration works correctly.

const WebSocket = require('ws');
const client = new WebSocket("ws://localhost:1337");

function rpc(method, params) {
    client.send(JSON.stringify({
        jsonrpc: "2.0",
        method,
        params
    }));
}

client.once('open', () => {
    const cbor =
      "83a4008182582000000000000000000000000000000000000000000000000000"+
      "0000000000000000018282583901010101010101010101010101010101010101"+
      "0101010101010101010101010101010101010101010101010101010101010101"+
      "0101010101011a001e8480825839010202020202020202020202020202020202"+
      "0202020202020202020202020202020202020202020202020202020202020202"+
      "020202020202021a0078175c021a0001faa403191e46a1008182582001000000"+
      "000000000000000000000000000000000000000000000000000000005840d7af"+
      "60ae33d2af351411c1445c79590526990bfa73cbb3732b54ef322daa142e6884"+
      "023410f8be3c16e9bd52076f2bb36bf38dfe034a9f04658e9f56197ab80ff6";

    rpc("submitTransaction", { transaction: { cbor } });
});

client.on('message', function(msg) {
    const response = JSON.parse(msg);
    console.log(response);
    client.close();
});

Errors

Errors from the transaction submission protocol are in the range 3000-3999 and are listed below.

API Reference

The complete description of the mempool monitoring requests and responses can be found in the API reference.

Plus, test vectors are available on the repository for testing, debugging and to serve as examples.