RADIX WikiRADIX Wiki

Overview

Pre-authorizations — the user-facing name for subintents — allow "a partial transaction to be authorized by one actor such that it can then become a part of a larger atomic transaction", which another actor then submits to the network (Radix Docs). They shipped to mainnet with the Cuttlefish protocol update on 18 December 2024, alongside the Transaction V2 format that carries them.

The problem they solve is a familiar one in decentralized finance. When two parties want to trade, lend, or settle with each other, each has to commit funds before knowing whether the other side will follow through. The conventional fix is an escrow contract or a custodial order book: a third party holds the assets while the match is arranged. Subintents remove that intermediary. Each party signs only their own half of the interaction, and the halves are combined into one transaction that either executes completely or not at all — so no one ever hands custody to anyone.

This is atomic composability extended across signers rather than across components. A single Radix transaction was already all-or-nothing; Transaction V2 makes the authorization of that transaction divisible among several people who never have to trust each other, or be online at the same time.

The Intent Tree

A Transaction V2 transaction is a tree. At the root is a single transaction intent — the part that locks the fee, is notarized, and is what the network actually accepts into its mempool. Hanging beneath it are subintents, each carrying its own manifest and its own signatures.

Execution is explicitly parent-driven. A subintent does not begin running when the transaction starts; it runs only when its parent yields to it. The parent may pass buckets of resources at the moment of yielding, and those arrive on the subintent's starting worktop. Every subintent manifest must finish with a YIELD_TO_PARENT instruction handing control back, and may include intermediate yields where it needs to run logic before and after interacting with the rest of the transaction.

Two structural rules follow from this design and are worth stating plainly:

  • Subintents cannot lock fees. Fee payment belongs to the transaction intent at the root. This is what makes delegated-fee patterns possible — the signer of a subintent need not hold any XRD at all.
  • The Radix Wallet currently requires that pre-authorizations have no children of their own. All interaction with other subintents must route through the parent (Radix Docs). The engine's intent tree is more general than what the wallet will presently sign.

The recommended manifest shape is to withdraw and yield buckets first, then take deposits at the end. That ordering leaves the maximum liquidity available to the rest of the transaction while it is being assembled.

The Pre-Authorization Flow

The documented flow has four parts. It parallels the ordinary transaction flow, but splits construction away from submission — in the dApp toolkit the two are initiated by sendTransaction and sendPreAuthorizationRequest respectively.

  1. Request. A dApp front-end sends the wallet a subintent manifest stub, an expiry schedule, and an optional message. The stub must not contain fee locks.
  2. Review and signing. The wallet shows a pre-authorization review. If the subintent is self-contained, the user gets a preview-style review and can add their own guarantees; otherwise, a subintent with the GeneralSubintent classification displays statically-computable bounds on account withdrawals and deposits. On signing, the wallet returns a hex-encoded SignedPartialTransaction. Aside from possibly adding access controller calls to reach the relevant accounts, the wallet does not alter the manifest.
  3. Propagation. The front-end relays the signed partial transaction to a back-end that can build it into a transaction — the dApp's own service, or an external matcher. This step happens entirely off-network: subintent aggregation is out-of-band, and the mempool only ever operates on complete transactions.
  4. Construction and submission. The aggregator finds or creates the other intents, assembles the transaction with a V2 transaction builder, previews it, notarizes it, and submits it.

Because the aggregator is an ordinary off-ledger service rather than protocol infrastructure, a subintent author who wants to constrain who may assemble their transaction adds a VERIFY_PARENT instruction to the manifest — a check that the parent satisfies a given access rule. Some aggregators require it so that only their own service can be used.

Expiry, Bounds, and What the Signer Is Guaranteed

A signed subintent is a standing offer, so it needs a deadline. The Radix dApp Toolkit exposes two forms of expiry on a pre-authorization request: setExpiration('afterDelay', seconds), measured from the moment the user signs, and setExpiration('atTime', unixTimestamp) for an exact cut-off.

A dApp may also set an optional SubintentHeader to bound the subintent more tightly:

FieldMeaning
startEpochInclusiveFirst epoch in which the subintent may be included
endEpochExclusiveEpoch at which it stops being includable
minProposerTimestampInclusiveEarliest proposer timestamp (optional)
maxProposerTimestampExclusiveLatest proposer timestamp (optional)
intentDiscriminatorValue distinguishing otherwise-identical subintents

Expiry and epoch bounds limit when a subintent can be used. What limits what it can do is the manifest itself. The signer's protection comes from ASSERT_ instructions written into the stub, which fail the whole transaction unless the signer ends up with at least the resources they expected. The documentation is direct about where the responsibility sits: the dApp developer will likely need to tweak those assertions so the displayed bounds match what the user expects.

This is a meaningful shift in what a signature means. In an ordinary transaction the user reviews the complete thing they are signing. In a pre-authorization they sign a fragment whose surrounding context does not exist yet, and they are protected not by seeing the final transaction but by the assertions and atomicity that constrain it. It is the same trade-off discussed under blind signing and transaction manifests, applied one level up.

Use Cases

The Radix documentation works through four patterns, which between them cover most of what the primitive is for.

Delegated fees

A dApp wants to pay network fees on a user's behalf so the user need not hold XRD. The user signs a subintent that interacts with the dApp; the dApp decompiles it to confirm it does exactly what was expected, wraps it in a transaction intent that locks fees from the dApp's own account, previews, signs, and submits. This is the clearest illustration of why subintents cannot lock fees themselves.

User badge deposit

An account can be configured to reject deposits of unfamiliar resources, which blocks a dApp from simply sending a user a badge. Instead the dApp proposes a subintent that receives the badge at the start of execution and deposits it to the user's own account — the user authorizes their own deposit. A VERIFY_PARENT can be added so the user knows only the dApp itself can use the manifest.

Intent-based partial trades

A user signs one side of a trade — withdraw, optionally interact with a component, yield the buckets, deposit what comes back — and passes it to an aggregation network that finds the counterparty. Anthic is the worked example in the documentation: a DEX front-end builds an Anthic-compatible subintent stub, sends it to the wallet, then submits the signed partial transaction to the Anthic Trade API, which matches it against market makers and assembles the finished transaction. The same shape underlies the fixed-rate peer-to-peer lending pattern, where a lender's offer and a borrower's collateral are two subintents combined at match time.

Coordinated purchase

Two friends want to buy tickets on the condition that they either both get one or neither does. A dApp proposes a separate subintent to each and combines the results into one transaction that must commit atomically. No escrow, no refund logic, no partial outcome to unwind.

Limitations

  • Matching is not a protocol service. Subintent aggregation happens off-network. At the Cuttlefish launch there was no general-purpose subintent aggregator or solver — only purpose-built matchers such as Anthic and dApps acting as their own aggregator. A dApp that wants intent-based matching has to choose or run one.
  • Tooling arrived unevenly. The V2 partial-transaction builders shipped in the Rust radix-transactions crate 1.3.0+ and in the UniFFI Radix Engine Toolkits; the TypeScript Radix Engine Toolkit did not support Transaction V2 at Cuttlefish launch.
  • Nesting is wallet-limited. The wallet will not sign a pre-authorization that has children of its own, so multi-level intent trees are not reachable through the standard wallet flow today.
  • Liveness is not guaranteed. A signed subintent obliges nobody to complete it. If no counterparty or aggregator picks it up, it simply expires — safely, but with nothing having happened.