YOU ARE: A web frontend or full-stack dApp developer
YOU’RE BUILDING: An application that needs to propose transactions to a user’s wallet
THE PROBLEM TO SOLVE: How to build transactions so that the wallet can present the best possible summary to your users, and give them confidence when doing transactions with your dApp.
THE TOOL TO USE: The Transaction Manifest, along with Gateway SDK, Radix dApp Toolkit (RDT), Radix Engine Toolkit (RET)
Background
The way transactions work on blockchains today is quite limiting and confusing to users to the point of being dangerous. They aren’t designed for composability across dApps, they are inflexible, and they fundamentally don’t describe a “transaction” in the way that humans normally think of them: movements of assets between different parties.
This creates a number of serious problems on DeFi networks – perhaps the worst one being that it’s simply not possible for any wallet software to give users a clear picture of what a given transaction will do before they sign and submit it.
To fix this, Radix uses a completely new and different form of transaction. Radix transactions are built as a “manifest” of instructions that directly describe what should happen, precisely in terms of movements of assets between accounts and smart contracts. This brings a surprising range of benefits to developers and users of Radix.
But once again, perhaps the most important thing Radix’s transaction manifest solves is that it means client software like the Radix Wallet can present a UI that provides reliable and understandable summaries of transactions that tell the user what matters to them before they choose to sign and submit.
For example, a swap with a DEX might look like this, parsed and summarized directly from the transaction manifest instructions themselves – no definition of presentation from the dApp at all:
(Note: Not all transactions must be proposed to a user’s wallet as described in this article. In some cases, your dApp may have its own backend system that constructs complete transactions (rather than stubs), signs them, and submits them to the network itself. For example, your system may wish to distribute tokens to users, or update data in an oracle component; these transactions require no approval or signatures from users. This article is specifically about transactions that users themselves must approve of by signing and submitting them. This includes any transaction that withdraws tokens from a user’s account, needs a badge proof from a user, etc. – basically any typical DeFi transaction.)
The Problem
To help your users to interact directly with your on-ledger Scrypto components, your dApp must construct transaction manifests to propose to users’ Radix Wallet mobile app. This means that the choices you make in how you construct transaction manifests has an effect on the experience your users have when interacting with your dApp.
Once your dApp proposes a transaction to a user’s wallet, it belongs to the user - they choose if it describes something they want to happen before signing and submitting it. In fact, unlike on other networks, constructing the final transaction is a collaborative process between your dApp and the user’s wallet app.
Your dApp builds just part of the transaction manifest – called a “transaction manifest stub” – that describes just the essential actions to be taken for your dApp.
Then the Radix Wallet takes the stub and adds on extra manifest instructions before signing and submitting the final transaction manifest. These additional instructions take care of user-side preferences, including things like locking fees from the user’s preferred account, making multi-factor auth calls as needed for the user’s accounts, and adding transaction-level guarantees of resource amounts that the user expects to receive (via “assert” statements on resource amounts before deposits).
This is quite a powerful system, but some things may not be immediately obvious:
- What can be done in a transaction manifest?
- How to build a transaction manifest stub?
- What should or shouldn’t be included in a transaction manifest stub?
- What choices can be made in stub construction to make wallet presentation as helpful to the user as possible?
The Radix Tool for the Job
A transaction manifest is simply a string containing a sequence of instructions to be executed. Execution is “atomic”, meaning that either all instructions must complete successfully or else the entire transaction fails.
The manifest instruction set, while not a true programming language, provides a lightly bash-like grammar able to do things like call accounts and components, manipulate resources, and conduct authentication – as well as other less-used functions like publishing Scrypto packages, setting metadata, and more.
You can think of it almost like an “asset-oriented shell script” that lets you chain together movements of resources between accounts and smart components. Even a simple token transfer is a two-part chain of withdrawing from one account and depositing to another. This makes multi-dApp composability easy and natural.
You can find the complete transaction manifest specification here.
There are multiple ways of constructing the transaction manifest stubs you will send to user wallets.
For many simple transactions, your dApp frontend may simply generate the relevant instructions directly as a string, perhaps from a canned template that only replaces things like account addresses and amounts of tokens.
For more complex transaction creation, the Radix Engine Toolkit (RET) library provides manifest builder functionality, in addition to a large amount of additional utility functionality. RET is written in Rust, but offers various language-specific wrappers, such as for Typescript. RET is fairly heavy-weight, and so it is generally recommended for backend use or server-side-rendered frontends.
Once the manifest stub string is constructed, it can be submitted by a web frontend as a transaction request to the Radix Wallet using the Radix dApp Toolkit. Your dApp may also provide extra header information to the wallet with the request, such as a “message” to attach to it, which may help a user understand (and remember later) what a transaction was about.
The Recommended Use Pattern
In nearly all circumstances, your dApp should build “conforming” transaction manifest stubs. A conforming manifest is one that is built according to one of a list of known “transaction type” patterns that the Radix Wallet can identify and summarize concisely and accurately for the user.
Conforming transactions will be shown to the user with an easy-to-understand graphical presentation. Non-conforming transactions are still accepted by the Radix Wallet, but only the raw transaction manifest instructions can be shown to the user – a much less pleasant experience that you should prefer to avoid.
Practically, however, the vast majority of transactions that a dApp might want to propose to a user can easily be made conforming – even highly complex transactions interacting with multiple components.
Each conforming “transaction type” is defined by the manifest instructions that are allowed, sometimes with some limitations on order. The full list of conforming transaction types and their specifications can be found in the docs here.
The first and by far most important transaction type for dApp developers is the “general transaction”. In fact, the great majority of transactions proposed by dApps will fall into this category because it is so flexible. The other transaction types are primarily there to support very particular and more infrequently-used use cases, like Radix network staking, package deployment, updating metadata, changing account settings, and the like.
The “general transaction” type is designed to accommodate virtually any combination of the things that typically happen in virtually every dApp interaction:
- Withdrawing tokens from accounts
- Using one or more components (typically associated with a dApp definition)
- Depositing returned tokens into user accounts
- Presenting proofs of user assets as needed for component auth
And indeed, the Radix Wallet’s handling of the general transaction type summarizes the transaction visually in these same terms.
For example, a DEX swap’s transaction manifest stub might look like this:
This would be a conforming general transaction type, and the wallet would summarize it like this:
In this case, the Radix Wallet knows that the account used for the withdrawal and deposit belongs to the user. And it automatically looks up the address of the exchange component being called and sees that it has a confirmed link to the dApp Definition for “Mega Exchange”.
This pattern works even for complex chains of “composed” component calls. Imagine a transaction that:
- Withdraws some tokens from a user’s account
- Uses those tokens to take out a flash loan
- Uses the flash loan to conduct a swap on one DEX
- Conducts another swap on a second DEX to take advantage of an arbitrage opportunity
- Uses the resulting tokens to pay back the flash loan
- Contributes those tokens to a liquidity pool
- Deposits the resulting pool unit tokens to the user’s account
That would still be a conforming general transaction type, which could be summarized in terms of:
- WITHDRAWING quantity of tokens from user’s account
- USING Flash Loan dApp, DEX 1, DEX 2, Liquidity Pool dApp
- DEPOSITING quantity of pool units to user’s account
All of the other “internal” details don’t matter; the user sees what matters to them: what they are interacting with, and anything touching their accounts and assets.
The only thing that would cause a manifest to break out of the general transaction type pattern would be to use more “unusual” instructions which would matter to the user, but are difficult to show in this sort of style. And generally these more unusual instructions are best used on their own in dedicated transactions of a different type. For example, it would be strange to stake to a Radix network validator in the same transaction as a complex dApp component interaction; better to keep staking to its own transaction that conforms to a dedicated “staking” transaction type that the wallet can summarize well.
Some additional “DON’Ts”
It’s worth noting some things that should not be included in your transaction manifest stubs. Doing these things will likely cause the wallet to reject your transaction out of hand.
DON’T include fee-locking calls to user accounts in their transaction manifest stubs. While transactions must always pay the required transaction fee, the user’s wallet will add any required fee-locking call itself, according to the user’s preferences.
(Note: If you want to pay fees on behalf of a user, do so within your own component logic. The Radix Wallet will know about these locks when it runs a preview of the transaction results, and adjust its own user-side fee locking automatically.)
DON’T worry about authentication to user accounts. Once again, the Radix Wallet will handle this automatically, producing any required signatures or even adding additional calls required as part of the user’s multi-factor configuration. For example, if a transaction needs to withdraw tokens from a user’s account, simply make the withdraw call in your manifest stub; the user’s wallet will determine what auth is required to make that withdraw possible.
Some additional “DOs”
However, you should be aware of some things that you should do when building your transaction manifest stubs.
DO take care of authentication to your own components (unlike to user accounts, as mentioned just above). For example, if a method call to your component requires proof of a given badge resource held by the user, it is up to you to include the call to the correct user account to produce that proof (again, with the Radix Wallet taking care of the auth to the account to do so).
DO try to make deposits of specific quantities to accounts whenever possible. When constructing buckets in the transaction manifest for deposit to an account, you can either do so using instructions like TAKE_FROM_WORKTOP that specify the quantity of the resource or using instructions like TAKE_ALL_FROM_WORKTOP that use whatever quantity is present.
When the Radix Wallet sees a definite quantity, it shows that deposit to the user as a known amount that they will receive. However, if the quantity of a deposit is indefinite, the wallet will run a preview of the transaction and show an “estimated” quantity. Sometimes an indefinite amount is unavoidable - for example, with the return from a typical DEX transaction. But whenever possible, using a definite quantity gives your users more assurance of results (and avoids them having to think about adding their own guarantees to the transaction in the wallet).
Additional Notes
In the documentation of conforming transaction types, many types may be shown as “coming later”. At launch, the Radix Wallet supports a few of the most important types, but its support will grow with time. To start, it is believed that most dApp use cases are well-supported by the “general transaction” type, and other types will be prioritized based on feedback from the community.
Related Links
- Transaction Manifest specification docs
- “Conforming” Transaction Manifest format docs
- Radix Engine Toolkit docs
- Rust Radix Engine Toolkit on Github
- Typescript Radix Engine Toolkit on Github
- Radix dApp Toolkit docs