Transactions

Transactions are objects that are signed by one or more accounts and are sent to the chain to interact with it.

Transactions are structured as such:

First, the transaction can import any number of types from external accounts using the import syntax.

1
import FungibleToken from 0x01

The body is declared using the transaction keyword and its contents are contained in curly braces.

Next is the body of the transaction, which first contains local variable declarations that are valid throughout the whole of the transaction.

1
transaction {
2
// transaction contents
3
let localVar: Int
4
5
...
6
}

Then, four optional main phases: Preparation, preconditions, execution, and postconditions, in that order. The preparation and execution phases are blocks of code that execute sequentially.

The following empty Cadence transaction contains no logic, but demonstrates the syntax for each phase, in the order these phases will be executed:

1
transaction {
2
prepare(signer1: AuthAccount, signer2: AuthAccount) {
3
// ...
4
}
5
6
pre {
7
// ...
8
}
9
10
execute {
11
// ...
12
}
13
14
post {
15
// ...
16
}
17
}

Although optional, each phase serves a specific purpose when executing a transaction and it is recommended that developers use these phases when creating their transactions. The following will detail the purpose of and how to use each phase.

Transaction Parameters

Transactions may declare parameters. Transaction parameters are declared like function parameters. The arguments for the transaction are passed in the sent transaction.

Transaction parameters are accessible in all phases.

1
// Declare a transaction which has one parameter named `amount`
2
// that has the type `UFix64`
3
//
4
transaction(amount: UFix64) {
5
6
}

Prepare phase

The prepare phase is used when access to the private AuthAccount object of signing accounts is required for your transaction.

Direct access to signing accounts is only possible inside the prepare phase.

For each signer of the transaction the signing account is passed as an argument to the prepare phase. For example, if the transaction has three signers, the prepare must have three parameters of type AuthAccount.

1
prepare(signer1: AuthAccount) {
2
// ...
3
}

As a best practice, only use the prepare phase to define and execute logic that requires access to the AuthAccount objects of signing accounts, and move all other logic elsewhere. Modifications to accounts can have significant implications, so keep this phase clear of unrelated logic to ensure users of your contract are able to easily read and understand logic related to their private account objects.

The prepare phase serves a similar purpose as the initializer of a contract/resource/structure.

For example, if a transaction performs a token transfer, put the withdrawal in the prepare phase, as it requires access to the account storage, but perform the deposit in the execute phase.

AuthAccount objects have the permissions to read from and write to the /storage/ and /private/ areas of the account, which cannot be directly accessed anywhere else. They also have the permission to create and delete capabilities that use these areas.

Pre Phase

The pre phase is executed after the prepare phase, and is used for checking if explicit conditions hold before executing the remainder of the transaction. A common example would be checking requisite balances before transferring tokens between accounts.

1
pre {
2
sendingAccount.balance > 0
3
}

If the pre phase throws an error, or does not return true the remainder of the transaction is not executed and it will be completely reverted.

Execute Phase

The execute phase does exactly what it says, it executes the main logic of the transaction. This phase is optional, but it is a best practice to add your main transaction logic in the section, so it is explicit.

1
execute {
2
// Invalid: Cannot access the authorized account object,
3
// as `account1` is not in scope
4
let resource <- account1.load<@Resource>(from: /storage/resource)
5
destroy resource
6
7
// Valid: Can access any account's public Account object
8
let publicAccount = getAccount(0x03)
9
}

You may not access private AuthAccount objects in the execute phase, but you may get an account's PublicAccount object, which allows reading and calling methods on objects that an account has published in the public domain of its account (resources, contract methods, etc.).

Post Phase

Statements inside of the post phase are used to verify that your transaction logic has been executed properly. It contains zero or more condition checks.

For example, a transfer transaction might ensure that the final balance has a certain value, or e.g. it was incremented by a specific amount.

1
post {
2
result.balance == 30: "Balance after transaction is incorrect!"
3
}

If any of the condition checks result in false, the transaction will fail and be completely reverted.

Only condition checks are allowed in this section. No actual computation or modification of values is allowed.

A Note about pre and post Phases

Another function of the pre and post phases is to help provide information about how the effects of a transaction on the accounts and resources involved. This is essential because users may want to verify what a transaction does before submitting it. pre and post phases provide a way to introspect transactions before they are executed.

For example, in the future the phases could be analyzed and interpreted to the user in the software they are using, e.g. "this transaction will transfer 30 tokens from A to B. The balance of A will decrease by 30 tokens and the balance of B will increase by 30 tokens."

Summary

Cadence transactions use phases to make the transaction's code / intent more readable and to provide a way for developer to separate potentially 'unsafe' account modifying code from regular transaction logic, as well as provide a way to check for error prior / after transaction execution, and abort the transaction if any are found.

The following is a brief summary of how to use the prepare, pre, execute, and post phases in a Cadence transaction.

1
transaction {
2
prepare(signer1: AuthAccount) {
3
// Access signing accounts for this transaction.
4
//
5
// Avoid logic that does not need access to signing accounts.
6
//
7
// Signing accounts can't be accessed anywhere else in the transaction.
8
}
9
10
pre {
11
// Define conditions that must be true
12
// for this transaction to execute.
13
}
14
15
execute {
16
// The main transaction logic goes here, but you can access
17
// any public information or resources published by any account.
18
}
19
20
post {
21
// Define the expected state of things
22
// as they should be after the transaction executed.
23
//
24
// Also used to provide information about what changes
25
// this transaction will make to accounts in this transaction.
26
}
27
}