Inputs and Results
Programmable transaction blocks (PTBs) operate on inputs and produce results. Inputs are the values you provide to the PTB, either objects on the network or pure values like numbers and strings. Results are the values that PTB commands produce, which subsequent commands in the same transaction can use. Together, inputs and results form the data flow through a PTB's execution.
Inputs
Input arguments to a PTB are broadly categorized as either objects or pure values. The direct implementation of these arguments is often obscured by transaction builders or SDKs. Each Input is either an object, Input::Object(ObjectArg), which contains the necessary metadata to specify the object being used, or a pure value, Input::Pure(PureArg), which contains the bytes of the value.
For object inputs, the metadata needed differs depending on the type of ownership of the object. The data for the ObjectArg enum follows:
-
If the object is owned by an address or it is immutable, then use
ObjectArg::ImmOrOwnedObject(ObjectID, SequenceNumber, ObjectDigest). The triple respectively specifies the object's ID, its sequence or version number, and the digest of the object's data. -
If an object is shared, then use
ObjectArg::SharedObject { id: ObjectID, initial_shared_version: SequenceNumber, mutable: bool }. UnlikeImmOrOwnedObject, a shared object's version and digest are determined by the network's consensus protocol. Theinitial_shared_versionis the version of the object when it was first shared, which is used by consensus when it has not yet seen a transaction with that object. While all shared objects can be mutated, themutableflag indicates whether the object is to be used mutably in this transaction. In the case where themutableflag is set tofalse, the object is read-only and the system can schedule other read-only transactions in parallel. -
If the object is owned by another object, as in it was sent to an object's ID through the
TransferObjectscommand or thesui::transfer::transferfunction, then useObjectArg::Receiving(ObjectID, SequenceNumber, ObjectDigest). The object data is the same as for theImmOrOwnedObjectcase.
For pure inputs, the only data provided is the BCS bytes, which are deserialized to construct Move values. Not all Move values can be constructed from BCS bytes. The following types are allowed to be used with pure values:
-
All primitive types:
u8,u16,u32,u64,u128,u256,bool,address. -
A string, either an ASCII string
std::ascii::Stringor UTF8 stringstd::string::String. In either case, the bytes are verified to be a valid string with the respective encoding. -
An object ID
sui::object::ID. -
A vector,
vector<T>. -
An option,
std::option::Option<T>.
For vectors and options, T must be a valid pure input type. This rule applies recursively.
Bytes are not validated until the type is specified in a command, for example in MoveCall or MakeMoveVec. This means that a given pure input could be used to instantiate Move values of several types.
Results
Each transaction command produces an array of values. The array can be empty. The type of the value can be any arbitrary Move type, so unlike inputs, the values are not limited to objects or pure values. The number of results generated and their types are specific to each transaction command:
-
MoveCall: The number of results and their types are determined by the Move function being called. Move functions that return references are not supported at this time. -
SplitCoins: Produces one or more coins from a single coin. The type of each coin issui::coin::Coin<T>where the specific coin typeTmatches the coin being split. -
Publish: Returns the upgrade capabilitysui::package::UpgradeCapfor the newly published package. -
Upgrade: Returns the upgrade receiptsui::package::UpgradeReceiptfor the upgraded package. -
TransferObjectsandMergeCoinsproduce an empty result vector.
Consider the following Move functions that create a Hero and a Sword for a game.
public struct Sword has key, store {
id: UID,
attack: u64,
}
public fun new_sword(attack: u64, ctx: &mut TxContext): Sword {
Sword {
id: object::new(ctx),
attack
}
}
public struct Hero has key, store {
id: UID,
health: u64,
stamina: u64,
}
/// Hero starts with 100 heath and 10 stamina.
public fun mint_hero(ctx: &mut TxContext): Hero {
Hero {
id: object::new(ctx),
health: 100,
stamina: 10,
}
}
There is also a specialized Move function that equips a Hero with a Sword:
/// Hero can equip a single sword.
/// Equiping a sword increases the `Hero`'s power by its attack.
public fun equip_sword(self: &mut Hero, sword: Sword) {
if (df::exists_(&self.id, b"sword".to_string())) {
abort (EAlreadyEquippedSword)
};
self.add_dof(b"sword".to_string(), sword)
}
Combine the creation of Hero, a Sword, and attaching the sword to the hero in a single transaction using TypeScript:
const tx = new Transaction();
//According to Move function mint_hero, this moveCall will return an Object of Type Hero
const hero = tx.moveCall({
target: `0x123::hero::mint_hero`,
arguments: [],
typeArguments: [],
});
//According to Move function new_sword, this moveCall will return an Object of Type Hero
const sword = tx.moveCall({
target: `0x123::hero::new_sword`,
arguments: [tx.pure.u64(10)],
typeArguments: [],
});
//According to Move function equip_sword, this moveCall expects as arguments an object of type Hero and an Object of type Sword:
tx.moveCall({
target: `0x123::hero::equip_sword`,
arguments: [hero, sword],
});