Transaction Manifest

The transaction model used with Radix Engine v2 is dramatically different from the transaction model used currently on the Radix Olympia mainnet. This new transaction manifest model will not become the model used for transactions on the Radix mainnet until the Babylon release.

Introduction

A transaction manifest is the Radix way of building transactions. It makes it possible to compose multiple actions to be executed atomically by describing a sequence of component calls and movements of resources between components. In short, full atomic composability becomes possible directly in transactions.

Transaction manifests can also describe the use of badges for authorization to components, payment of transaction fees, and checks on resources amounts to provide guaranteed results for the user.

Transaction manifests are human-readable so that developers or client software (such as a new Radix Wallet currently in development) can understand what they are signing. When it’s time to submit, the transaction manifest is translated into a binary representation and cryptographically signed to create a final transaction that may be efficiently sent to the network and processed by the Radix Engine.

Radix Transaction Layer

Transaction manifests orchestrate the movement of resources between components. This includes accounts, which are also components (that only their owner may withdraw from). This is done through a sequence of instructions using a special instruction set created specifically for this purpose (transaction manifests do not use Scrypto). Radix Engine processes these instructions in order, and if any step fails for any reason, the entire transaction fails and none of the steps are committed to the ledger on the Radix network. This is what is meant by the transaction being "atomic".

Execution of a given transaction can be thought of as happening at its own "layer", above any components that are called during the transaction. This layer has some special features that make transaction manifests quite powerful.

The Worktop

The most common instruction in a transaction manifest is a component call. Each call to a component can include data and buckets of resources, and each component may then return resources.

The transaction layer itself must include a way of managing resources between component calls. For this, we introduce the worktop. Each transaction has a worktop that is a place that resources may be held during the transaction execution. Resources returned from component calls are automatically put onto the worktop. From there, the manifest may specify that resources on the worktop be put into buckets so that those buckets may be passed as arguments to other component calls.

The manifest may also use ASSERT commands to check the contents of the worktop, causing the entire transaction to fail if not enough of the checked resource is present. This is useful to guarantee results of a transaction, even if you may be unsure of what a component may return.

Of course we know that all resources must be in a vault by the end of any transaction, so the transaction manifest creator must ensure that no resources are left hanging around the worktop or in buckets by the end of the manifest’s steps.

The Authorization Zone

Another feature of the transaction layer is the authorization zone. This is somewhat similar to the worktop, but is used specifically for authorization. Rather than holding resources, the authorization zone holds Proofs. A Proof is a special object that proves the possession of a given resource or resources. When a component is called by the transaction manifest, the Proofs in that transaction’s authorization zone are automatically passed to that component method’s authorization rules to be checked to determine if that method may be called or not.

These Proofs will include those that are automatically added to the authorization zone from "virtual badges" that are produced by signatures to the transaction. This, for example, is how you are able to call the withdraw method on your account component.

For more about Proofs and authorization, please see Access Control.

Fee payment

To run a transaction on the Radix network, you have to pay a fee depending on the number of instructions you are calling and the permanent storage used. You do this by locking XRD from a vault that will then be used to pay for the fee calculated at the end of a transaction. To lock a fee, you must call a method that essentially calls .lock_fee(amount) on a vault containing XRD. There is a method that do this on the Account component:

CALL_METHOD ComponentAddress("[account_component_address]") "lock_fee" Decimal("[amount]");

For testing purposes, on the local simulator, you can also call the lock_fee on the System component (address component_sim1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqgyhcl2) so the fee payment comes from the system rather than your account.

You can read more about fees here.

How to Create a Transaction Manifest?

There are two ways of creating transaction manifests for testing and interacting with your components in the Radix simulator:

Use the Simulator Tools

The resim CLI supports basic method (or function) invocation transactions. Starting from v0.3.0, any transaction resim supports can be outputted as a transaction manifest without committing it to the simulated ledger.

To turn on the feature, add --manifest <path> flag to your resim call-function command (or any other command that would create a transaction).

For example,

resim call-function package_sim1qy2f2f63ddpw8x40m55ytpru428vk66edhdpvz60ljqsj5y7v8 Hello instantiate_hello --manifest out.rtm

will produce something like:

CALL_METHOD ComponentAddress("component_sim1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqgyhcl2") "lock_fee" Decimal("10"); (1)
CALL_FUNCTION PackageAddress("package_sim1qy2f2f63ddpw8x40m55ytpru428vk66edhdpvz60ljqsj5y7v8") "Hello" "instantiate_hello"; (2)
CALL_METHOD_WITH_ALL_RESOURCES ComponentAddress("account_sim1qv2am46henyuuyjfccz5zkmup843e3wthc4y59e9plyqv88gl4") "deposit_batch"; (3)
1 To pay for the transaction fees, your transaction must contain a call to a method locking fees. For testing purposes, this example calls the lock_fee method on the System component. In practice, you would call the lock_fee method on your account’s component.
2 This line calls the instantiate_hello function on the Hello blueprint
3 This line takes all the resources present on the worktop, puts them in buckets and send them back into your account. In this case its not important since no resources are returned from the instantiate_hello function.

