From b9a66e963684c521c93b8c4147c999131eb5646e Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Thu, 19 Mar 2026 16:53:24 -0700 Subject: [PATCH 1/9] initial prog --- docs/content/concepts/sui-move-concepts.mdx | 2 +- .../guides/developer/dev-cheat-sheet.mdx | 2 +- .../objects/object-ownership/immutable.mdx | 24 +++- .../transactions/ptbs/prog-txn-blocks.mdx | 107 +++++++++++++++++- 4 files changed, 122 insertions(+), 13 deletions(-) diff --git a/docs/content/concepts/sui-move-concepts.mdx b/docs/content/concepts/sui-move-concepts.mdx index 4396e3a21e07..2e0c94e3a924 100644 --- a/docs/content/concepts/sui-move-concepts.mdx +++ b/docs/content/concepts/sui-move-concepts.mdx @@ -66,4 +66,4 @@ In addition to this Sui-specific use case, there are other rules and restriction - `entry` functions can only return types with the `drop` ability. -- `entry` functions can only take objects as inputs if they weren't used as inputs in any non-`entry` functions in the same PTB. \ No newline at end of file +- Non-public `entry` functions (private or `public(package)`) have restrictions on arguments that are entangled with hot potato values in a PTB. See [Non-public entry function restrictions](/guides/developer/transactions/ptbs/prog-txn-blocks#non-public-entry-function-restrictions) for details. \ No newline at end of file diff --git a/docs/content/guides/developer/dev-cheat-sheet.mdx b/docs/content/guides/developer/dev-cheat-sheet.mdx index 30e69a4f0e51..7a66a8e47642 100644 --- a/docs/content/guides/developer/dev-cheat-sheet.mdx +++ b/docs/content/guides/developer/dev-cheat-sheet.mdx @@ -30,7 +30,7 @@ Quick reference on best practices for Sui Network developers. - Packages are immutable, so any published package can be called forever. Use [object versioning](/guides/developer/packages/upgrade.mdx#versioned-shared-objects) to prevent older versions from being called. - If you upgrade a package `P1` to `P2`, other packages and clients that depend on `P1` will continue using `P1`. They do not auto-update to `P2`. Both dependent packages and client code must be explicitly updated to point at `P2`. - Packages that expect to be extended by dependent packages can avoid breaking their extensions with each upgrade by providing a standard (unchanging) interface that all versions conform to. See the [message sending](https://github.com/wormhole-foundation/wormhole/blob/74dea3bf22f0e27628b432c3e9eac05c85786a99/sui/wormhole/sources/publish_message.move) across a bridge example from Wormhole. Extension packages that produce outbound messages can use [`prepare_message`](https://github.com/wormhole-foundation/wormhole/blob/74dea3bf22f0e27628b432c3e9eac05c85786a99/sui/wormhole/sources/publish_message.move#L68-L90) from any version of the Wormhole package to produce a [`MessageTicket`](https://github.com/wormhole-foundation/wormhole/blob/74dea3bf22f0e27628b432c3e9eac05c85786a99/sui/wormhole/sources/publish_message.move#L52-L66) while client code that sends the message must pass that `MessageTicket` into [`publish_message`](https://github.com/wormhole-foundation/wormhole/blob/74dea3bf22f0e27628b432c3e9eac05c85786a99/sui/wormhole/sources/publish_message.move#L92-L152) in the latest version of the package. - - `public` function signatures cannot be deleted or changed, but `public(friend)` functions can. Use `public(friend)` or private visibility liberally unless you are exposing library functions that will live forever. + - `public` function signatures cannot be deleted or changed, but `public(package)` functions can. Use `public(package)` or private visibility liberally unless you are exposing library functions that will live forever. - It is not possible to delete `struct` types, change their definition or add new [abilities](https://move-book.com/reference/abilities) via an upgrade. Introduce new types carefully as they will live forever. ### Testing diff --git a/docs/content/guides/developer/objects/object-ownership/immutable.mdx b/docs/content/guides/developer/objects/object-ownership/immutable.mdx index 74aee7787ce3..b537d3ae306b 100644 --- a/docs/content/guides/developer/objects/object-ownership/immutable.mdx +++ b/docs/content/guides/developer/objects/object-ownership/immutable.mdx @@ -1,7 +1,15 @@ --- title: Immutable Objects description: Immutable objects cannot be changed, transferred, or deleted. Immutable objects cannot have an owner and anyone can use them. -keywords: [ immutable objects, objects immutable, create immutable object, convert object, use immutable object, test immutable object ] +keywords: + [ + immutable objects, + objects immutable, + create immutable object, + convert object, + use immutable object, + test immutable object, + ] --- Objects on Sui can either be immutable or mutable. An immutable object cannot be mutated, transferred, or deleted. These objects have no owner and are freely accessible to everyone. @@ -18,7 +26,12 @@ This call permanently makes the object immutable. The operation cannot be revers You can see this function used in the [`color_object` example module](https://github.com/MystenLabs/sui/blob/main/examples/move/color_object/sources/example.move) tests. The test creates a new address-owned `ColorObject` object, then calls `public_freeze_object` to turn it into an immutable object. - + In this test, you must own a `ColorObject` initially. After freezing, the object becomes immutable and ownerless. @@ -37,9 +50,9 @@ This function creates a new `ColorObject` and immediately makes it immutable bef ## When to use immutable objects -After an object becomes immutable, the rules of who can use this object in Sui Move calls change: +After an object becomes immutable, the rules of who can use this object in Move calls change: -1. You can only pass an immutable object as a read-only, immutable reference to Sui Move entry functions as `&T`. +1. You can only pass an immutable object as a read-only, immutable reference to Move functions as `&T`. 2. All network participants can access immutable objects. @@ -58,6 +71,7 @@ First, view the objects you own: ```sh $ export ADDR=`sui client active-address` ``` + ```sh $ sui client objects $ADDR ``` @@ -166,4 +180,4 @@ public fun update( } ``` -The function fails because the `ColorObject` is immutable. \ No newline at end of file +The function fails because the `ColorObject` is immutable. diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index 5d0fb4ba0e89..ae44e58ee963 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -4,7 +4,7 @@ description: Programmable transaction blocks are a group of commands that comple keywords: [ programmable transaction blocks, transaction blocks, program transaction blocks, PTB, PTBs, what are PTBs, what is PTB, transaction group, programmable group of transactions, program transaction block, block of transactions, group of transactions ] --- -Transactions on Sui are composed of groups of commands that execute on inputs to define the result of the transaction. Referred to as **programmable transaction blocks (PTBs)**, these groups of commands define all user transactions on Sui. PTBs allow a user to call multiple Move functions, manage their objects, and manage their coins in a single transaction without publishing a new Move package. Designed with automation and transaction builders in mind, PTBs are a lightweight and flexible way of generating transactions. +Transactions on Sui are composed of groups of commands that execute on inputs to define the result of the transaction. Referred to as **programmable transaction blocks (PTBs)**, these groups of commands define all user transactions on Sui. PTBs allow a user to call multiple Move functions, manage their objects, and manage their coins in a single transaction without publishing a new Move package. Designed with automation and transaction builders in mind, PTBs are a lightweight and flexible way of generating transactions. However, more intricate programming patterns, such as loops, are not supported. In such cases, you must publish a new Move package. @@ -44,6 +44,7 @@ Looking closer at the two main components: - `tx.moveCall({ target, arguments, typeArguments })`: Executes a Move call. Returns whatever the Sui Move call returns. - Example: `tx.moveCall({ target: '0x2::devnet_nft::mint', arguments: [tx.pure.string(name), tx.pure.string(description), tx.pure.string(image)] })` + - `tx.makeMoveVec({ type, elements })`: Constructs a vector of objects that can be passed into a moveCall. This is required as there's no other way to define a vector as an input. - Example: `tx.makeMoveVec({ elements: [tx.object(id1), tx.object(id2)] })` @@ -86,11 +87,11 @@ At the beginning of execution, the PTB runtime takes the input objects and loads The most important thing to note at this stage is the effects on the gas coin. At the beginning of execution, the maximum gas budget (in terms of SUI) is withdrawn from the gas coin. Any unused gas is returned to the gas coin at the end of execution, even if the coin has changed owners. -### Object consumption +### Object consumption -All objects created or returned by Move commands must either be consumed (destroyed, transferred, or used by another command) or explicitly dropped if the type has the `drop` ability. +All objects created or returned by Move commands must either be consumed (destroyed, transferred, or used by another command) or explicitly dropped if the type has the `drop` ability. -In a PTB, if you create an object through a Move command and do not destroy, transfer, or use it in a subsequent command, the transaction will fail with an error. +In a PTB, if you create an object through a Move command and do not destroy, transfer, or use it in a subsequent command, the transaction will fail with an error. ### Pre-execution validation @@ -140,6 +141,100 @@ Shared objects have restrictions on by value usage to ensure they remain shared - Shared objects can be wrapped or converted to dynamic fields during execution, but must be re-shared or deleted before the transaction completes. +#### Non-public entry function restrictions {#non-public-entry-function-restrictions} + +A non-public `entry` function is a function declared with `entry` that is NOT `public`--either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages. + +Non-public `entry` functions have a restriction: their arguments cannot be in a "hot" clique when the function is called. This restriction ensures that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. + +**Hot potato values** + +A value is a _hot potato_ if its type has neither the `drop` nor the `store` ability. Hot potato values must be consumed (moved by value) before the transaction completes; they cannot be silently dropped or stored. + +**Cliques** + +The system tracks which values are entangled using _cliques_: + +- Each PTB input starts in its own clique with a hot count of 0. +- When values are used together as arguments in a command, their cliques merge. The hot counts add together. +- Hot potato return values from a command increment the merged clique's hot count. +- Moving (consuming) a hot potato decrements its clique's hot count. +- Before a non-public `entry` call, the merged clique of its arguments must have a hot count of 0. + +A non-public `entry` function _can_ consume hot potato values — they just must be the last hot values in their clique. The hot count check happens after arguments are consumed but before the function is verified. + +**Shared objects consumed by value** + +Consuming a shared object by value permanently marks its clique as "always hot." Since shared objects cannot be wrapped (they must be re-shared or deleted) consuming one by value is treated similarly to a hot potato that can never be resolved. A non-public `entry` function can receive a shared object by value directly, but it cannot receive a value whose clique previously interacted with a shared object consumed by value. + +**Examples** + +Consider the following module: + +```move +module ex::m; + +public struct HotPotato() + +public fun hot(x: &mut Coin): HotPotato { ... } + +entry fun spend(x: &mut Coin) { ... } + +public fun cool(h: HotPotato) { ... } +``` + +In this invalid PTB, the `HotPotato` from command 0 is still alive in the same clique as `Input(0)` when `spend` is called: + +``` +// Invalid PTB +// Input 0: Coin +// cliques: { Input(0) } => 0 +0: ex::m::hot(Input(0)); +// cliques: { Input(0), Result(0) } => 1 +1: ex::m::spend(Input(0)); // INVALID: Input(0)'s clique has count > 0 +2: ex::m::cool(Result(0)); +``` + +However, if the hot potato is consumed before calling `spend`, the clique's count drops to 0 and the call succeeds: + +``` +// Valid PTB +// Input 0: Coin +// cliques: { Input(0) } => 0 +0: ex::m::hot(Input(0)); +// cliques: { Input(0), Result(0) } => 1 +1: ex::m::cool(Result(0)); +// cliques: { Input(0) } => 0 +2: ex::m::spend(Input(0)); // Valid: Input(0)'s clique has count 0 +``` + +In a flash loan scenario, entanglement extends transitively. Even if a value was not directly involved in the loan, being in the same clique as the loan's hot potato makes it ineligible for a non-public `entry` call: + +```move +module flash::loan; + +public struct Loan { amount: u64 } + +public fun issue(bank: &mut Bank, amount: u64): (Balance, Loan) { ERROR } + +public fun repay(bank: &mut Bank, loan: Loan, repayment: Balance) { ERROR } +``` + +``` +// Invalid PTB +// Input 0: flash::loan::Bank, Input 1: u64 +// cliques: { Input(0) } => 0, { Input(1) } => 0 +0: flash::loan::issue(Input(0), Input(1)) +// cliques: { Input(0), Input(1), NestedResult(0,0), NestedResult(0,1) } => 1 +1: sui::coin::from_balance(NestedResult(0,0)); +// cliques: { Input(0), Input(1), NestedResult(0,1), Result(1) } => 1 +2: ex::m::spend(Result(1)); // INVALID: Result(1)'s clique has count > 0 +3: sui::coin::into_balance(Result(1)); +4: flash::loan::repay(Input(0), NestedResult(0,1), Result(3)); +``` + +Even though the `Coin` created in command 1 was not directly involved in the flash loan, it is part of a clique with the outstanding `Loan` hot potato (`NestedResult(0,1)`). Repaying the loan before calling `spend` would make the PTB valid. + #### Pure value type checking Pure values are not type checked until their usage. When checking if a pure value has type `T`, the system verifies whether `T` is a valid type for a pure value (see the list in the [Inputs section](/guides/developer/transactions/ptbs/inputs-and-results)). If it is, the bytes are validated. You can use a pure value with multiple types as long as the bytes are valid for each type. For example, you can use a string as an ASCII string `std::ascii::String` and as a UTF8 string `std::string::String`. However, after you mutably borrow the pure value, the type becomes fixed, and all future usages must be with that type. @@ -172,7 +267,7 @@ Any remaining SUI deducted from the gas coin at the beginning of execution is re The total effects (created, mutated, and deleted objects) are then passed out of the execution layer and applied by the Sui network. -## Execution example +## Execution example By following each command's execution, you can see how inputs flow through commands, how results accumulate, and how the final transaction effects are determined. @@ -286,4 +381,4 @@ Results: [ - `Item { id: id2 }` transferred to sender. -- Marketplace object returned as shared (mutated). \ No newline at end of file +- Marketplace object returned as shared (mutated). From 3c694ff8eeff1524d78a9412fc5d93d62614868f Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Thu, 19 Mar 2026 16:57:37 -0700 Subject: [PATCH 2/9] note --- .../guides/developer/transactions/ptbs/prog-txn-blocks.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index ae44e58ee963..2e8db716f058 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -145,7 +145,9 @@ Shared objects have restrictions on by value usage to ensure they remain shared A non-public `entry` function is a function declared with `entry` that is NOT `public`--either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages. -Non-public `entry` functions have a restriction: their arguments cannot be in a "hot" clique when the function is called. This restriction ensures that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. +Previously, `entry` functions had signature restrictions that limited their parameter and return types compared to `public` functions. These restrictions have been removed--`entry` functions can now have the same signature as any `public` function! + +Non-public `entry` functions have a single restriction: their arguments cannot be in a "hot" clique when the function is called. This restriction ensures that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. **Hot potato values** From 8ab491a2241c1d6560de3d55ca7101b4b39cc0bb Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Thu, 19 Mar 2026 17:24:34 -0700 Subject: [PATCH 3/9] move call --- docs/content/concepts/sui-move-concepts.mdx | 4 ++-- .../objects/object-ownership/immutable.mdx | 18 ++---------------- .../transactions/ptbs/prog-txn-blocks.mdx | 17 +++++++++++++++-- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/docs/content/concepts/sui-move-concepts.mdx b/docs/content/concepts/sui-move-concepts.mdx index 2e0c94e3a924..70df6fc77708 100644 --- a/docs/content/concepts/sui-move-concepts.mdx +++ b/docs/content/concepts/sui-move-concepts.mdx @@ -64,6 +64,6 @@ Make your function private (don't add the `public` visibility keyword) and mark In addition to this Sui-specific use case, there are other rules and restrictions for `entry` functions: -- `entry` functions can only return types with the `drop` ability. +- Non-public `entry` functions (private or `public(package)`) have restrictions on arguments that are entangled with hot potato values in a PTB. See [Non-public entry function restrictions](/guides/developer/transactions/ptbs/prog-txn-blocks#non-public-entry-function-restrictions) for details. -- Non-public `entry` functions (private or `public(package)`) have restrictions on arguments that are entangled with hot potato values in a PTB. See [Non-public entry function restrictions](/guides/developer/transactions/ptbs/prog-txn-blocks#non-public-entry-function-restrictions) for details. \ No newline at end of file +Note that while previously `entry` functions had additional restrictions on their signatures (their types for parameters and return values), this is no longer the case! `entry` functions can have the same arguments or return values as a `public` function in a PTB. diff --git a/docs/content/guides/developer/objects/object-ownership/immutable.mdx b/docs/content/guides/developer/objects/object-ownership/immutable.mdx index b537d3ae306b..e9b10fc5d3d7 100644 --- a/docs/content/guides/developer/objects/object-ownership/immutable.mdx +++ b/docs/content/guides/developer/objects/object-ownership/immutable.mdx @@ -1,15 +1,7 @@ --- title: Immutable Objects description: Immutable objects cannot be changed, transferred, or deleted. Immutable objects cannot have an owner and anyone can use them. -keywords: - [ - immutable objects, - objects immutable, - create immutable object, - convert object, - use immutable object, - test immutable object, - ] +keywords: [ immutable objects, objects immutable, create immutable object, convert object, use immutable object, test immutable object ] --- Objects on Sui can either be immutable or mutable. An immutable object cannot be mutated, transferred, or deleted. These objects have no owner and are freely accessible to everyone. @@ -26,12 +18,7 @@ This call permanently makes the object immutable. The operation cannot be revers You can see this function used in the [`color_object` example module](https://github.com/MystenLabs/sui/blob/main/examples/move/color_object/sources/example.move) tests. The test creates a new address-owned `ColorObject` object, then calls `public_freeze_object` to turn it into an immutable object. - + In this test, you must own a `ColorObject` initially. After freezing, the object becomes immutable and ownerless. @@ -71,7 +58,6 @@ First, view the objects you own: ```sh $ export ADDR=`sui client active-address` ``` - ```sh $ sui client objects $ADDR ``` diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index 2e8db716f058..6ee1bae4dbf1 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -141,12 +141,25 @@ Shared objects have restrictions on by value usage to ensure they remain shared - Shared objects can be wrapped or converted to dynamic fields during execution, but must be re-shared or deleted before the transaction completes. -#### Non-public entry function restrictions {#non-public-entry-function-restrictions} +#### Move call rules -A non-public `entry` function is a function declared with `entry` that is NOT `public`--either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages. +PTBs can call any `public` function and any `entry` function, whether private (`entry fun f()`), `public(package)` or public package (`public(package) entry fun f()`). Non-entry private and `public(package)` functions cannot be called from PTBs. Note that in this way, there is no reason to add `entry` to a `public` function. Previously, `entry` functions had signature restrictions that limited their parameter and return types compared to `public` functions. These restrictions have been removed--`entry` functions can now have the same signature as any `public` function! +**Return types:** +Move calls cannot return references (`&T` or `&mut T`). However, this restriction will be lifted in the future. + +**Private generics:** +Some framework functions have type parameters that can only be instantiated with types defined in the same module. Since PTBs are not modules, they cannot supply these types and therefore cannot call these functions directly. For example, certain `sui::transfer` functions like `transfer` and `share_object` require an object type with defined in the calling module, and as such cannot be used in the PTB. Instead, use the `public_transfer` and `public_share_object` variants. + +**TxContext handling:** +`TxContext` parameters (`&TxContext` or `&mut TxContext`) are automatically injected by the runtime--callers do not supply them. `TxContext` can appear at any position in the parameter list, and a function can have multiple `TxContext` parameters as long as they are immutable (`&TxContext`). These parameters are not counted toward the user-supplied argument count for terms of indexing. + +#### Non-public entry function restrictions {#non-public-entry-function-restrictions} + +A non-public `entry` function is a function declared with `entry` that is NOT `public`--either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages. + Non-public `entry` functions have a single restriction: their arguments cannot be in a "hot" clique when the function is called. This restriction ensures that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. **Hot potato values** From 8dbff3ff6730a8e39715c6d737621b89f048ad6a Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Thu, 19 Mar 2026 19:56:49 -0700 Subject: [PATCH 4/9] fix docs --- .../transactions/ptbs/inputs-and-results.mdx | 12 +++++----- .../transactions/ptbs/prog-txn-blocks.mdx | 24 ++++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx b/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx index f41e4756d8a3..502bc429985a 100644 --- a/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx +++ b/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx @@ -8,7 +8,7 @@ Programmable transaction blocks (PTBs) operate on inputs and produce results. In ## 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. +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, `CallArg::Object(ObjectArg)`, which contains the necessary metadata to specify the object being used, or a pure value, `CallArg::Pure(Vec)`, which contains the BCS bytes of the value. For object inputs, the metadata needed differs depending on the type of [ownership of the object](/guides/developer/objects/object-ownership). The data for the `ObjectArg` enum follows: @@ -32,19 +32,19 @@ For pure inputs, the only data provided is the [BCS](https://github.com/MystenLa 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. +Bytes are not validated until a command specifies the expected type, for example in `MoveCall` or `MakeMoveVec`. Each distinct type creates a separate typed copy of the bytes, so the same pure input can be used at multiple types as long as the bytes are valid for each. Each typed copy is treated as its own input, so mutations affect only that type's value. ## 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. +- `MoveCall`: The number of results and their types are determined by the Move function being called. Move calls cannot return references (`&T` or `&mut T`); this restriction will be lifted in the future. - `SplitCoins`: Produces one or more coins from a single coin. The type of each coin is `sui::coin::Coin` where the specific coin type `T` matches the coin being split. -- `Publish`: Returns the upgrade capability `sui::package::UpgradeCap` for the newly published package. +- `Publish`: Returns a single `sui::package::UpgradeCap`. Module bytes and dependency IDs are embedded in the command structure--they are not `Argument` values. After the package is staged, `init` functions run for each module in order. -- `Upgrade`: Returns the upgrade receipt `sui::package::UpgradeReceipt` for the upgraded package. +- `Upgrade`: Takes exactly one `Argument`: a `sui::package::UpgradeTicket` (by value). Returns a single `sui::package::UpgradeReceipt`. Module bytes and dependency IDs are embedded in the command, not supplied as `Argument` values. Does not call `init` functions. - `TransferObjects` and `MergeCoins` produce an empty result vector. @@ -104,7 +104,7 @@ const hero = tx.moveCall({ typeArguments: [], }); -//According to Move function new_sword, this moveCall will return an Object of Type Hero +//According to Move function new_sword, this moveCall will return an Object of Type Sword const sword = tx.moveCall({ target: `0x123::hero::new_sword`, arguments: [tx.pure.u64(10)], diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index 6ee1bae4dbf1..402106de8869 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -151,10 +151,10 @@ Previously, `entry` functions had signature restrictions that limited their para Move calls cannot return references (`&T` or `&mut T`). However, this restriction will be lifted in the future. **Private generics:** -Some framework functions have type parameters that can only be instantiated with types defined in the same module. Since PTBs are not modules, they cannot supply these types and therefore cannot call these functions directly. For example, certain `sui::transfer` functions like `transfer` and `share_object` require an object type with defined in the calling module, and as such cannot be used in the PTB. Instead, use the `public_transfer` and `public_share_object` variants. +Some framework functions have type parameters that can only be instantiated with types defined in the same module. Since PTBs are not modules, they cannot supply these types and therefore cannot call these functions directly. For example, certain `sui::transfer` functions like `transfer` and `share_object` require a type defined in the calling module, and as such cannot be called from a PTB. Instead, use the `public_transfer` and `public_share_object` variants. **TxContext handling:** -`TxContext` parameters (`&TxContext` or `&mut TxContext`) are automatically injected by the runtime--callers do not supply them. `TxContext` can appear at any position in the parameter list, and a function can have multiple `TxContext` parameters as long as they are immutable (`&TxContext`). These parameters are not counted toward the user-supplied argument count for terms of indexing. +`TxContext` parameters (`&TxContext` or `&mut TxContext`) are automatically injected by the runtime--callers do not supply them. `TxContext` can appear at any position in the parameter list, and a function can have multiple `TxContext` parameters as long as they are immutable (`&TxContext`). These parameters are not counted toward the user-supplied argument count for indexing purposes. #### Non-public entry function restrictions {#non-public-entry-function-restrictions} @@ -252,7 +252,25 @@ Even though the `Coin` created in command 1 was not directly involved in the fla #### Pure value type checking -Pure values are not type checked until their usage. When checking if a pure value has type `T`, the system verifies whether `T` is a valid type for a pure value (see the list in the [Inputs section](/guides/developer/transactions/ptbs/inputs-and-results)). If it is, the bytes are validated. You can use a pure value with multiple types as long as the bytes are valid for each type. For example, you can use a string as an ASCII string `std::ascii::String` and as a UTF8 string `std::string::String`. However, after you mutably borrow the pure value, the type becomes fixed, and all future usages must be with that type. +Pure values are not type checked until they are used. The first time a pure value appears as an argument expecting type `T`, the system checks that `T` is a valid pure type (see the [Inputs section](/guides/developer/transactions/ptbs/inputs-and-results)) and that the bytes deserialize to `T`. Each distinct type creates a separate typed copy of the bytes--so the same pure input can be used at multiple types as long as the bytes are valid for each. For example, you can use the same bytes as an ASCII string `std::ascii::String` and as a UTF-8 string `std::string::String`. Each typed copy is treated as its own input, so mutations affect only that type's value, not all values created from the same bytes. + +#### Publish and Upgrade rules + +Both commands embed their module bytes and dependency IDs directly in the command structure--these are not PTB `Argument` values. + +**Publish:** + +- Returns a single `sui::package::UpgradeCap`. +- After the package is staged, the runtime calls each module's `init` function (if present) in module order. `init` receives `&mut TxContext` and optionally a one-time witness. Additional `init` arguments are not yet supported but are planned. +- The package is available to `init` functions during execution--it is staged before they run. + +**Upgrade:** + +- Takes exactly one PTB argument: a `sui::package::UpgradeTicket` (by value). +- Returns a single `sui::package::UpgradeReceipt`. +- Does _not_ call `init` functions--newly added modules in an upgrade cannot have `init` functions. This restriction will be lifted in a future release. +- The module digest and package ID in the ticket must match exactly. +- The upgrade policy (compatible, additive, dep-only) is enforced from the ticket. ### End of execution From c878b18f9009e39684b74654c08fdd4aeef6138c Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Mon, 23 Mar 2026 19:01:12 -0700 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Adam Welc Co-authored-by: Jessie Mongeon <133128541+jessiemongeon1@users.noreply.github.com> --- docs/content/concepts/sui-move-concepts.mdx | 6 +++- .../transactions/ptbs/inputs-and-results.mdx | 2 +- .../transactions/ptbs/prog-txn-blocks.mdx | 30 +++++++++++-------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/content/concepts/sui-move-concepts.mdx b/docs/content/concepts/sui-move-concepts.mdx index 70df6fc77708..1b644cf08bbd 100644 --- a/docs/content/concepts/sui-move-concepts.mdx +++ b/docs/content/concepts/sui-move-concepts.mdx @@ -66,4 +66,8 @@ In addition to this Sui-specific use case, there are other rules and restriction - Non-public `entry` functions (private or `public(package)`) have restrictions on arguments that are entangled with hot potato values in a PTB. See [Non-public entry function restrictions](/guides/developer/transactions/ptbs/prog-txn-blocks#non-public-entry-function-restrictions) for details. -Note that while previously `entry` functions had additional restrictions on their signatures (their types for parameters and return values), this is no longer the case! `entry` functions can have the same arguments or return values as a `public` function in a PTB. +:::info + +Note that while previously `entry` functions had additional restrictions on their signatures (their types for parameters and return values), this is no longer the case. `entry` functions can have the same arguments or return values as a `public` function in a PTB. + +::: diff --git a/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx b/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx index 502bc429985a..d76d3155356d 100644 --- a/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx +++ b/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx @@ -42,7 +42,7 @@ Each transaction command produces an array of values. The array can be empty. Th - `SplitCoins`: Produces one or more coins from a single coin. The type of each coin is `sui::coin::Coin` where the specific coin type `T` matches the coin being split. -- `Publish`: Returns a single `sui::package::UpgradeCap`. Module bytes and dependency IDs are embedded in the command structure--they are not `Argument` values. After the package is staged, `init` functions run for each module in order. +- `Publish`: Returns a single `sui::package::UpgradeCap`. Module bytes and dependency IDs are embedded in the command structure; they are not `Argument` values. After the package is staged, `init` functions run for each module in order. - `Upgrade`: Takes exactly one `Argument`: a `sui::package::UpgradeTicket` (by value). Returns a single `sui::package::UpgradeReceipt`. Module bytes and dependency IDs are embedded in the command, not supplied as `Argument` values. Does not call `init` functions. diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index 402106de8869..edf192c5d8e4 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -143,9 +143,13 @@ Shared objects have restrictions on by value usage to ensure they remain shared #### Move call rules -PTBs can call any `public` function and any `entry` function, whether private (`entry fun f()`), `public(package)` or public package (`public(package) entry fun f()`). Non-entry private and `public(package)` functions cannot be called from PTBs. Note that in this way, there is no reason to add `entry` to a `public` function. +PTBs can call any `public` function and any `entry` function, whether private (`entry fun f()`), or `public(package)` (`public(package) entry fun f()`). Non-entry private and `public(package)` functions cannot be called from PTBs. Note that in this way, there is no reason to add `entry` to a `public` function. -Previously, `entry` functions had signature restrictions that limited their parameter and return types compared to `public` functions. These restrictions have been removed--`entry` functions can now have the same signature as any `public` function! +:::info + +Previously, `entry` functions had signature restrictions that limited their parameter and return types compared to `public` functions. These restrictions have been removed. `entry` functions can now have the same signature as any `public` function. + +::: **Return types:** Move calls cannot return references (`&T` or `&mut T`). However, this restriction will be lifted in the future. @@ -153,22 +157,22 @@ Move calls cannot return references (`&T` or `&mut T`). However, this restrictio **Private generics:** Some framework functions have type parameters that can only be instantiated with types defined in the same module. Since PTBs are not modules, they cannot supply these types and therefore cannot call these functions directly. For example, certain `sui::transfer` functions like `transfer` and `share_object` require a type defined in the calling module, and as such cannot be called from a PTB. Instead, use the `public_transfer` and `public_share_object` variants. -**TxContext handling:** -`TxContext` parameters (`&TxContext` or `&mut TxContext`) are automatically injected by the runtime--callers do not supply them. `TxContext` can appear at any position in the parameter list, and a function can have multiple `TxContext` parameters as long as they are immutable (`&TxContext`). These parameters are not counted toward the user-supplied argument count for indexing purposes. +**`TxContext` handling:** +`TxContext` parameters (`&TxContext` or `&mut TxContext`) are automatically injected by the runtime; callers do not supply them. `TxContext` can appear at any position in the parameter list, and a function can have multiple `TxContext` parameters as long as they are immutable (`&TxContext`). These parameters are not counted toward the user-supplied argument count for indexing purposes. #### Non-public entry function restrictions {#non-public-entry-function-restrictions} -A non-public `entry` function is a function declared with `entry` that is NOT `public`--either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages. +A non-public `entry` function is a function declared with `entry` that is **not** `public`. It is either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages. -Non-public `entry` functions have a single restriction: their arguments cannot be in a "hot" clique when the function is called. This restriction ensures that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. +Non-public `entry` functions have a single restriction: their arguments cannot be in a "hot" clique (see below) when the function is called. This restriction ensures that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. **Hot potato values** -A value is a _hot potato_ if its type has neither the `drop` nor the `store` ability. Hot potato values must be consumed (moved by value) before the transaction completes; they cannot be silently dropped or stored. +A value is a hot potato if its type has neither the `drop` nor the `store` ability. Hot potato values must be consumed (moved by value) before the transaction completes; they cannot be silently dropped or stored. **Cliques** -The system tracks which values are entangled using _cliques_: +The system tracks which values are entangled using cliques: - Each PTB input starts in its own clique with a hot count of 0. - When values are used together as arguments in a command, their cliques merge. The hot counts add together. @@ -176,7 +180,7 @@ The system tracks which values are entangled using _cliques_: - Moving (consuming) a hot potato decrements its clique's hot count. - Before a non-public `entry` call, the merged clique of its arguments must have a hot count of 0. -A non-public `entry` function _can_ consume hot potato values — they just must be the last hot values in their clique. The hot count check happens after arguments are consumed but before the function is verified. +A non-public `entry` function can consume hot potato values, but they must be the last hot values in their clique. The hot count check happens after arguments are consumed but before the function is verified. **Shared objects consumed by value** @@ -252,23 +256,23 @@ Even though the `Coin` created in command 1 was not directly involved in the fla #### Pure value type checking -Pure values are not type checked until they are used. The first time a pure value appears as an argument expecting type `T`, the system checks that `T` is a valid pure type (see the [Inputs section](/guides/developer/transactions/ptbs/inputs-and-results)) and that the bytes deserialize to `T`. Each distinct type creates a separate typed copy of the bytes--so the same pure input can be used at multiple types as long as the bytes are valid for each. For example, you can use the same bytes as an ASCII string `std::ascii::String` and as a UTF-8 string `std::string::String`. Each typed copy is treated as its own input, so mutations affect only that type's value, not all values created from the same bytes. +Pure values are not type checked until they are used. The first time a pure value appears as an argument expecting type `T`, the system checks that `T` is a valid pure type (see the [Inputs section](/guides/developer/transactions/ptbs/inputs-and-results)) and that the bytes deserialize to `T`. Each distinct type creates a separate typed copy of the bytes, so the same pure input can be used at multiple types as long as the bytes are valid for each. For example, you can use the same bytes as an ASCII string `std::ascii::String` and as a UTF-8 string `std::string::String`. Each typed copy is treated as its own input, so mutations affect only that type's value, not all values created from the same bytes. #### Publish and Upgrade rules -Both commands embed their module bytes and dependency IDs directly in the command structure--these are not PTB `Argument` values. +Both commands embed their module bytes and dependency IDs directly in the command structure, however, these are not PTB `Argument` values. **Publish:** - Returns a single `sui::package::UpgradeCap`. - After the package is staged, the runtime calls each module's `init` function (if present) in module order. `init` receives `&mut TxContext` and optionally a one-time witness. Additional `init` arguments are not yet supported but are planned. -- The package is available to `init` functions during execution--it is staged before they run. +- The package is available to `init` functions during execution. It is staged before they run. **Upgrade:** - Takes exactly one PTB argument: a `sui::package::UpgradeTicket` (by value). - Returns a single `sui::package::UpgradeReceipt`. -- Does _not_ call `init` functions--newly added modules in an upgrade cannot have `init` functions. This restriction will be lifted in a future release. +- Does not call `init` functions. Newly added modules in an upgrade cannot have `init` functions. This restriction will be lifted in a future release. - The module digest and package ID in the ticket must match exactly. - The upgrade policy (compatible, additive, dep-only) is enforced from the ticket. From 4f8288debefb5cc521ea028d700aedbdaa29c311 Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Wed, 25 Mar 2026 14:29:53 -0700 Subject: [PATCH 6/9] suggestions --- .../transactions/ptbs/prog-txn-blocks.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index edf192c5d8e4..cca0339be489 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -145,7 +145,7 @@ Shared objects have restrictions on by value usage to ensure they remain shared PTBs can call any `public` function and any `entry` function, whether private (`entry fun f()`), or `public(package)` (`public(package) entry fun f()`). Non-entry private and `public(package)` functions cannot be called from PTBs. Note that in this way, there is no reason to add `entry` to a `public` function. -:::info +:::info Previously, `entry` functions had signature restrictions that limited their parameter and return types compared to `public` functions. These restrictions have been removed. `entry` functions can now have the same signature as any `public` function. @@ -164,7 +164,7 @@ Some framework functions have type parameters that can only be instantiated with A non-public `entry` function is a function declared with `entry` that is **not** `public`. It is either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages. -Non-public `entry` functions have a single restriction: their arguments cannot be in a "hot" clique (see below) when the function is called. This restriction ensures that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. +Non-public `entry` functions have a single restriction: their arguments cannot be in a "hot" clique (see below) when the function is called. The intuition behind this restriction is to ensure that arguments to non-public `entry` functions are not entangled with outstanding [hot potato](https://move-book.com/programmability/hot-potato-pattern) values that could force behavior after the function executes. **Hot potato values** @@ -172,7 +172,7 @@ A value is a hot potato if its type has neither the `drop` nor the `store` abili **Cliques** -The system tracks which values are entangled using cliques: +The system tracks which values are entangled using cliques. As described above, the intuition behind to goal is to prevent hot potato values from forcing behavior outside of the call to the non-public `entry` function. The cliques provide a modeling of this "entanglement" by keeping a count of how many hot potato values are live and which values they have interacted with: - Each PTB input starts in its own clique with a hot count of 0. - When values are used together as arguments in a command, their cliques merge. The hot counts add together. @@ -204,7 +204,7 @@ public fun cool(h: HotPotato) { ... } In this invalid PTB, the `HotPotato` from command 0 is still alive in the same clique as `Input(0)` when `spend` is called: -``` +```rust // Invalid PTB // Input 0: Coin // cliques: { Input(0) } => 0 @@ -216,7 +216,7 @@ In this invalid PTB, the `HotPotato` from command 0 is still alive in the same c However, if the hot potato is consumed before calling `spend`, the clique's count drops to 0 and the call succeeds: -``` +```rust // Valid PTB // Input 0: Coin // cliques: { Input(0) } => 0 @@ -239,7 +239,7 @@ public fun issue(bank: &mut Bank, amount: u64): (Balance, Loan) { ERROR } public fun repay(bank: &mut Bank, loan: Loan, repayment: Balance) { ERROR } ``` -``` +```rust // Invalid PTB // Input 0: flash::loan::Bank, Input 1: u64 // cliques: { Input(0) } => 0, { Input(1) } => 0 @@ -265,7 +265,7 @@ Both commands embed their module bytes and dependency IDs directly in the comman **Publish:** - Returns a single `sui::package::UpgradeCap`. -- After the package is staged, the runtime calls each module's `init` function (if present) in module order. `init` receives `&mut TxContext` and optionally a one-time witness. Additional `init` arguments are not yet supported but are planned. +- After the package is staged, the runtime calls each module's `init` function (if present) in the order the modules are provided (in the vector of serialized bytes). `init` receives `&mut TxContext` and optionally a one-time witness. Additional `init` arguments are not yet supported but are planned. - The package is available to `init` functions during execution. It is staged before they run. **Upgrade:** From af0b5b3c8fba9aba385674fb4693ea4ab86e8efa Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Wed, 25 Mar 2026 14:31:19 -0700 Subject: [PATCH 7/9] Update docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx Co-authored-by: Damir Shamanaev --- .../guides/developer/transactions/ptbs/inputs-and-results.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx b/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx index d76d3155356d..92a49e334f55 100644 --- a/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx +++ b/docs/content/guides/developer/transactions/ptbs/inputs-and-results.mdx @@ -104,7 +104,7 @@ const hero = tx.moveCall({ typeArguments: [], }); -//According to Move function new_sword, this moveCall will return an Object of Type Sword +// According to Move function new_sword, this moveCall will return an Object of Type Sword const sword = tx.moveCall({ target: `0x123::hero::new_sword`, arguments: [tx.pure.u64(10)], From 281f4ab1a6363d54ab3bf39b9700b0b5af60556e Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Wed, 25 Mar 2026 16:29:52 -0700 Subject: [PATCH 8/9] Update docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx Co-authored-by: Jessie Mongeon <133128541+jessiemongeon1@users.noreply.github.com> --- .../guides/developer/transactions/ptbs/prog-txn-blocks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index cca0339be489..7d95162710f8 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -258,7 +258,7 @@ Even though the `Coin` created in command 1 was not directly involved in the fla Pure values are not type checked until they are used. The first time a pure value appears as an argument expecting type `T`, the system checks that `T` is a valid pure type (see the [Inputs section](/guides/developer/transactions/ptbs/inputs-and-results)) and that the bytes deserialize to `T`. Each distinct type creates a separate typed copy of the bytes, so the same pure input can be used at multiple types as long as the bytes are valid for each. For example, you can use the same bytes as an ASCII string `std::ascii::String` and as a UTF-8 string `std::string::String`. Each typed copy is treated as its own input, so mutations affect only that type's value, not all values created from the same bytes. -#### Publish and Upgrade rules +#### Publish and upgrade rules Both commands embed their module bytes and dependency IDs directly in the command structure, however, these are not PTB `Argument` values. From 33cff2fffacb66b3598d4ec2cbacf1c56cffa25f Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Wed, 25 Mar 2026 16:31:33 -0700 Subject: [PATCH 9/9] comments --- .../guides/developer/transactions/ptbs/prog-txn-blocks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx index 7d95162710f8..d6f43b6129b8 100644 --- a/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx +++ b/docs/content/guides/developer/transactions/ptbs/prog-txn-blocks.mdx @@ -160,7 +160,7 @@ Some framework functions have type parameters that can only be instantiated with **`TxContext` handling:** `TxContext` parameters (`&TxContext` or `&mut TxContext`) are automatically injected by the runtime; callers do not supply them. `TxContext` can appear at any position in the parameter list, and a function can have multiple `TxContext` parameters as long as they are immutable (`&TxContext`). These parameters are not counted toward the user-supplied argument count for indexing purposes. -#### Non-public entry function restrictions {#non-public-entry-function-restrictions} +#### Non-public entry function restrictions A non-public `entry` function is a function declared with `entry` that is **not** `public`. It is either private (`entry fun f()`) or `public(package)` (`public(package) entry fun f()`). These functions can be called directly in PTBs but not from other packages.