You can edit the auto-generated manifest to modify or add any other instructions you like.

Write it from Scratch

Once you’re familiar enough with the transaction manifest syntax, you can write a manifest from scratch.

Web front-end applications will of course need to construct their own transaction manifests to be passed to the Radix Wallet for signature and submission.

Please read the full specifications to learn more.

Example: Simple Token Transfer

Let’s start with a simple, and very common, example of a transaction manifest: transferring tokens from one account to another. Remember that accounts are components, so even a token transfer happens by calling methods on the accounts of the sender and recipient.

We’ll start by using our account’s withdraw method to get 10 XRD from our account, which will return the XRD to the worktop. We’ll take it from the worktop and put it in a bucket, which we’ll then pass to the deposit method on the recipient’s account.

Let’s take a look at the transaction manifest, with some placeholders for some arguments for readability:

# lock fees to pay for the transaction
CALL_METHOD ComponentAddress("component_sim1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqgyhcl2") "lock_fee" Decimal("10");

# withdraw 10 XRD from account, which goes to the worktop
CALL_METHOD ComponentAddress("[your_account_address]") "withdraw_by_amount" Decimal("10") ResourceAddress("[xrd_address]");

# take 10 XRD from the worktop and put it in a bucket
TAKE_FROM_WORKTOP_BY_AMOUNT Decimal("10") ResourceAddress("[xrd_address]") Bucket("xrd");

# deposit the bucket of XRD into the recipient's account
CALL_METHOD ComponentAddress("[recipient_account_address]") "deposit" Bucket("xrd");

If my account doesn’t actually have 10 XRD in it, then the transaction will fail on that first line.

In fact we can make this transaction manifest even simpler by using the CALL_METHOD_WITH_ALL_RESOURCES command, which sweeps up everything on the worktop and allows it to be deposited to a deposit_batch method on the account.

# lock fees to pay for the transaction
CALL_METHOD ComponentAddress("component_sim1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqgyhcl2") "lock_fee" Decimal("10");

# withdraw 10 XRD from account, which goes to the worktop
CALL_METHOD ComponentAddress("[your_account_address]") "withdraw_by_amount" Decimal("10") ResourceAddress("[xrd_address]");

# take everything from the worktop (currently 10 XRD) and deposit it into the recipient's account
CALL_METHOD_WITH_ALL_RESOURCES ComponentAddress("[recipient_account_address]") "deposit_batch";

Example: "Composed" Multi-Component Transaction

Now let’s get a little more advanced by composing together calls to multiple components, using the resources returned from each to feed into the next.

We’ll take 1 XRD from our account, call another component to buy a gumball, and then swap that gumball in a Radiswap component for some other resources (we won’t check on what we got back; we’ll just deposit whatever it is back into our account).

First, we’ll use some placeholders for readability:

# lock fees to pay for the transaction
CALL_METHOD ComponentAddress("component_sim1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqgyhcl2") "lock_fee" Decimal("10");

# withdraw 1 XRD from account, which goes to the worktop
CALL_METHOD ComponentAddress("[account_address]") "withdraw" Decimal("1") ResourceAddress("[xrd_address]");

# take 1 XRD from the worktop and pass it to the gumball machine
TAKE_FROM_WORKTOP_BY_AMOUNT Decimal("1") ResourceAddress("[xrd_address]") Bucket("xrd");
CALL_METHOD ComponentAddress("[gumball_machine_address]") "buy_gumball" Bucket("xrd");

# take all returned gumballs and do a radiswap
TAKE_FROM_WORKTOP ResourceAddress("[gum_resource_address]") Bucket("gumballs");
CALL_METHOD ComponentAddress("[radiswap_component_address]") "swap" Bucket("gumballs");

# deposit everything into my account
CALL_METHOD_WITH_ALL_RESOURCES ComponentAddress("[account_address]") "deposit_batch";

If you want to see the same transaction with some actual addresses from a local run, here you go:

# lock fees to pay for the transaction
CALL_METHOD ComponentAddress("component_sim1qgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqgyhcl2") "lock_fee" Decimal("10");

# withdraw 1 XRD from account, which goes to the worktop
CALL_METHOD ComponentAddress("[account_address]") "withdraw_by_amount" Decimal("1") ResourceAddress("[xrd_resource_address]");

# take 1 XRD from the worktop and pass it to the gumball machine
TAKE_FROM_WORKTOP_BY_AMOUNT Decimal("1") ResourceAddress("[xrd_resource_address]]") Bucket("xrd");
CALL_METHOD ComponentAddress("[gumball_component_address]") "buy_gumball" Bucket("xrd");

# take all returned gumballs and do a radiswap
TAKE_FROM_WORKTOP ResourceAddress("[gumball_resource_address]") Bucket("gumballs");
CALL_METHOD ComponentAddress("[radiswap_component_address]") "swap" Bucket("gumballs");

# deposit everything into my account
CALL_METHOD_WITH_ALL_RESOURCES ComponentAddress("[account_address]") "deposit_batch";