diff --git a/docs/content/about-iota/iota-architecture/transaction-lifecycle.mdx b/docs/content/about-iota/iota-architecture/transaction-lifecycle.mdx index ce4ce3e1ccc..c5c4a396447 100644 --- a/docs/content/about-iota/iota-architecture/transaction-lifecycle.mdx +++ b/docs/content/about-iota/iota-architecture/transaction-lifecycle.mdx @@ -116,7 +116,7 @@ A checkpoint contains a list of transactions and is signed by a majority of vali Before sending back an effects certificate, a full node might execute the transaction locally if the request asks it to. This is more important for high-frequency applications like gaming but can add unnecessary delay for simple transactions like buying coffee. The `WaitForLocalExecution` parameter requests this local execution, while you can use the `WaitForEffects` parameter for a quicker response. -Additionally, when any app builds a transaction, the full node is usually in charge of choosing the object that is used to pay for the transaction's gas. Since gas is paid in IOTA, which is a shared object, if the full node is not up-to-date, it could potentially lead to an invalid transaction or even a [client equivocation](../../developer/iota-101/transactions/sponsored-transactions.mdx#potential-risks-using-sponsored-transactions). You can avoid this unwanted behavior by sending the `WaitForLocalExecution` parameter. +Additionally, when any app builds a transaction, the full node is usually in charge of choosing the object that is used to pay for the transaction's gas. Since gas is paid in IOTA, which is a shared object, if the full node is not up-to-date, it could potentially lead to an invalid transaction or even a [client equivocation](../../developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions.mdx#risk-considerations). You can avoid this unwanted behavior by sending the `WaitForLocalExecution` parameter. ### Epoch Change @@ -129,4 +129,5 @@ If your wallet app crashes during a transaction, it stores the signed transactio The wallet app can query the full node with the `getTransactionBlock` method to check if the transaction is finalized. If the response contains transaction details, it is finalized. If the response is `None`, the transaction may need to be resubmitted. This ensures that the coffee shop's full node will eventually recognize the transaction once it's included in a checkpoint and update the coffee shop's balance. ## Quizzes + \ No newline at end of file diff --git a/docs/content/developer/getting-started/create-a-module.mdx b/docs/content/developer/getting-started/create-a-module.mdx index a715d63903e..c4db8a8c43b 100644 --- a/docs/content/developer/getting-started/create-a-module.mdx +++ b/docs/content/developer/getting-started/create-a-module.mdx @@ -74,11 +74,11 @@ Keep in mind that if you [upgrade your package](../iota-101/move-overview/packag ### Entry Functions -Add the `entry` modifier to functions you want to call from a [programmable transaction block](../iota-101/transactions/ptb/prog-txn-blocks.mdx). All parameters passed to the function must be inputs to the transaction block, not results from other transactions in the block, nor can they be modified by previous transactions in the block. These functions can only return types with the `drop` ability. +Add the `entry` modifier to functions you want to call from a [programmable transaction block](../iota-101/transactions/ptb/programmable-transaction-blocks). All parameters passed to the function must be inputs to the transaction block, not results from other transactions in the block, nor can they be modified by previous transactions in the block. These functions can only return types with the `drop` ability. ### Public Functions -`public` functions can be called from a [programmable transaction block](../iota-101/transactions/ptb/prog-txn-blocks.mdx) or another module. +`public` functions can be called from a [programmable transaction block](../iota-101/transactions/ptb/programmable-transaction-blocks) or another module. #### Accessor Functions diff --git a/docs/content/developer/getting-started/publish.mdx b/docs/content/developer/getting-started/publish.mdx index 50506de6f55..677306197df 100644 --- a/docs/content/developer/getting-started/publish.mdx +++ b/docs/content/developer/getting-started/publish.mdx @@ -90,7 +90,7 @@ iota client objects ### Accessing Your Package -After successfully publishing the package, you can use the `iota client call` command to execute individual functions or create sophisticated [programmable transaction blocks](../iota-101/transactions/ptb/prog-txn-blocks.mdx) that group multiple commands into a single, cost-effective transaction with the `iota client ptb` command. +After successfully publishing the package, you can use the `iota client call` command to execute individual functions or create sophisticated [programmable transaction blocks](../iota-101/transactions/ptb/programmable-transaction-blocks) that group multiple commands into a single, cost-effective transaction with the `iota client ptb` command. ```mermaid flowchart TB diff --git a/docs/content/developer/guides.mdx b/docs/content/developer/guides.mdx index bac5ee7446f..e4b9f24759c 100644 --- a/docs/content/developer/guides.mdx +++ b/docs/content/developer/guides.mdx @@ -36,7 +36,7 @@ With IOTA installed, you're ready to start developing. Learn the basics of IOTA and how they might differ from other blockchains. - + Transactions on IOTA are more powerful than other blockchains. Learn why and how to use them. diff --git a/docs/content/developer/iota-101.mdx b/docs/content/developer/iota-101.mdx index 028527d99e7..00ae371b7da 100644 --- a/docs/content/developer/iota-101.mdx +++ b/docs/content/developer/iota-101.mdx @@ -16,7 +16,7 @@ Everything on the IOTA blockchain is an object. These topics use code examples t You can create programmable transaction blocks (PTBs) on IOTA to perform multiple commands in a single transaction. The Working with PTBs topics demonstrate how to build efficient PTBs using the IOTA TypeScript SDK. -Go to [Working with PTBs](iota-101/transactions/ptb/working-with-ptbs.mdx). +Go to [Working with PTBs](iota-101/transactions/ptb/programmable-transaction-blocks-overview). ## Using Events diff --git a/docs/content/developer/iota-101/transactions/gas-smashing.mdx b/docs/content/developer/iota-101/transactions/gas-smashing.mdx deleted file mode 100644 index e3f1d4f9582..00000000000 --- a/docs/content/developer/iota-101/transactions/gas-smashing.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Gas Smashing -description: IOTA optimizes coin management by combining multiple coins into a single object to pay for gas fees. ---- -import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/gas-smashing.json'; - -Every transaction on IOTA has a gas fee associated with its execution that must be paid to successfully execute the transaction. Gas smashing enables you to pay for this gas fee using multiple coins instead of just one. This mechanism is especially helpful in scenarios where you might have a number of coins with smaller denominations, or if you simply want to minimize the number of IOTA coins under your account. Gas smashing is generally a useful tool for coin management, especially when coupled with the `GasCoin` programmable transaction block (PTB) argument. - -## Smashing gas - -Gas smashing happens automatically in a transaction if you provide multiple coins to pay for the gas fee. When IOTA executes a transaction, IOTA combines, or "smashes", all of the coins you provide to pay for the gas into a single coin. The smashing occurs regardless of coin amounts or the gas budget provided with the transaction (as long as it is within the minimum and maximum gas budgets). IOTA deducts the gas fee from the single coin regardless of the execution status of the transaction. In particular, this means that even if the transaction fails to execute for some reason (such as an execution error) the coins that you provided as gas coins remain smashed after the transaction's execution. - -Gas smashing is an efficient way to perform coin management, and to combine many smaller coins into one single coin that you can use to not only pay for gas fees, but also for other operations in the transaction that smashes the coins. In particular, you can use gas smashing to combine multiple coins to pay for the gas fee of the PTB, and then that same PTB can withdraw from the `GasCoin`. A special argument is available in PTBs that references the coin used to pay gas after IOTA deducts the gas fee from it, which you can use to transfer the remaining IOTA to another address. Because gas smashing happens automatically in a transaction if you provide multiple gas coins, you can combine multiple coins as part of other transactions to perform coin management in parallel with non-coin-management specific transactions. - -IOTA has a maximum of 256 coins that you can smash in a single PTB - the transaction is not processed if the number of gas coins exceeds this amount. Additionally, when you smash gas coins, IOTA deletes all but the first coin. Because of this, there is often a storage rebate associated with the deletion of these coins. As with other storage rebates, you can't use the resulting refund to pay for the gas fee of the transaction (and isn't credited to the coin until after the execution of the transaction), but it might result in a refund after the execution of the transaction. This refund, along with the remaining balance after the transaction's gas fee, resides in the first gas coin you provide in the transaction after execution. - -## Running out of gas with a refund - -Because coins are smashed regardless of the execution status, storage rebates can lead to seemingly odd cases where a transaction can both run out of gas and end up with a negative gas and storage fee (you get a refund from the transaction). As an example of how this might happen, take a transaction `T` that has a gas budget of `5000`, and you provide coins `C1`, `C2`, `C3`, `C4`, `C5` with values of `1000`, `2000`, `3000`, `4000`, and `5000` to pay for gas. If the storage rebate for a coin object is `2000`, and the gas fee for `T` execution is more than the provided gas budget of `5000` (so, `T` runs out of gas), this results in an execution status of `OutOfGas`. The `C1` coin has a balance of `1000` + `2000` + `3000` + `4000` + `5000` - `5000` + `2000` * `4` = `15000` - `5000` + `8000` = `18000` and `T` ends up with a negative gas and storage fee (a refund) of `3000`. - - - \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx b/docs/content/developer/iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx new file mode 100644 index 00000000000..70b171a6459 --- /dev/null +++ b/docs/content/developer/iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx @@ -0,0 +1,294 @@ +--- +description: Learn how to create programmable transaction blocks using the IOTA TypeScript SDK. +--- +import Quiz from '@site/src/components/Quiz'; +import questions from '/json/developer/iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.json'; + +# Build Programmable Transaction Blocks with TypeScript SDK + +This guide illustrates how to build a [programmable transaction block (PTB)](programmable-transaction-blocks.mdx) on IOTA +using the [TypeScript SDK](../../../../references/ts-sdk/typescript/install.mdx). + +This example starts by building a PTB to send IOTA tokens. +To construct transactions, import the `Transaction` class and create a new instance: + +```ts +import { Transaction } from "@iota/iota-sdk"; +const txb = new Transaction(); +``` + +With this instance, you can add transactions to the PTB: + +```ts +// Generate a new coin with a balance of 100, derived from the coins used for gas payment. +// You can specify any balance here. +const [coin] = txb.splitCoins(txb.gas, [txb.pure(100)]); + +// Transfer the newly created coin to a specific address. +txb.transferObjects([coin], txb.pure("0xSomeIOTAAddress")); +``` + +You can also attach multiple transaction commands of the same type to a PTB. +For instance, to process a list of transfers and send coins to each recipient: + +```ts +interface Transfer { + to: string; + amount: number; +} + +// Obtain a list of IOTA transfers to execute: +const transfers: Transfer[] = getTransfers(); + +const txb = new Transaction(); + +// First, split the gas coin into multiple coins: +const coins = txb.splitCoins( + txb.gas, + transfers.map((transfer) => txb.pure(transfer.amount)) +); + +// Then, create a transfer transaction for each coin: +transfers.forEach((transfer, index) => { + txb.transferObjects([coins[index]], txb.pure(transfer.to)); +}); +``` + +After defining the PTB, you can execute it directly with a `signer` using `signAndExecuteTransaction`: + +```ts +signer.signAndExecuteTransaction({ Transaction: txb }); +``` + +## Defining Inputs + +Inputs allow you to provide external values to PTBs, +such as specifying the amount of IOTA to transfer or passing objects into a Move call. + +There are two primary ways to define inputs: + +- For objects: use the `txb.object(objectId)` function to construct an input containing an object reference. +- For pure values: use the `txb.pure(value, type?)` function to create an input for non-object values. + - If the value is a `Uint8Array`, it's treated as raw bytes and used directly. + - If a type is provided, it's used to generate the BCS serialization layout for the value. If not, the type is automatically determined based on the value. + +## Available Transaction Commands + +IOTA supports the following transaction commands: + +### `txb.splitCoins(coin, amounts)` +Creates new coins with specified amounts, split from the provided coin. Returns the coins for use in subsequent transactions. + +#### Example + +`txb.splitCoins(txb.gas, [txb.pure(100), txb.pure(200)])` + +### `txb.mergeCoins(destinationCoin, sourceCoins)` + +Merges the `sourceCoins` into the `destinationCoin`. + +#### Example + +`txb.mergeCoins(txb.object(coin1), [txb.object(coin2), txb.object(coin3)])` + +### `txb.transferObjects(objects, address)` + +Transfer a list of objects to the specified address. + +#### Example + +`txb.transferObjects([txb.object(thing1), txb.object(thing2)], txb.pure(myAddress))` + +### `txb.moveCall({ target, arguments, typeArguments })` + +Executes a Move call and returns whatever the IOTA Move call returns. + +#### Example + +`txb.moveCall({ target: '0x2::devnet_nft::mint', arguments: [txb.pure(name), txb.pure(description), txb.pure(image)] })` + +### `txb.makeMoveVec({ type, elements })` + +Constructs a vector of objects that can be passed into a `moveCall`. This is required since there's no other way to define a vector as an input. + +#### Example + +`txb.makeMoveVec({ elements: [txb.object(id1), txb.object(id2)] })` + +### `txb.publish(modules, dependencies)` + +Publishes a Move package and returns the upgrade capability object. + +## Using Transaction Results as Arguments + +You can pass the result of a transaction command as an argument in subsequent commands. +Each transaction command method on the transaction builder returns a reference to the transaction result. + +```ts +// Split a coin object from the gas object: +const [coin] = txb.splitCoins(txb.gas, [txb.pure(100)]); +// Transfer the resulting coin object: +txb.transferObjects([coin], txb.pure(address)); +``` + +When a transaction command returns multiple results, +you can access a specific result using destructuring or array indices. + +```ts +// Using destructuring (preferred for logical naming): +const [nft1, nft2] = txb.moveCall({ target: "0x2::nft::mint_many" }); +txb.transferObjects([nft1, nft2], txb.pure(address)); + +// Using array indices: +const mintMany = txb.moveCall({ target: "0x2::nft::mint_many" }); +txb.transferObjects([mintMany[0], mintMany[1]], txb.pure(address)); +``` + +## Using the Gas Coin + +With PTBs, you can use the gas payment coin to create coins with a specific balance using [`splitCoin`](programmable-transaction-blocks.mdx#splitcoins). +This is useful for IOTA payments and eliminates the need for upfront coin selection. +You can access the gas coin in a PTB using `txb.gas`, and it's valid as input for any arguments. +However, with `transferObjects`, `txb.gas` must be used by reference. +Practically, this means you can add to the gas coin with `mergeCoins` or borrow it for Move functions with `moveCall`. + +You can also transfer the gas coin using `transferObjects` if you wish to transfer your entire coin balance to another address. + +## Obtaining PTB Bytes + +If you need the PTB bytes instead of signing or executing it, you can use the `build` method on the transaction builder. + +:::tip + +You might need to explicitly call `setSender()` on the PTB to ensure the `sender` field is populated. This is usually done by the signer before signing the transaction but won't happen automatically if you're building the PTB bytes yourself. + +::: + +```ts +const txb = new Transaction(); + +// ... add some transactions... + +await txb.build({ provider }); +``` + +In most cases, building requires your JSON RPC provider to fully resolve input values. + +If you have PTB bytes, you can also convert them back into a `Transaction` class: + +```ts +const bytes = getTransactionBytesFromSomewhere(); +const txb = Transaction.from(bytes); +``` + +## Building Offline + +If you want to build a PTB offline (without a `provider`), you need to fully define all your input values and gas configuration (see the example below). For pure values, you can provide a `Uint8Array` which is used directly in the transaction. For objects, you can use the `Inputs` helper to construct an object reference. + +```ts +import { Inputs } from "@iota/iota-sdk/transactions"; + +// For pure values: +txb.pure(pureValueAsBytes); + +// For owned or immutable objects: +txb.object(Inputs.ObjectRef({ digest, objectId, version })); + +// For shared objects: +txb.object(Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable })); +``` + +You can then omit the `provider` object when calling `build` on the transaction. If any required data is missing, this will throw an error. + +## Configuring Gas Settings + +The transaction builder comes with default behavior for gas logic, including automatically setting the gas price, budget, and selecting coins for gas payment. This behavior can be customized. + +### Setting Gas Price + +By default, the gas price is set to the network's reference gas price. You can explicitly set the gas price of the PTB by calling `setGasPrice` on the transaction builder. + +```ts +txb.setGasPrice(gasPrice); +``` + +### Setting Gas Budget + +By default, the gas budget is automatically derived by executing a dry run of the PTB beforehand. The dry run's gas consumption is then used to determine a balance for the transaction. You can override this behavior by explicitly setting a gas budget for the transaction using `setGasBudget`. + +:::info + +The gas budget is represented in IOTA and should consider the PTB's gas price. + +::: + +```ts +txb.setGasBudget(gasBudgetAmount); +``` + +### Specifying Gas Payment + +By default, the gas payment is automatically determined by the SDK, +selecting all coins at the provided address that are not used as inputs in the PTB. + +[The selected coins will be merged into a single gas coin before executing the PTB](optimizing-gas-with-coin-merging.mdx), +and all but one of the gas objects will be deleted. The gas coin at index 0 will be the coin into which all others are merged. + +```ts +// Ensure that the coins do not overlap with any input objects for the PTB. +txb.setGasPayment([coin1, coin2]); +``` + +### Integrating with dApps and Wallets + +The Wallet Standard interface now supports the `Transaction` kind directly. All `signTransaction` and `signAndExecuteTransaction` calls from dApps to wallets are expected to provide a `Transaction` class. This PTB class can then be serialized and sent to your wallet for execution. + +To serialize a PTB for sending to a wallet, it's recommended to use the `txb.serialize()` function, +which returns an opaque string representation of the PTB +that can be passed from the wallet standard dApp context to your wallet. +This can then be converted back into a `Transaction` using `Transaction.from()`. + +:::tip + +You should not build the PTB from bytes in the dApp code. +Using `serialize` instead of `build` allows you to build the PTB bytes within the wallet itself, +enabling the wallet to perform gas logic and coin selection as needed. + +::: + +```ts +// Within a dApp +const tx = new Transaction(); +wallet.signTransaction({ Transaction: tx }); + +// Wallet standard code: +function handleSignTransaction(input) { + sendToWalletContext({ Transaction: input.Transaction.serialize() }); +} + +// Within your wallet context: +function handleSignRequest(input) { + const userTx = Transaction.from(input.transaction); +} +``` + +## Creating Sponsored PTBs + +The PTB builder supports sponsored PTBs by using the `onlyTransactionKind` flag when building the PTB. + +```ts +const txb = new Transaction(); +// ... add some transactions... + +const kindBytes = await txb.build({ provider, onlyTransactionKind: true }); + +// Construct a sponsored transaction from the kind bytes: +const sponsoredTxb = Transaction.fromKind(kindBytes); + +// Set the necessary sponsored transaction data: +sponsoredTxb.setSender(sender); +sponsoredTxb.setGasOwner(sponsor); +sponsoredTxb.setGasPayment(sponsorCoins); +``` + + \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/ptb/building-ptb.mdx b/docs/content/developer/iota-101/transactions/ptb/building-ptb.mdx deleted file mode 100644 index 3b665c04a0e..00000000000 --- a/docs/content/developer/iota-101/transactions/ptb/building-ptb.mdx +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: Building Programmable Transaction Blocks -description: Using the IOTA TypeScript SDK, you can create programmable transaction blocks to perform multiple commands in a single transaction. ---- -import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/ptb/building-ptb.json'; - -This guide explores creating a programmable transaction block (PTB) on IOTA using the TypeScript SDK. For an overview of what a PTB is, see [Programmable Transaction Blocks](prog-txn-blocks.mdx) in the Concepts section. If you don't already have the IOTA TypeScript SDK, follow the [install instructions](../../../../references/ts-sdk/typescript/install.mdx) on the IOTA TypeScript SDK site. - -This example starts by constructing a PTB to send IOTA. If you are familiar with the legacy IOTA transaction types, this is similar to a `payIota` transaction. To construct transactions, import the `Transaction` class, and construct it: - -```ts -import { Transaction } from "@iota/iota-sdk"; -const txb = new Transaction(); -``` - -Using this, you can then add transactions to this PTB. - -```ts -// Create a new coin with balance 100, based on the coins used as gas payment. -// You can define any balance here. -const [coin] = txb.splitCoins(txb.gas, [txb.pure(100)]); - -// Transfer the split coin to a specific address. -txb.transferObjects([coin], txb.pure("0xSomeIOTAAddress")); -``` - -You can attach multiple transaction commands of the same type to a PTB as well. For example, to get a list of transfers, and iterate over them to transfer coins to each of them: - -```ts -interface Transfer { - to: string; - amount: number; -} - -// Procure a list of some IOTA transfers to make: -const transfers: Transfer[] = getTransfers(); - -const txb = new Transaction(); - -// First, split the gas coin into multiple coins: -const coins = txb.splitCoins( - txb.gas, - transfers.map((transfer) => txb.pure(transfer.amount)) -); - -// Next, create a transfer transaction for each coin: -transfers.forEach((transfer, index) => { - txb.transferObjects([coins[index]], txb.pure(transfer.to)); -}); -``` - -After you have the PTB defined, you can directly execute it with a `signer` using `signAndExecuteTransaction`. - -```ts -signer.signAndExecuteTransaction({ Transaction: txb }); -``` - -## Constructing Inputs - -Inputs are how you provide external values to PTBs. For example, defining an amount of IOTA to transfer, or which object to pass into a Move call, or a shared object. - -There are currently two ways to define inputs: - -- For objects: the `txb.object(objectId)` function is used to construct an input that contains an object reference. -- For pure values: the `txb.pure(value, type?)` function is used to construct an input for a non-object input. - - If value is a `Uint8Array`, then the value is assumed to be raw bytes and is used directly. - - If type is provided, it's used to generate the BCS serialization layout for the value. If not provided, the type is automatically determined based on the value. - -## Available Transactions - -IOTA supports following transaction commands: - -- `txb.splitCoins(coin, amounts)`: Creates new coins with the defined amounts, split from the provided coin. Returns the coins so that it can be used in subsequent transactions. - - Example: `txb.splitCoins(txb.gas, [txb.pure(100), txb.pure(200)])` -- `txb.mergeCoins(destinationCoin, sourceCoins)`: Merges the sourceCoins into the destinationCoin. - - Example: `txb.mergeCoins(txb.object(coin1), [txb.object(coin2), txb.object(coin3)])` -- `txb.transferObjects(objects, address)`: Transfers a list of objects to the specified address. - - Example: `txb.transferObjects([txb.object(thing1), txb.object(thing2)], txb.pure(myAddress))` -- `txb.moveCall({ target, arguments, typeArguments })`: Executes a Move call. Returns whatever the IOTA Move call returns. - - Example: `txb.moveCall({ target: '0x2::devnet_nft::mint', arguments: [txb.pure(name), txb.pure(description), txb.pure(image)] })` -- `txb.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: `txb.makeMoveVec({ elements: [txb.object(id1), txb.object(id2)] })` -- `txb.publish(modules, dependencies)`: Publishes a Move package. Returns the upgrade capability object. - -## Passing Transaction Results As Arguments - -You can use the result of a transaction command as an argument in subsequent transaction commands. Each transaction command method on the transaction builder returns a reference to the transaction result. - -```ts -// Split a coin object off of the gas object: -const [coin] = txb.splitCoins(txb.gas, [txb.pure(100)]); -// Transfer the resulting coin object: -txb.transferObjects([coin], txb.pure(address)); -``` - -When a transaction command returns multiple results, you can access the result at a specific index either using destructuring, or array indexes. - -```ts -// Destructuring (preferred, as it gives you logical local names): -const [nft1, nft2] = txb.moveCall({ target: "0x2::nft::mint_many" }); -txb.transferObjects([nft1, nft2], txb.pure(address)); - -// Array indexes: -const mintMany = txb.moveCall({ target: "0x2::nft::mint_many" }); -txb.transferObjects([mintMany[0], mintMany[1]], txb.pure(address)); -``` - -## Use the Gas Coin - -With PTBs, you can use the gas payment coin to construct coins with a set balance using `splitCoin`. This is useful for IOTA payments, and avoids the need for up-front coin selection. You can use `txb.gas` to access the gas coin in a PTB, and it is valid as input for any arguments; with the exception of `transferObjects`, `txb.gas` must be used by-reference. Practically speaking, this means you can also add to the gas coin with `mergeCoins` or borrow it for Move functions with `moveCall`. - -You can also transfer the gas coin using `transferObjects`, in the event that you want to transfer all of your coin balance to another address. - -## Get PTB Bytes - -If you need the PTB bytes, instead of signing or executing the PTB, you can use the `build` method on the transaction builder itself. - -:::tip - -You might need to explicitly call `setSender()` on the PTB to ensure that the `sender` field is populated. This is normally done by the signer before signing the transaction, but will not be done automatically if you're building the PTB bytes yourself. - -::: - -```ts -const txb = new Transaction(); - -// ... add some transactions... - -await txb.build({ provider }); -``` - -In most cases, building requires your JSON RPC provider to fully resolve input values. - -If you have PTB bytes, you can also convert them back into a `Transaction` class: - -```ts -const bytes = getTransactionBytesFromSomewhere(); -const txb = Transaction.from(bytes); -``` - -## Building Offline - -In the event that you want to build a PTB offline (as in with no `provider` required), you need to fully define all of your input values, and gas configuration (see the following example). For pure values, you can provide a `Uint8Array` which is used directly in the transaction. For objects, you can use the `Inputs` helper to construct an object reference. - -```ts -import { Inputs } from "@iota/iota-sdk/transactions"; - -// For pure values: -txb.pure(pureValueAsBytes); - -// For owned or immutable objects: -txb.object(Inputs.ObjectRef({ digest, objectId, version })); - -// For shared objects: -txb.object(Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable })); -``` - -You can then omit the `provider` object when calling `build` on the transaction. If there is any required data that is missing, this will throw an error. - -## Gas Configuration - -The new transaction builder comes with default behavior for all gas logic, including automatically setting the gas price, budget, and selecting coins to be used as gas. This behavior can be customized. - -### Gas Price - -By default, the gas price is set to the reference gas price of the network. You can also explicitly set the gas price of the PTB by calling `setGasPrice` on the transaction builder. - -```ts -txb.setGasPrice(gasPrice); -``` - -### Budget - -By default, the gas budget is automatically derived by executing a dry-run of the PTB beforehand. The dry run gas consumption is then used to determine a balance for the transaction. You can override this behavior by explicitly setting a gas budget for the transaction, by calling `setGasBudget` on the transaction builder. - -:::info - -The gas budget is represented in IOTA, and should take the gas price of the PTB into account. - -::: - -```ts -txb.setGasBudget(gasBudgetAmount); -``` - -### Gas Payment - -By default, the gas payment is automatically determined by the SDK. The SDK selects all coins at the provided address that are not used as inputs in the PTB. - -The list of coins used as gas payment will be merged down into a single gas coin before executing the PTB, and all but one of the gas objects will be deleted. The gas coin at the 0-index will be the coin that all others are merged into. - -```ts -// NOTE: You need to ensure that the coins do not overlap with any -// of the input objects for the PTB. -txb.setGasPayment([coin1, coin2]); -``` - -### dApp / Wallet Integration - -The Wallet Standard interface has been updated to support the `Transaction` kind directly. All `signTransaction` and `signAndExecuteTransaction` calls from dApps into wallets is expected to provide a `Transaction` class. This PTB class can then be serialized and sent to your wallet for execution. - -To serialize a PTB for sending to a wallet, IOTA recommends using the `txb.serialize()` function, which returns an opaque string representation of the PTB that can be passed from the wallet standard dApp context to your wallet. This can then be converted back into a `Transaction` using `Transaction.from()`. - -:::tip - -You should not build the PTB from bytes in the dApp code. Using `serialize` instead of `build` allows you to build the PTB bytes within the wallet itself. This allows the wallet to perform gas logic and coin selection as needed. - -::: - -```ts -// Within a dApp -const tx = new Transaction(); -wallet.signTransaction({ Transaction: tx }); - -// Your wallet standard code: -function handleSignTransaction(input) { - sendToWalletContext({ Transaction: input.Transaction.serialize() }); -} - -// Within your wallet context: -function handleSignRequest(input) { - const userTx = Transaction.from(input.transaction); -} -``` - -## Sponsored PTBs - -The PTB builder can support sponsored PTBs by using the `onlyTransactionKind` flag when building the PTB. - -```ts -const txb = new Transaction(); -// ... add some transactions... - -const kindBytes = await txb.build({ provider, onlyTransactionKind: true }); - -// Construct a sponsored transaction from the kind bytes: -const sponsoredTxb = Transaction.fromKind(kindBytes); - -// You can now set the sponsored transaction data that is required: -sponsoredTxb.setSender(sender); -sponsoredTxb.setGasOwner(sponsor); -sponsoredTxb.setGasPayment(sponsorCoins); -``` - - \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/ptb/coin-management.mdx b/docs/content/developer/iota-101/transactions/ptb/coin-management.mdx new file mode 100644 index 00000000000..e942cff51f2 --- /dev/null +++ b/docs/content/developer/iota-101/transactions/ptb/coin-management.mdx @@ -0,0 +1,69 @@ +--- +description: Understand how to handle owned coins in IOTA transactions and programmable transaction block development. +--- +import Quiz from '@site/src/components/Quiz'; +import questions from '/json/developer/iota-101/transactions/ptb/coin-management.json'; + +# Managing Owned Coins in IOTA + +In IOTA programming, effectively managing owned coins is essential for seamless transaction execution. +This guide explores the concept of [owned objects](../../objects/object-ownership/address-owned.mdx), +how they differ from traditional account balances, and best practices for coin management in IOTA. + +## Understanding Owned Objects + +IOTA introduces the concept of [address-owned objects](../../objects/object-ownership/address-owned.mdx), +which allows for highly parallelizable transactions and logically maps to assets exclusively owned by an individual. +Coins in IOTA are examples of these owned objects, similar to holding cash rather than maintaining a bank balance. + +Unlike other blockchains that use a single balance per account, +IOTA's approach means you might own multiple coins as separate objects . +This paradigm shift requires a different strategy for managing your assets. + +## The Importance of Coin Management + +Owning multiple coins means you may need to merge them to meet the value required for certain transactions. +[Merging coins](#coin-merging-overview) becomes necessary when the amount needed exceeds any single coin you possess, +making coin management a crucial step in transaction preparation. + +## Using the IOTA SDKs + +The IOTA SDKs for [TypeScript](../../../../references/ts-sdk/typescript/index.mdx) and [Rust](../../../../references/rust-sdk.mdx) handle coin management automatically, +merging coins as needed and assuming sequential transaction execution. +This automation simplifies the process for most use cases where high concurrency isn't a concern. + +## Coin Merging Overview + +IOTA allows you to provide multiple coins as payment in a transaction. +This feature, known as [coin merging](optimizing-gas-with-coin-merging.mdx), +automatically combines these coins into one, +presenting your [programmable transaction blocks (PTBs)](programmable-transaction-blocks) with a single gas coin +usable for various purposes beyond just paying for gas. + +You can supply numerous coins (up to a protocol-defined limit), which are merged into the first coin provided. +After deducting the gas budget, the remaining amount is available within the transaction and returned if unused. + +## Managing Generic Coins + +While coin merging works seamlessly for `Coin` objects used for gas payments, +other coin types require manual management. +PTBs offer commands like [`mergeCoins`](programmable-transaction-blocks#mergecoins) to combine multiple coins, +and [`splitCoins`](programmable-transaction-blocks#splitcoins) to divide them, giving you control over your coin distribution. + +These operations are cost-effective but necessitate awareness of your specific needs and coin holdings. + +## Addressing Concurrency Challenges + +When high concurrency is required, merging all your `Coin` into a single coin can create bottlenecks. +Since each coin is an owned object, it's locked during a transaction, preventing simultaneous use in other transactions. +Attempting to use the same coin concurrently can lead to conflicts and render the coin unusable until the epoch ends. + +To enable concurrent transactions, +consider splitting a coin into multiple coins corresponding to the number of transactions, +or using different coins for each transaction without overlap. + +Concurrency introduces additional complexities, +such as performance impacts from multiple network interactions with full nodes during transaction creation and submission. +Careful planning is essential, and strategies should be tailored to your specific scenarios. + + diff --git a/docs/content/developer/iota-101/transactions/ptb/coin-mgt.mdx b/docs/content/developer/iota-101/transactions/ptb/coin-mgt.mdx deleted file mode 100644 index 1f3c41edc9f..00000000000 --- a/docs/content/developer/iota-101/transactions/ptb/coin-mgt.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Coin Management -description: Because IOTA uses Coins as owned objects for transactions, you need to explicitly manage them in your programmable transaction block development. ---- -import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/ptb/coin-mgt.json'; - -A key concept when programming on IOTA is that of owned objects. Address-owned objects are important in that they allow for highly parallelizable transactions. And they also logically map to assets or resources that someone exclusively owns. Coins are a typical case of owned object usage, with cash being a real-life reference. The owned objects paradigm, however, and particularly as related to coins, is somewhat of a divergence from other blockchains which have a concept of balance. In other words, in other systems, especially account based systems, coins are held in a single location (field) which can be thought of as a balance in a bank account. - -Because IOTA uses owned objects instead of a balance, it is common to own a number of coins, at times even a significant number of them. Some scenarios necessitate merging some or all of those coins into a single object. At times, merging coins together might even be required because the amount necessary to execute a transaction is more than any single coin the sender owns, thus making merging an inevitable step. - -## SDK usage - -The IOTA SDKs ([TypeScript](../../../../references/ts-sdk/typescript/index.mdx) and [Rust](../../../../references/rust-sdk.mdx)) manage coins on your behalf, removing the overhead of having to deal with coin management manually. The SDKs attempt to merge coins whenever possible and assume that transactions are executed in sequence. That's a reasonable assumption with wallet-based transactions and for common scenarios in general. IOTA recommends relying on this feature if you do not have a need for heavy parallel or concurrent execution. - -## Gas Smashing - -When executing a transaction IOTA allows you to provide a number of coins as payment. In other words, the payment can be a vector of coins rather than a single coin. That feature, known as gas smashing, performs merging of coins automatically, and presents the PTBs you write with a single gas coin that can be used for other purposes besides just gas. - -Basically, you can provide as many coins as you want (with a max limit defined in the protocol configuration) and have all of them merged (smashed) into the first coin provided as payment. That coin, minus the gas budget, is then available inside the transaction and can be used in any command. If the coin is unused it is returned to the user. - -Gas smashing is an important feature - and a key concept to understand - to have for the optimal management of coins. See [Gas Smashing](../gas-smashing.mdx) for more details. - -## Generic coins - -Gas smashing works well for `Coin` objects, which is the only coin type that can be used for gas payment. - -Any other coin type requires explicit management from users. PTBs offer a `mergeCoins` command that you can use to combine multiple coins into a single one. And a `splitCoins` as the complementary operation to break them up. - -From a cost perspective, those are very cheap transactions, however they require a user to be aware of their coin distribution and their own needs. - -## Concurrency - -Merging coins, and particularly `Coin`, into a single coin or a very small number of coins might prove problematic in scenarios where heavy or high concurrency is required. - -If you merge all `Coin` into a single one, you would need to sequentially submit every transaction. The coin - being an owned object - would have to be provided with a version and it would be locked by the system when signing a transaction, effectively making it impossible to use it in any other transaction until the one that locked it was executed. Moreover, an attempt to sign multiple transactions with the same coin might result in equivocation and the coin being unusable and locked until the end of the epoch. - -So when you require heavy concurrency, you should first split a coin into as many coins as the number of transactions to execute concurrently. Alternatively, you could provide multiple and different coins (gas smashing) to the different transactions. It is critically important that the set of coins you use in the different transactions has no intersection at all. - -The possible pitfalls in dealing with heavy concurrency are many. Concurrency in transaction execution is not the only performance bottleneck. In creating and submitting a transaction, several round trips with a Full node might be required to discover and fetch the right objects, and to dry run a transaction. Those round trips might affect performance significantly. - -Concurrency is a difficult subject and is beyond the scope of this documentation. You must take maximum care when dealing with coin management in the face of concurrency, and the right strategy is often tied to the specific scenario, rather than universally available. - - \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.mdx b/docs/content/developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.mdx new file mode 100644 index 00000000000..650588d0b2c --- /dev/null +++ b/docs/content/developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.mdx @@ -0,0 +1,64 @@ +--- +description: Learn how to manage multiple IOTA coins by combining them to pay gas fees efficiently. +--- +import Quiz from '@site/src/components/Quiz'; +import questions from '/json/developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.json'; + +# Optimizing Gas Fees with Coin Merging + +In the IOTA blockchain, every transaction incurs a gas fee that must be paid for successful execution. +Coin merging allows you to pay this gas fee using multiple coins combined into one, +rather than relying on a single coin. +This method is particularly useful when you have several coins with small denominations or wish +to reduce the number of individual IOTA coins in your account. +Coin merging enhances coin management, especially when used with the `GasCoin` argument in [programmable transaction blocks (PTBs)](programmable-transaction-blocks). + +## How Coin Merging Works + +When you include multiple coins to cover the gas fee in a transaction, +IOTA automatically merges them into a single coin during execution. +This merging occurs regardless of the individual coin amounts or the specified gas budget, +as long as it falls within the allowed gas limits. +The gas fee is then deducted from this combined coin, +even if the transaction fails to execute (for example, due to an execution error). +This means that after the transaction, the coins you provided for gas remain merged. + +Coin merging is an efficient strategy for managing your coins by consolidating smaller amounts into a single coin. +You can use this merged coin not only to pay for gas fees but also for other operations within the same transaction. +For instance, you can merge multiple coins to cover the gas fee for a PTB, +and then have that PTB withdraw from the `GasCoin`. +PTBs offer a special argument that references the coin used for gas payment after the gas fee has been deducted, +allowing you to transfer the remaining IOTA to another address. +Since coin merging happens automatically when you supply multiple gas coins, +you can combine coin management with other transactions seamlessly. + +It's important to note that IOTA sets a maximum limit of 256 coins that can be merged in a single PTB. +If you attempt to merge more than this number, the transaction will not be processed. +Additionally, during the merging process, all coins except the first one are deleted. +This deletion often results in a storage rebate due to the removal of coin objects. +While you cannot use this rebate to pay for the current transaction's gas fee, +as it isn't credited until after execution, +it may result in a refund after the transaction completes. +The refund and any remaining balance after deducting the gas fee will reside in the first coin +you provided in the transaction. + +## Unexpected Refunds When Running Out of Gas + +Because coins are merged regardless of whether a transaction succeeds, +storage rebates can lead to scenarios where a transaction runs out of gas but still results in a net refund. +For example, suppose you have a transaction `T` with a gas budget of `5000`, +and you provide coins `C1`, `C2`, `C3`, `C4`, and `C5` with values `1000`, `2000`, `3000`, +`4000`, and `5000` respectively to pay for gas. +If each deleted coin object provides a storage rebate of `2000`, +and the actual gas fee for executing `T` exceeds the `5000` gas budget (causing `T` to run out of gas), the execution status will be `OutOfGas`. + +After execution, the balance of coin `C1` would be calculated as: + +- Initial total coin value: `1000 + 2000 + 3000 + 4000 + 5000 = 15,000` +- Gas fee deducted: `-5000` +- Storage rebate from deleted coins: `2000 * 4 = 8000` (since four coins were deleted) +- Final balance: `15,000 - 5000 + 8000 = 18,000` + +In this case, even though the transaction failed due to insufficient gas, you receive a net refund of `3000` (since you end up with `18,000` in `C1`). + + diff --git a/docs/content/developer/iota-101/transactions/ptb/prog-txn-blocks.mdx b/docs/content/developer/iota-101/transactions/ptb/prog-txn-blocks.mdx deleted file mode 100644 index 5a584716642..00000000000 --- a/docs/content/developer/iota-101/transactions/ptb/prog-txn-blocks.mdx +++ /dev/null @@ -1,447 +0,0 @@ ---- -title: Programmable Transaction Blocks -description: Programmable transaction blocks are a group of commands that complete a transaction on IOTA. ---- -import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/ptb/prog-txn-blocks.json'; - -On IOTA, a transaction is more than a basic record of the flow of assets. Transactions on IOTA are composed of a number of commands that execute on inputs to define the result of the transaction. Termed programmable transaction blocks (PTBs), these groups of commands define all user transactions on IOTA. 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. More intricate programming patterns, such as loops, are not supported, however, and in those cases you must publish a new Move package. - -As mentioned, each PTB is comprised of individual transaction commands (sometimes referred to simply as transactions or commands). Each transaction command executes in order, and you can use the results from one transaction command in any subsequent transaction command. The effects, specifically object modifications or transfers, of all transaction commands in a block are applied atomically at the end of the transaction. If one transaction command fails, the entire block fails and no effects from the commands are applied. - -A PTB can perform up to 1,024 unique operations in a single execution, whereas transactions on traditional blockchains would require 1,024 individual executions to accomplish the same result. The structure also promotes cheaper gas fees. The cost of facilitating individual transactions is always more than the cost of those same transactions blocked together in a PTB. - -The remainder of this topic covers the semantics of the execution of the transaction commands. It assumes familiarity with the IOTA object model and the Move language. For more information on those topics, see the following documents: - -- [Object model](../../objects/object-model.mdx) -- [Move Concepts](../../move-overview/move-overview.mdx) - -## Transaction type - -There are two parts of a PTB that are relevant to execution semantics. Other transaction information, such as the transaction sender or the gas limit, might be referenced but are out of scope. The structure of a PTB is: - -```rust -{ - inputs: [Input], - commands: [Command], -} -``` - -Looking closer at the two main components: -- The `inputs` value is a vector of arguments, `[Input]`. These arguments are either objects or pure values that you can use in the commands. The objects are either owned by the sender or are shared/immutable objects. The pure values represent simple Move values, such as `u64` or `String` values, which you can be construct purely from their bytes. For historical reasons, `Input` is `CallArg` in the Rust implementation. -- The `commands` value is a vector of commands, `[Command]`. The possible commands are: - - `TransferObjects` sends multiple (one or more) objects to a specified address. - - `SplitCoins` splits off multiple (one or more) coins from a single coin. It can be any `iota::coin::Coin<_>` object. - - `MergeCoins` merges multiple (one or more) coins into a single coin. Any `iota::coin::Coin<_>` objects can be merged, as long as they are all of the same type. - - `MakeMoveVec` creates a vector (potentially empty) of Move values. This is used primarily to construct vectors of Move values to be used as arguments to `MoveCall`. - - `MoveCall` invokes either an `entry` or a `public` Move function in a published package. - - `Publish` creates a new package and calls the `init` function of each module in the package. - - `Upgrade` upgrades an existing package. The upgrade is gated by the `iota::package::UpgradeCap` for that package. - -## Inputs and results - -Inputs and results are the two types of values you can use in transaction commands. Inputs are the values that are provided to the PTB, and results are the values that are produced by the PTB commands. The inputs are either objects or simple Move values, and the results are arbitrary Move values (including objects). - -The inputs and results can be seen as populating an array of values. For inputs, there is a single array, but for results, there is an array for each individual transaction command, creating a 2D-array of result values. You can access these values by borrowing (mutably or immutably), by copying (if the type permits), or by moving (which takes the value out of the array without re-indexing). - -### 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. This section describes information or data the IOTA network needs when specifying the list of inputs, `[Input]`. Each `Input` is either an object, `Input::Object(ObjectArg)`, which contains the necessary metadata to specify to 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](../../objects/object-ownership/object-ownership.mdx). 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 number (also known as its version), and the digest of the object's data. - -If an object is shared, then use `Object::SharedObject { id: ObjectID, initial_shared_version: SequenceNumber, mutable: bool }`. Unlike `ImmOrOwnedObject`, a shared objects version and digest are determined by the network's consensus protocol. The `initial_shared_version` is 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, the `mutable` flag indicates whether the object is to be used mutably in this transaction. In the case where the `mutable` flag is set to `false`, 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 via the `TransferObjects` command or the `iota::transfer::transfer` function, then use `ObjectArg::Receiving(ObjectID, SequenceNumber, ObjectDigest)`. The object data is the same as for the `ImmOrOwnedObject` case. - -For pure inputs, the only data provided is the [BCS](/references/framework/iota-framework/bcs.mdx) bytes, which are deserialized to construct Move values. Not all Move values can be constructed from BCS bytes. This means that even if the bytes match the expected layout for a given Move type, they cannot be deserialized into a value of that type unless the type is one of the types permitted for `Pure` values. 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::String`) or UTF8 string (`std::string::String`). In either case, the bytes are validated to be a valid string with the respective encoding. -- An object ID `iota::object::ID`. -- A vector, `vector`, where `T` is a valid type for a pure input, checked recursively. -- An option, `std::option::Option`, where `T` is a valid type for a pure input, checked recursively. - -Interestingly, the 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. See the [Arguments section](#arguments) for more details. - -### Results - -Each transaction command produces a (possibly empty) array of values. 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. The specifics for each command can be found in the section for that command, but in summary: - -- `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 is `iota::coin::Coin` where the specific coin type `T` matches the coin being split. -- `Publish`: returns the upgrade capability, `iota::package::UpgradeCap`, for the newly published package. -- `Upgrade`: returns the upgrade receipt, `iota::package::UpgradeReceipt`, for the upgraded package. -- `TransferObjects` and `MergeCoins` do not produce any results (an empty result vector). - -### Argument structure and usage - -Each command takes `Argument`s that specify the input or result being used. The usage (by-reference or by-value) is inferred based on the type of the argument and the expected argument of the command. First, examine the structure of the `Argument` enum. - -- `Input(u16)` is an input argument, where the `u16` is the index of the input in the input vector. For example, given an input vector of `[Object1, Object2, Pure1, Object3]`, `Object1` is accessed with `Input(0)` and `Pure1` is accessed with `Input(2)`. -- `GasCoin` is a special input argument representing the object for the `IOTA` coin used to pay for gas. It is kept separate from the other inputs because the gas coin is always present in each transaction and has special restrictions (see below) not present for other inputs. Additionally, the gas coin being separate makes its usage explicit, which is helpful for sponsored transactions where the sponsor might not want the sender to use the gas coin for anything other than gas. - - The gas coin cannot be taken by-value except with the `TransferObjects` command. If you need an owned version of the gas coin, you can first use `SplitCoins` to split off a single coin. - - This limitation exists to make it easy for the remaining gas to be returned to the coin at the end of execution. In other words, if the gas coin was wrapped or deleted, then there would not be an obvious spot for the excess gas to be returned. See the [Execution section](#execution) for more details. - -- `NestedResult(u16, u16)` uses the value from a previous command. The first `u16` is the index of the command in the command vector, and the second `u16` is the index of the result in the result vector of that command. For example, given a command vector of `[MoveCall1, MoveCall2, TransferObjects]` where `MoveCall2` has a result vector of `[Value1, Value2]`, `Value1` would be accessed with `NestedResult(1, 0)` and `Value2` would be accessed with `NestedResult(1, 1)`. -- `Result(u16)` is a special form of `NestedResult` where `Result(i)` is roughly equivalent to `NestedResult(i, 0)`. Unlike `NestedResult(i, 0)`, `Result(i)`, however, this errors if the result array at index `i` is empty or has more than one value. The ultimate intention of `Result` is to allow accessing the entire result array, but that is not yet supported. So in its current state, `NestedResult` can be used instead of `Result` in all circumstances. - -## Execution - -For the execution of PTBs, the input vector is populated by the input objects or pure value bytes. The transaction commands are then executed in order, and the results are stored in the result vector. Finally, the effects of the transaction are applied atomically. The following sections describe each aspect of execution in greater detail. - -### Start of execution - -At the beginning of execution, the PTB runtime takes the already loaded input objects and loads them into the input array. The objects are already verified by the network, checking rules like existence and valid ownership. The pure value bytes are also loaded into the array but not validated until usage. - -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 `IOTA`) 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. - -### Executing a transaction command - -Each transaction command is then executed in order. First, examine the rules around arguments, which are shared by all commands. - -#### Arguments - -You can use each argument by-reference or by-value. The usage is based on the type of the argument and the type signature of the command. - - If the signature expects an `&mut T`, the runtime checks the argument has type `T` and it is then mutably borrowed. - - If the signature expects an `&T`, the runtime checks the argument has type `T` and it is then immutably borrowed. - - If the signature expects a `T`, the runtime checks the argument has type `T` and it is copied if `T: copy` and moved otherwise. No object in IOTA has `copy` because the unique ID field `iota::object::UID` present in all objects does not have the `copy` ability. - -The transaction fails if an argument is used in any form after being moved. There is no way to restore an argument to its position (its input or result index) after it is moved. - -If an argument is copied but does not have the `drop` ability, then the last usage is inferred to be a move. As a result, if an argument has `copy` and does not have `drop`, the last usage _must_ be by value. Otherwise, the transaction will fail because a value without `drop` has not been used. - -The borrowing of arguments has other rules to ensure unique safe usage of an argument by reference. If an argument is: - - Mutably borrowed, there must be no outstanding borrows. Duplicate borrows with an outstanding mutable borrow could lead to dangling references (references that point to invalid memory). - - Immutably borrowed, there must be no outstanding mutable borrows. Duplicate immutable borrows are allowed. - - Moved, there must be no outstanding borrows. Moving a borrowed value would dangle those outstanding borrows, making them unsafe. - - Copied, there can be outstanding borrows, mutable or immutable. While it might lead to some unexpected results in some cases, there is no safety concern. - -Object inputs have the type of their object `T` as you might expect. However, for `ObjectArg::Receiving` inputs, the object type `T` is instead wrapped as `iota::transfer::Receiving`. This is because the object is not owned by the sender, but instead by another object. And to prove ownership with that parent object, you call the `iota::transfer::receive` function to remove the wrapper. - -The `GasCoin` has special restrictions on being used by-value (moved). You can only use it by-value with the `TransferObjects` command. - -Shared objects also have restrictions on being used by-value. These restrictions exist to ensure that at the end of the transaction the shared object is either still shared or deleted. A shared object cannot be unshared (having the owner changed) and it cannot be wrapped. A shared object: - - Marked as not `mutable` (being used read-only) cannot be used by value. - - Cannot be transferred or frozen. These checks are not done dynamically, however, but rather at the end of the transaction only. For example, `TransferObjects` succeeds if passed a shared object, but at the end of execution the transaction fails. - - Can be wrapped and can become a dynamic field transiently, but by the end of the transaction it must be re-shared or deleted. - -Pure values are not type checked until their usage. When checking if a pure value has type `T`, it is checked whether `T` is a valid type for a pure value (see the previous list). If it is, the bytes are then 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. - -#### `TransferObjects` - -The command has the form `TransferObjects(ObjectArgs, AddressArg)` where `ObjectArgs` is a vector of objects and `AddressArg` is the address the objects are sent to. - -- Each argument `ObjectArgs: [Argument]` must be an object, however, the objects do not need to have the same type. -- The address argument `AddressArg: Argument` must be an address, which could come from a `Pure` input or a result. -- All arguments, objects and address, are taken by value. -- The command does not produce any results (an empty result vector). -- While the signature of this command cannot be expressed in Move, you can think of it roughly as having the signature `(vector, address): ()` where `forall T: key + store. T` is indicating that the `vector` is a heterogeneous vector of objects. - -#### `SplitCoins` - -The command has the form `SplitCoins(CoinArg, AmountArgs)` where `CoinArg` is the coin being split and `AmountArgs` is a vector of amounts to split off. - -- When the transaction is signed, the network verifies that the `ToMergeArgs` is non-empty. -- The coin argument `CoinArg: Argument` must be a coin of type `iota::coin::Coin` where `T` is the type of the coin being split. It can be any coin type and is not limited to `IOTA` coins. -- The amount arguments `AmountArgs: [Argument]` must be `u64` values, which could come from a `Pure` input or a result. -- The coin argument `CoinArg` is taken by mutable reference. -- The amount arguments `AmountArgs` are taken by value (copied). -- The result of the command is a vector of coins, `iota::coin::Coin`. The coin type `T` is the same as the coin being split, and the number of results matches the number of arguments -- For a rough signature expressed in Move, it is similar to a function `(coin: &mut iota::coin::Coin, amounts: vector): vector>` where the result `vector` is guaranteed to have the same length as the `amounts` vector. - -#### `MergeCoins` - -The command has the form `MergeCoins(CoinArg, ToMergeArgs)` where the `CoinArg` is the target coin in which the `ToMergeArgs` coins are merged into. In other words, you merge multiple coins (`ToMergeArgs`) into a single coin (`CoinArg`). - -- When the transaction is signed, the network verifies that the AmountArgs is non-empty. -- The coin argument `CoinArg: Argument` must be a coin of type `iota::coin::Coin` where `T` is the type of the coin being merged. It can be any coin type and is not limited to `IOTA` coins. -- The coin arguments `ToMergeArgs: [Argument]` must be `iota::coin::Coin` values where the `T` is the same type as the `CoinArg`. -- The coin argument `CoinArg` is taken by mutable reference. -- The merge arguments `ToMergeArgs` are taken by value (moved). -- The command does not produce any results (an empty result vector). -- For a rough signature expressed in Move, it is similar to a function `(coin: &mut iota::coin::Coin, to_merge: vector>): ()` - -#### `MakeMoveVec` - -The command has the form `MakeMoveVec(VecTypeOption, Args)` where `VecTypeOption` is an optional argument specifying the type of the elements in the vector being constructed and `Args` is a vector of arguments to be used as elements in the vector. - -- When the transaction is signed, the network verifies that if that the type must be specified for an empty vector of `Args`. -- The type `VecTypeOption: Option` is an optional argument specifying the type of the elements in the vector being constructed. The `TypeTag` is a Move type for the elements in the vector, i.e. the `T` in the produced `vector`. - - The type does not have to be specified for an object vector--when `T: key`. - - The type _must_ be specified if the type is not an object type or when the vector is empty. -- The arguments `Args: [Argument]` are the elements of the vector. The arguments can be any type, including objects, pure values, or results from previous commands. -- The arguments `Args` are taken by value. Copied if `T: copy` and moved otherwise. -- The command produces a _single_ result of type `vector`. The elements of the vector cannot then be accessed individually using `NestedResult`. Instead, the entire vector must be used as an argument to another command. If you wish to access the elements individually, you can use the `MoveCall` command and do so inside of Move code. -- While the signature of this command cannot be expressed in Move, you can think of it roughly as having the signature `(T...): vector` where `T...` indicates a variadic number of arguments of type `T`. - -#### `MoveCall` - -This command has the form `MoveCall(Package, Module, Function, TypeArgs, Args)` where `Package::Module::Function` combine to specify the Move function being called, `TypeArgs` is a vector of type arguments to that function, and `Args` is a vector of arguments for the Move function. - -- The package `Package: ObjectID` is the Object ID of the package containing the module being called. -- The module `Module: String` is the name of the module containing the function being called. -- The function `Function: String` is the name of the function being called. -- The type arguments `TypeArgs: [TypeTag]` are the type arguments to the function being called. They must satisfy the constraints of the type parameters for the function. -- The arguments `Args: [Argument]` are the arguments to the function being called. The arguments must be valid for the parameters as specified in the function's signature. -- Unlike the other commands, the usage of the arguments and the number of results are dynamic in that they both depend on the signature of the Move function being called. - -#### `Publish` - -The command has the form `Publish(ModuleBytes, TransitiveDependencies)` where `ModuleBytes` are the bytes of the module being published and `TransitiveDependencies` is a vector of package Object ID dependencies to link against. - -When the transaction is signed, the network verifies that the `ModuleBytes` are not empty. The module bytes `ModuleBytes: [[u8]]` contain the bytes of the modules being published with each `[u8]` element is a module. - -The transitive dependencies `TransitiveDependencies: [ObjectID]` are the Object IDs of the packages that the new package depends on. While each module indicates the packages used as dependencies, the transitive object IDs must be provided to select the version of those packages. In other words, these object IDs are used to select the version of the packages marked as dependencies in the modules. - -After the modules in the package are verified, the `init` function of each module is called in same order as the module byte vector `ModuleBytes`. - -The command produces a single result of type `iota::package::UpgradeCap`, which is the upgrade capability for the newly published package. - -#### `Upgrade` - -The command has the form `Upgrade(ModuleBytes, TransitiveDependencies, Package, UpgradeTicket)`, where the `Package` indicates the object ID of the package being upgraded. The `ModuleBytes` and `TransitiveDependencies` work similarly as the `Publish` command. - -For details on the `ModuleBytes` and `TransitiveDependencies`, see the [`Publish` command](#publish). Note though, that no `init` functions are called for the upgraded modules. - -The `Package: ObjectID` is the Object ID of the package being upgraded. The package must exist and be the latest version. - -The `UpgradeTicket: iota::package::UpgradeTicket` is the upgrade ticket for the package being upgraded and is generated from the `iota::package::UpgradeCap`. The ticket is taken by value (moved). - -The command produces a single result type `iota::package::UpgradeReceipt` which provides proof for that upgrade. For more details on upgrades, see [Upgrading Packages](../../move-overview/package-upgrades/upgrade.mdx). - -### End of execution - -At the end of execution, the remaining values are checked and effects for the transaction are calculated. - -For inputs, the following checks are performed: - - Any remaining immutable or readonly input objects are skipped since no modifications have been made to them. - - Any remaining mutable input objects are returned to their original owners--if they were shared they remain shared, if they were owned they remain owned. - - Any remaining pure input values are dropped. Note that pure input values must have `copy` and `drop` since all permissible types for those values have `copy` and `drop`. - - For any shared object you must also check that it has only been deleted or re-shared. Any other operation (wrap, transfer, freezing, and so on) results in an error. - -For results, the following checks are performed: - - Any remaining result with the `drop` ability is dropped. - - If the value has `copy` but not `drop`, it's last usage must have been by-value. In that way, it's last usage is treated as a move. - - Otherwise, an error is given because there is an unused value without `drop`. - -Any remaining IOTA deducted from the gas coin at the beginning of execution is returned to the coin, even if the owner has changed. In other words, the maximum possible gas is deducted at the beginning of execution, and then the unused gas is returned at the end of execution (all in IOTA). Because you can take the gas coin only by-value with `TransferObjects`, it will not have been wrapped or deleted. - -The total effects (which contain the created, mutated, and deleted objects) are then passed out of the execution layer and are applied by the IOTA network. - -## Example - -Let's walk through an example of a PTB's execution. While this example is not exhaustive in demonstrating all the rules, it does show the general flow of execution. - -Suppose you want to buy two items from a marketplace costing `100 NANOS`. You keep one for yourself, and then send the object and the remaining coin to a friend at address `0x808`. You can do that all in one PTB: - -```rust -{ - inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - Object(SharedObject { /* Marketplace shared object */ id: market_id, ... }), - Pure(/* 100u64 BCS bytes */ ...), - ] - commands: [ - SplitCoins(GasCoin, [Input(2)]), - MoveCall("some_package", "some_marketplace", "buy_two", [], [Input(1), NestedResult(0, 0)]), - TransferObjects([GasCoin, NestedResult(1, 0)], Input(0)), - MoveCall("iota", "tx_context", "sender", [], []), - TransferObjects([NestedResult(1, 1)], NestedResult(3, 0)), - ] -} -``` - -The inputs include the friend's address, the marketplace object, and the value for the coin split. For the commands, split off the coin, call the market place function, send the gas coin and one object, grab your address (via `iota::tx_context::sender`), and then send the remaining object to yourself. For simplicity, the documentation refers to the package names by name, but note that in practice they are referenced by the package's Object ID. - -To walk through this, first look at the memory locations, for the gas object, inputs, and results - -```rust -Gas Coin: iota::coin::Coin { id: gas_coin, balance: iota::balance::Balance { value: 1_000_000u64 } } -Inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - some_package::some_marketplace::Marketplace { id: market_id, ... }, - Pure(/* 100u64 BCS bytes */ ...), -] -Results: [] -``` - -Here you have two objects loaded so far, the gas coin with a value of `1_000_000u64` and the marketplace object of type `some_package::some_marketplace::Marketplace` (these names and representations are shortened for simplicity going forward). The pure arguments are not loaded, and are present as BCS bytes. - -Note that while gas is deducted at each command, that aspect of execution is not demonstrated in detail. - -### Before commands: start of execution - -Before execution, remove the gas budget from the gas coin. Assume a gas budget of `500_000` so the gas coin now has a value of `500_000u64`. - -```rust -Gas Coin: Coin { id: gas_coin, ... value: 500_000u64 ... } // The maximum gas is deducted -Inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - Marketplace { id: market_id, ... }, - Pure(/* 100u64 BCS bytes */ ...), -] -Results: [] -``` - -Now you can execute the commands. - -### Command 0: `SplitCoins` - -The first command `SplitCoins(GasCoin, [Input(2)])` accesses the gas coin by mutable reference and loads the pure argument at `Input(2)` as a `u64` value of `100u64`. Because `u64` has the `copy` ability, you do not move the `Pure` input at `Input(2)`. Instead, the bytes are copied out. - -For the result, a new coin object is made. - -This gives us updated memory locations of - -```rust -Gas Coin: Coin { id: gas_coin, ... value: 499_900u64 ... } -Inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - Marketplace { id: market_id, ... }, - Pure(/* 100u64 BCS bytes */ ...), -] -Results: [ - [Coin { id: new_coin, value: 100u64 ... }], // The result of SplitCoins -], -``` - -### Command 1: `MoveCall` - -Now the command, `MoveCall("some_package", "some_marketplace", "buy_two", [], [Input(1), NestedResult(0, 0)])`. Call the function `some_package::some_marketplace::buy_two` with the arguments `Input(1)` and `NestedResult(0, 0)`. To determine how they are used, you need to look at the function's signature. For this example, assume the signature is - -```rust -entry fun buy_two( - marketplace: &mut Marketplace, - coin: Coin, - ctx: &mut TxContext, -): (Item, Item) -``` -where `Item` is the type of the two objects being sold. - -Since the `marketplace` parameter has type `&mut Marketplace`, use `Input(1)` by mutable reference. Assume some modifications are being made into the value of the `Marketplace` object. However, the `coin` parameter has type `Coin`, so use `NestedResult(0, 0)` by value. The `TxContext` input is automatically provided by the runtime. - -This gives updated memory locations, where `_` indicates the object has been moved. - -```rust -Gas Coin: Coin { id: gas_coin, ... value: 499_900u64 ... } -Inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - Marketplace { id: market_id, ... }, // Any mutations are applied - Pure(/* 100u64 BCS bytes */ ...), -] -Results: [ - [ _ ], // The coin was moved - [Item { id: id1 }, Item { id: id2 }], // The results from the Move call -], -``` - -Assume that `buy_two` deletes its `Coin` object argument and transfers the `Balance` into the `Marketplace` object. - -### Command 2: `TransferObjects` - -`TransferObjects([GasCoin, NestedResult(1, 0)], Input(0))` transfers the gas coin and first item to the address at `Input(0)`. All inputs are by value, and the objects do not have `copy` so they are moved. While no results are given, the ownership of the objects is changed. This cannot be seen in the memory locations, but rather in the transaction effects. - -You now have updated memory locations of - -```rust -Gas Coin: _ // The gas coin is moved -Inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - Marketplace { id: market_id, ... }, - Pure(/* 100u64 BCS bytes */ ...), -] -Results: [ - [ _ ], - [ _ , Item { id: id2 }], // One item was moved - [], // No results from TransferObjects -], -``` - -### Command 3: `MoveCall` - -Make another Move call, this one to `iota::tx_context::sender` with the signature - -```rust -public fun sender(ctx: &TxContext): address -``` - -While you could have just passed in the sender's address as a `Pure` input, this example demonstrates calling some of the additional utility of PTBs; while this function is not an `entry` function, you can call the `public` function, too, because you can provide all of the arguments. In this case, the only argument, the `TxContext`, is provided by the runtime. The result of the function is the sender's address. Note that this value is not treated like the `Pure` inputs--the type is fixed to `address` and it cannot be deserialized into a different type, even if it has a compatible BCS representation. - -You now have updated memory locations of - -```rust -Gas Coin: _ -Inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - Marketplace { id: market_id, ... }, - Pure(/* 100u64 BCS bytes */ ...), -] -Results: [ - [ _ ], - [ _ , Item { id: id2 }], - [], - [/* senders address */ ...], // The result of the Move call -], -``` - -### Command 4: `TransferObjects` - -Finally, transfer the remaining item to yourself. This is similar to the previous `TransferObjects` command. You are using the last `Item` by-value and the sender's address by-value. The item is moved because `Item` does not have `copy`, and the address is copied because `address` does have `copy`. - -The final memory locations are - -```rust -Gas Coin: _ -Inputs: [ - Pure(/* @0x808 BCS bytes */ ...), - Marketplace { id: market_id, ... }, - Pure(/* 100u64 BCS bytes */ ...), -] -Results: [ - [ _ ], - [ _ , _ ], - [], - [/* senders address */ ...], - [], // No results from TransferObjects -], -``` - -### After commands: end of execution - -At the end of execution, the runtime checks the remaining values, which are the three inputs and -the sender's address. The following summarizes the checks performed before effects are given: - -- Any remaining input objects are marked as being returned to their original owners. - - The gas coin has been Moved. And the `Marketplace` keeps the same owner, which is shared. -- All remaining values must have `drop`. - - The Pure inputs have `drop` because any type they can instantiate has `drop`. - - The sender's address has `drop` because the primitive type `address` has `drop`. - - All other results have been moved. -- Any remaining shared objects must have been deleted or re-shared. - - The `Marketplace` object was not moved, so the owner remains as shared. - -After these checks are performed, generate the effects. - -- The coin split off from the gas coin, `new_coin`, does not appear in the effects because it was created and deleted in the same transaction. -- The gas coin and the item with `id1` are transferred to `0x808`. - - The gas coin is mutated to update its balance. The remaining gas of the maximum budget of `500_000` is returned to the gas coin even though the owner has changed. - - The `Item` with `id1` is a newly created object. -- The item with `id2` is transferred to the sender's address. - - The `Item` with `id2` is a newly created object. -- The `Marketplace` object is returned, remains shared, and it's mutated. - - The object remains shared but its contents are mutated. - - \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/ptb/programmable-transaction-blocks-overview.mdx b/docs/content/developer/iota-101/transactions/ptb/programmable-transaction-blocks-overview.mdx new file mode 100644 index 00000000000..eee50e55c49 --- /dev/null +++ b/docs/content/developer/iota-101/transactions/ptb/programmable-transaction-blocks-overview.mdx @@ -0,0 +1,24 @@ +--- +description: Learn about Programmable Transaction Blocks (PTBs) in IOTA and how they enhance smart contract efficiency. +--- +# Programmable Transaction Blocks Overview + +[Programmable Transaction Blocks (PTBs)](programmable-transaction-blocks) are fundamental elements within the IOTA ecosystem. Understanding how to effectively use PTBs is crucial for developing efficient and cost-effective smart contracts. + +## Building PTBs with the TypeScript SDK + +To truly harness the capabilities of PTBs, practical experience is key. Utilizing tools like the [IOTA TypeScript SDK](../../../../references/ts-sdk/typescript/index.mdx) allows you to explore the power and flexibility that PTBs offer. + +Dive deeper into this topic in [Building Programmable Transaction Blocks](building-programmable-transaction-blocks-ts-sdk). + +## Navigating Coin Management + +In IOTA, `Coin` objects are distinct because they are [owned objects](../../objects/object-ownership/address-owned.mdx), setting them apart from coins on other blockchains. Whether you're handling IOTA for gas payments or managing generic coins, a solid grasp of coin management is essential. Smart contracts typically use standard patterns to accept coins, so your PTBs must interact correctly with these contracts to facilitate successful transactions. + +Explore this subject further in [Coin Management](coin-management). + +## Simulating References with the Borrow Module + +The [`borrow` module](../../../../references/framework/iota-framework/borrow.mdx) in the IOTA framework offers valuable features for working with objects by reference within your PTBs. + +Learn how to implement these features in [Simulating References](simulating-references.mdx). diff --git a/docs/content/developer/iota-101/transactions/ptb/programmable-transaction-blocks.mdx b/docs/content/developer/iota-101/transactions/ptb/programmable-transaction-blocks.mdx new file mode 100644 index 00000000000..1c8536d0f96 --- /dev/null +++ b/docs/content/developer/iota-101/transactions/ptb/programmable-transaction-blocks.mdx @@ -0,0 +1,485 @@ +--- +description: Learn how to execute multiple commands in a single IOTA transaction using programmable transaction blocks. +--- +import Quiz from '@site/src/components/Quiz'; +import questions from '/json/developer/iota-101/transactions/ptb/programmable-transaction-blocks.json'; + +# Use Programmable Transaction Blocks + +Programmable transaction blocks (PTBs) let you execute multiple commands within a single IOTA transaction. +They enable you to call multiple Move functions, manage your objects, +and handle your coins—all without needing to publish a new Move package. +While PTBs are ideal for automation and transaction builders, +they don't support complex programming patterns like loops. +For such cases, you'll need to [publish a new Move package](../../../getting-started/publish.mdx). + +Each PTB is made up of individual transaction commands (also known as transactions or commands). +These commands execute sequentially, and you can use the results from one command in any subsequent command. +The effects of all commands—specifically object modifications or transfers—are applied atomically at the end of the transaction. +**If any command fails, the entire PTB fails, and none of the effects are applied.** + +Using PTBs, you can perform up to 1,024 unique operations in a single execution. +In contrast, traditional blockchains would require 1,024 individual executions to achieve the same result. +This structure not only enhances efficiency but also reduces gas fees, +as grouping transactions in a PTB is more cost-effective than processing them individually. + +This guide explains how PTBs work and how to use them effectively. +It assumes you're familiar with the IOTA [object model](../../objects/object-model.mdx) and the [Move programming language]](../../move-overview/move-overview.mdx). + +## Understanding PTB Components + +A PTB consists of two main components: + +```rust +{ + inputs: [Input], + commands: [Command], +} +``` + +- **Inputs**: A list of arguments `[Input]`, which are either objects or pure values used in the commands. +- Objects can be owned by you or be shared/immutable. Pure values are simple Move values like `u64` or `String`. + +- **Commands**: A list of commands `[Command]` that specify the actions to execute. Possible commands include: + + - [`TransferObjects`](#transferobjects): Send one or more objects to a specified address. + - [`SplitCoins`](#splitcoins): Split a single coin into multiple coins. + - [`MergeCoins`](#mergecoins): Combine multiple coins into one coin. + - [`MakeMoveVec`](#makemovevec): Create a vector of Move values, primarily used as arguments for `MoveCall`. + - [`MoveCall`](#movecall): Invoke an `entry` or `public` Move function in a published package. + - [`Publish`](#publish): Create a new package and call the `init` function of each module. + - [`Upgrade`](#upgrade): Upgrade an existing package, gated by [`iota::package::UpgradeCap`](../../../../references/framework/iota-framework/package.mdx#resource-upgradecap). + +### Inputs and Results + +**Inputs** are the values you provide to the PTB, either as objects or pure values. **Results** are the values produced by the commands within the PTB. You can access these values by borrowing, copying (if allowed), or moving them. + +#### Inputs + +Inputs are categorized as objects or pure values: + +- **Object Inputs**: Include the necessary metadata to specify the object being used. Depending on the object's ownership type, the metadata differs: + + - **Owned or Immutable Objects**: Use `ObjectArg::ImmOrOwnedObject(ObjectID, SequenceNumber, ObjectDigest)`. + - **Shared Objects**: Use `ObjectArg::SharedObject { id: ObjectID, initial_shared_version: SequenceNumber, mutable: bool }`. A shared objects version and digest are determined by the network's consensus protocol. The `mutable` flag indicates whether the object is to be used mutably in this transaction + - **Objects Owned by Other Objects**: Use `ObjectArg::Receiving(ObjectID, SequenceNumber, ObjectDigest)`. + +- **Pure Inputs**: Provide [BCS](../../../../references/framework/iota-framework/bcs.mdx) bytes deserialized into Move values. Only certain types are allowed, such as primitive types, strings, object IDs, vectors, and options. 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::String`) or UTF8 string (`std::string::String`). In either case, the bytes are validated to be a valid string with the respective encoding. + - An object ID `iota::object::ID`. + - A vector, `vector`, where `T` is a valid type for a pure input, checked recursively. + - An option, `std::option::Option`, where `T` is a valid type for a pure input, checked recursively. + +#### Results + +Each command may produce a (possibly empty) array of results. The number and types of results depend on the command: + +- `MoveCall`: Returns results based on the Move function called. +- `SplitCoins`: Produces coins from a single [coin](../../../../references/framework/iota-framework/coin.mdx). +- `Publish`: Returns the [upgrade capability](../../../../references/framework/iota-framework/package.mdx#resource-upgradecap) for the new package. +- `Upgrade`: Returns the [upgrade receipt](../../../../references/framework/iota-framework/package.mdx#struct-upgradereceipt) for the upgraded package. +- `TransferObjects` and `MergeCoins`: Do not produce results. + +### Using Arguments in Commands + +Commands accept `Argument`s to specify inputs or results: + +- `Input(u16)`: Refers to an input by its index in the input list, where the `u16` is the index of the input in the input vector. + - `GasCoin`: Represents the [IOTA coin](../../../../references/framework/iota-framework/coin.mdx) used for gas payment. It cannot be taken by-value except with `TransferObjects`. To get an owned version, use `SplitCoins` to create a new coin. + + This limitation ensures that any remaining gas is returned to the gas coin at the end of execution. + If the gas coin were wrapped or deleted, there would be no place to return the excess gas. + +- `NestedResult(u16, u16)`: Uses a value from a previous command's results. The first `u16` is the command index, and the second is the result index within that command. +- `Result(u16)`: Equivalent to `NestedResult(i, 0)`, but errors if the result array is empty or has more than one value. + +## Executing Programmable Transaction Blocks + +When you execute a PTB, the process involves three main steps: + +1. **[Initializing Inputs](#1-initializing-inputs)**: The runtime loads input objects and pure value bytes into the input array. +2. **Executing Commands**: Transaction commands are executed sequentially, and results are stored. +3. **Applying Effects**: The transaction's effects are applied atomically at the end. + +Let's explore each of these steps in detail. + +### 1. Initializing Inputs + +At the start, the PTB runtime loads the input objects into the input array. +The network has already verified these objects for existence and valid ownership. +Pure value bytes are also loaded but are not validated until they are used. + +:::note Gas + +The gas coin is important at this stage. +The maximum gas budget (in IOTA tokens) is withdrawn from the gas coin at the beginning. +Any unused gas is returned to the gas coin at the end of execution, even if the coin's ownership has changed. + +::: + +### 2. Executing Commands + +Each transaction command is executed in order. +Before diving into specific commands, it's crucial to understand how arguments are used. + +#### Using Arguments + +Arguments can be used by-reference or by-value, depending on their type and the command's requirements. + +- **Mutable Reference (`&mut T`)**: The argument must be of type `T` and is mutably borrowed. +- **Immutable Reference (`&T`)**: The argument must be of type `T` and is immutably borrowed. +- **By-Value (`T`)**: The argument must be of type `T`. If `T` has the `copy` trait, it's copied; otherwise, it's moved. + +:::note + +Once you move an argument, you cannot use it again. +If you try to use an argument after it's been moved, the transaction will fail. + +::: + +When borrowing arguments: + +- **Mutable Borrow**: No other borrows (mutable or immutable) can exist to avoid references that point to invalid memory. +- **Immutable Borrow**: No mutable borrows can exist; multiple immutable borrows are allowed. +- **Moving or Copying**: There can be outstanding borrows, mutable or immutable. While it might lead to some unexpected results in some cases, there is no safety concern + +#### Special Considerations + +- **Object Inputs**: For `ObjectArg::Receiving` inputs, the object type `T` is wrapped as [`iota::transfer::Receiving`](../../../../references/framework/iota-framework/transfer.mdx#struct-receiving). You need to call [`iota::transfer::receive`](../../../../references/framework/iota-framework/transfer.mdx#function-receive) to prove ownership. +- **Gas Coin**: You can only use the gas coin by-value with the [`TransferObjects`](#transferobjects) command. This ensures that any remaining gas can be returned to it. + - **Shared Objects**: Shared objects have restrictions to ensure they remain shared or are deleted by the end of the transaction. They cannot be unshared or wrapped. + A shared object: + - Marked as not `mutable` (being used read-only) cannot be used by value. + - Cannot be transferred or frozen. These checks are done at the end of the transaction only. For example, `TransferObjects` succeeds if passed a shared object, but at the end of execution the transaction fails. + - Can be wrapped and can become a dynamic field transiently, but by the end of the transaction it must be re-shared or deleted. + + +#### Pure Values + +Pure values are not type-checked until used. +They can be used with multiple types as long as the bytes are valid for each type. +Once you mutably borrow a pure value, its type becomes fixed for subsequent uses. +For example, you can use a [string](../../../../references/framework/move-stdlib/string.mdx) 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. + +#### Command Execution Details + +Let's look at how specific commands are executed. + +##### `TransferObjects` + +- **Syntax**: `TransferObjects(ObjectArgs, AddressArg)` +- **Usage**: + - `ObjectArgs`: A list of objects to transfer (by-value). + - `AddressArg`: The recipient's address (by-value) from a `Pure` input or a result. +- **Notes**: + - Objects can be of different types. + - The command does not produce any results. + +##### `SplitCoins` + +- **Syntax**: `SplitCoins(CoinArg, AmountArgs)` +- **Usage**: + - `CoinArg`: The coin to split (mutable reference). Must be a coin of type [`iota::coin::Coin`](../../../../references/framework/iota-framework/coin.mdx). + - `AmountArgs`: Amounts to split off (by-value). Must be `u64` values, which could come from a `Pure` input or a result. +- **Notes**: + - Produces a list of new coins. + - Coin types must match. + +##### `MergeCoins` + +- **Syntax**: `MergeCoins(CoinArg, ToMergeArgs)` +- **Usage**: + - `CoinArg`: The target coin (mutable reference). Must be a coin of type [`iota::coin::Coin`](../../../../references/framework/iota-framework/coin.mdx). + - `ToMergeArgs`: Coins to merge into the target (by-value). +- **Notes**: + - Coin types must match. + - Does not produce any results. + +##### `MakeMoveVec` + +- **Syntax**: `MakeMoveVec(VecTypeOption, Args)` +- **Usage**: + - `VecTypeOption`: Optional type of the vector elements. + - `Args`: Elements to include in the vector (by-value). Copied if `T: copy` and moved otherwise. +- **Notes**: + - Produces a single vector result. + - Useful for constructing arguments for `MoveCall`. + +##### `MoveCall` + +- **Syntax**: `MoveCall(Package, Module, Function, TypeArgs, Args)` +- **Usage**: + - `Package`: Object ID of the package. + - `Module`: Name of the module. + - `Function`: Name of the function. + - `TypeArgs`: Type arguments for the function. + - `Args`: Arguments for the function. +- **Notes**: + - The number of results and argument usage depend on the function's signature. + +##### `Publish` + +- **Syntax**: `Publish(ModuleBytes, TransitiveDependencies)` +- **Usage**: + - `ModuleBytes`: Bytes of the modules to publish. + - `TransitiveDependencies`: Package IDs to link against. +- **Notes**: + - Calls the `init` function of each module. + - Produces an [`iota::package::UpgradeCap`](../../../../references/framework/iota-framework/package.mdx#resource-upgradecap). + +##### `Upgrade` + +- **Syntax**: `Upgrade(ModuleBytes, TransitiveDependencies, Package, UpgradeTicket)` +- **Usage**: + - Similar to `Publish`, but upgrades an existing package. + - `Package`: Object ID of the package to upgrade. + - `UpgradeTicket`: Obtained from [`iota::package::UpgradeCap`](../../../../references/framework/iota-framework/package.mdx#resource-upgradecap). +- **Notes**: + - Does not call the `init` function of each module. + - Produces an [`iota::package::UpgradeReceipt`](../../../../references/framework/iota-framework/package.mdx#struct-upgradereceipt). + +### 3. Applying Effects + +After all the commands have executed: + +- **Input Checks**: + - Immutable or read-only inputs remain unchanged. + - Mutable inputs return to their original owners. + - Pure inputs are dropped. Note that pure input values must have `copy` and `drop` since all permissible types for those values have `copy` and `drop`. + +- **Shared Objects**: + - Must be either deleted or remain shared. + - Cannot be unshared or wrapped by the end of the transaction. + +- **Result Handling**: + - Remaining results with the `drop` trait are dropped. + - Values with `copy` but not `drop` must have been moved in their last usage. + - Unused values without `drop` cause the transaction to fail. + +**Gas Refund**: Any unused gas is returned to the gas coin, regardless of ownership changes. + +Finally, the transaction's effects (created, mutated, and deleted objects) are applied atomically by the IOTA network. + + +## Usage Example + +Let's explore a practical example to understand how a programmable transaction block (PTB) executes. +While this won't cover every rule, it will illustrate the general execution flow. + +Suppose you want to purchase two items from a marketplace, each costing `100 NANOS`. You plan to keep one item and send the other item, along with the remaining coin balance, to a friend at address `0x808`. You can achieve all of this within a single PTB: + +```rust +{ + inputs: [ + Pure(/* @0x808 BCS bytes */ ...), + Object(SharedObject { /* Marketplace shared object */ id: market_id, ... }), + Pure(/* 100u64 BCS bytes */ ...), + ] + commands: [ + SplitCoins(GasCoin, [Input(2)]), + MoveCall("some_package", "some_marketplace", "buy_two", [], [Input(1), NestedResult(0, 0)]), + TransferObjects([GasCoin, NestedResult(1, 0)], Input(0)), + MoveCall("iota", "tx_context", "sender", [], []), + TransferObjects([NestedResult(1, 1)], NestedResult(3, 0)), + ] +} +``` + +In this PTB: + +- **Inputs**: + - `Input(0)`: Your friend's address as a pure input. + - `Input(1)`: The shared marketplace object. + - `Input(2)`: The amount (`100u64`) to split from the gas coin. + +- **Commands**: + 1. **SplitCoins**: Splits `100u64` from the gas coin. + 2. **MoveCall**: Calls the marketplace's `buy_two` function with the marketplace object and the split coin. + 3. **TransferObjects**: Sends the gas coin and one item to your friend's address. + 4. **MoveCall**: Retrieves your own address using `iota::tx_context::sender`. + 5. **TransferObjects**: Sends the remaining item to your address. + +### Initial State + +Before executing the commands, the gas coin and marketplace object are loaded: + +```rust +Gas Coin: iota::coin::Coin { id: gas_coin, balance: iota::balance::Balance { value: 1_000_000u64 } } +Inputs: [ + Pure(/* @0x808 BCS bytes */ ...), + some_package::some_marketplace::Marketplace { id: market_id, ... }, + Pure(/* 100u64 BCS bytes */ ...), +] +Results: [] +``` + +The gas coin has an initial balance of `1_000,000u64`. The maximum gas budget (e.g., `500,000`) is deducted upfront, leaving the gas coin with `500,000u64`. + +### Command Execution + +#### Command 0: SplitCoins + +```rust +SplitCoins(GasCoin, [Input(2)]) +``` + +- **Action**: Splits off `100u64` from the gas coin. +- **Result**: A new coin (`new_coin`) with a balance of `100u64`. + +Updated memory: + +```rust +Gas Coin: Coin { id: gas_coin, balance: 499,900u64 } +Results: [ +[Coin { id: new_coin, value: 100u64 }], +] +``` + +#### Command 1: MoveCall (`buy_two` function) + +```rust +MoveCall("some_package", "some_marketplace", "buy_two", [], [Input(1), NestedResult(0, 0)]) +``` + +- **Action**: Calls `buy_two` with the marketplace object and the split coin. +- **Assumed Signature**: + + ```rust + entry fun buy_two( + marketplace: &mut Marketplace, + coin: Coin, + ctx: &mut TxContext, + ): (Item, Item) + ``` + +- **Result**: Two items (`Item { id: id1 }` and `Item { id: id2 }`) are returned. + +Updated memory: + +```rust +Gas Coin: Coin { id: gas_coin, ... value: 499_900u64 ... } +Inputs: [ + Pure(/* @0x808 BCS bytes */ ...), + Marketplace { id: market_id, ... }, // Any mutations are applied + Pure(/* 100u64 BCS bytes */ ...), +] +Results: [ + [ _ ], // The coin was moved + [Item { id: id1 }, Item { id: id2 }], // The results from the Move call +], +``` + +#### Command 2: TransferObjects (to friend) + +```rust +Gas Coin: _ // The gas coin is moved +Inputs: [ + Pure(/* @0x808 BCS bytes */ ...), + Marketplace { id: market_id, ... }, + Pure(/* 100u64 BCS bytes */ ...), +] +Results: [ + [ _ ], + [ _ , Item { id: id2 }], // One item was moved + [], // No results from TransferObjects +], +``` + +#### Command 3: MoveCall (`sender` function) + +```rust +MoveCall("iota", "tx_context", "sender", [], []) +``` + +```rust +public fun sender(ctx: &TxContext): address +``` + +- **Action**: Retrieves your own address. +- **Result**: Your address is returned. + +Updated memory: + +```rust +Gas Coin: _ +Inputs: [ + Pure(/* @0x808 BCS bytes */ ...), + Marketplace { id: market_id, ... }, + Pure(/* 100u64 BCS bytes */ ...), +] +Results: [ + [ _ ], + [ _ , Item { id: id2 }], + [], + [/* senders address */ ...], // The result of the Move call +], +``` + +#### Command 4: TransferObjects (to self) + +```rust +TransferObjects([NestedResult(1, 1)], NestedResult(3, 0)) +``` + +- **Action**: Transfers the second item to your address. +- **Result**: Ownership of `Item { id: id2 }` is transferred to you. + +Updated memory: + +```rust +Gas Coin: _ +Inputs: [ + Pure(/* @0x808 BCS bytes */ ...), + Marketplace { id: market_id, ... }, + Pure(/* 100u64 BCS bytes */ ...), +] +Results: [ + [ _ ], + [ _ , _ ], + [], + [/* senders address */ ...], + [], // No results from TransferObjects +], +``` + +### Final State and Validation + +At the end of execution, the runtime performs checks: + +- **Inputs**: + - Pure inputs are dropped (have `drop` ability). + - The marketplace object remains shared and is returned. + +- **Results**: + - All moved objects are accounted for. + - The sender's address is dropped (has `drop` ability). + +- **Gas Refund**: + - Unused gas is returned to the gas coin, even though it has changed ownership. + +### Transaction Effects + +The transaction produces the following effects: + +- **Created Objects**: + - `Item { id: id1 }` and `Item { id: id2 }` are new items. + +- **Transferred Objects**: + - Gas coin and `Item { id: id1 }` are transferred to `0x808`. + - `Item { id: id2 }` is transferred to your address. + +- **Mutated Objects**: + - The gas coin's balance is updated. + - The marketplace object is mutated but remains shared. + + diff --git a/docs/content/developer/iota-101/transactions/ptb/simulating-references.mdx b/docs/content/developer/iota-101/transactions/ptb/simulating-references.mdx new file mode 100644 index 00000000000..cfc759ec3ea --- /dev/null +++ b/docs/content/developer/iota-101/transactions/ptb/simulating-references.mdx @@ -0,0 +1,153 @@ +--- +description: Discover how to simulate references in IOTA's programmable transaction blocks using the borrow module. +--- +import Quiz from '@site/src/components/Quiz'; +import questions from '/json/developer/iota-101/transactions/ptb/simulating-references.json'; + +# Simulating References in IOTA PTBs + +In IOTA, all on-chain data is represented as [objects](../../objects/object-model.mdx). +When developing Move packages for the IOTA network, you often need to manipulate these objects using the IOTA API. +Typically, the API functions require you to provide an object by reference. + +References are fundamental in Move programming and IOTA development. +Most IOTA API functionalities accept objects as references, which enhances security and asset safety in smart contracts. + +However, there are two primary ways to interact with an object: + +- **By Value:** You have complete ownership of the object, allowing you to destroy it, wrap it (if it has the `store` ability), or transfer it to another address. +- **By Reference:** You access the object's data without owning it, limiting operations to those defined by the module that provides the object. This method prevents you from destroying or transferring the object. References come in two forms: + - **Mutable Reference (`&mut`):** Allows you to modify the object according to the API but not destroy or transfer it. + - **Immutable Reference (`&`):** Provides read-only access to the object's data, further restricting operations. + +Currently, [programmable transaction blocks (PTBs)](programmable-transaction-blocks) in IOTA do not support +using object references returned from transaction commands. +While you can use input objects, objects created within the PTB, or objects returned by value, +you cannot use a reference returned by a transaction command in subsequent calls. +This limitation hinders certain common patterns in Move. + +## Solving with the Borrow Module + +The IOTA framework offers a [borrow module](../../../../references/framework/iota-framework/borrow.mdx) to address this reference limitation. +This module allows you to access an object by value while preventing it from being destroyed, transferred, or wrapped. +It introduces a `Referent` object that wraps the target object you wish to reference. +Using the [hot potato pattern](../../move-overview/patterns/hot-potato.mdx) through a `Borrow` instance, you can retrieve the wrapped object by value and ensure it is returned to the `Referent` within the same PTB. +The `Borrow` instance ensures the returned object is the same as the one retrieved. + +### Example Usage + +Consider a module `a_module` that defines an `Asset` object and a function `use_asset`: + +```rust +module a_module { + struct Asset has key, store { + … // some data + } + + public fun use_asset(asset: &Asset) { + …. // some code + } +} +``` + +The `use_asset` function accepts an immutable reference to `Asset`, which is common in API definitions. + +Now, imagine another module `another_module` that utilizes this asset: + +```rust +module another_module { + struct AssetManager has key { + asset: Asset, + } + + public fun get_asset(manager: &AssetManager): &Asset { + &manager.asset + } +} +``` + +Here, `AssetManager` holds a reference to `Asset` from `a_module`. +You might write a function to retrieve the asset by reference and pass it to `use_asset`: + +```rust +fun do_something(manager: &AssetManager) { + let asset = another_module::get_asset(manager); + a_module::use_asset(asset); +} +``` + +However, in PTBs, this pattern is invalid because you cannot use references returned by functions in subsequent calls. To work around this, you can modify `another_module` to use the borrow module: + +```rust +module another_module { + struct AssetManager has key { + asset: Referent, + } + + public fun get_asset(manager: &mut AssetManager): (Asset, Borrow) { + borrow::borrow(&mut manager.asset) + } + + + public fun return_asset( + manager: &mut AssetManager, + asset: Asset, + b: Borrow) { + borrow::put_back(&mut manager.asset, asset, b) + } +} +``` + +With these changes, you can retrieve the asset, use it, and then return it within the PTB. + +## Important Considerations + +The `Borrow` object is crucial for maintaining the integrity of the borrow module's guarantees. +Defined as `struct Borrow { ref: address, obj: ID }`, it cannot be dropped or stored elsewhere, +ensuring it is consumed within the same transaction (the hot potato pattern). +This structure ensures you cannot keep the retrieved object or swap it with another, maintaining consistency. + +:::caution + +Using `Referent` requires explicit changes to your codebase and can be intrusive. +Carefully consider this approach when designing your solution. + +::: + +While support for references in PTBs is forthcoming, using the borrow module is a temporary workaround. +Be mindful of the implications and plan for a transition to a more natural reference pattern in the future. + +Additionally, the `Referent` model necessitates the use of mutable references and returns objects by value, +which can significantly impact API design. +Exercise caution in how you expose objects and logic in your modules. + +## Practical Example + +Expanding on the earlier example, here's how you might write a PTB that calls `use_asset`: + +```rust +// Initialize the PTB +const txb = new TransactionBlock(); +// Load the assetManager +const assetManager = txb.object(assetManagerId); +// Retrieve the asset +const [asset, borrow] = txb.moveCall({ + target: "0xaddr1::another_module::get_asset", + arguments: [ assetManager ], +}); + +// Use the asset +txb.moveCall({ + target: "0xaddr2::a_module::use_asset", + arguments: [ asset ], +}); + +// Return the asset +txb.moveCall({ + target: "0xaddr1::another_module::return_asset", + arguments: [ assetManager, asset, borrow ], +}); +... +``` + + diff --git a/docs/content/developer/iota-101/transactions/ptb/simulating-refs.mdx b/docs/content/developer/iota-101/transactions/ptb/simulating-refs.mdx deleted file mode 100644 index 01c45da0475..00000000000 --- a/docs/content/developer/iota-101/transactions/ptb/simulating-refs.mdx +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: Simulating References -description: Use the borrow module in the IOTA framework to include objects by reference in your programmable transaction blocks. ---- -import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/ptb/simulating-refs.json'; - -Everything on the IOTA blockchain is an object. When you develop Move packages for the IOTA network, you're typically manipulating or using on-chain objects in some way through functionality available in the IOTA API. For most API functions, you provide an object by reference. - -References are a key construct when programming in Move and on IOTA. Most of the functionality available in the IOTA API takes objects by reference. - -There are two ways to use an object: -- **by value:** When you use an object by value, you have full control over that object. You can destroy it (if the functionality is available), wrap it (if it has the `store` ability), or transfer it to an address. -- **by reference:** When you use an object by reference, operations over that object are determined by the logic the module that defines the object provides because you are using a reference to its data rather than having ownership of the object itself. The restrictions of references allow you to develop smart contracts with a high level of security and safety around assets. There are two types of references: - - Mutable reference (`&mut`): You can alter the object (according to the API) but you can't destroy or transfer it. - - Immutable reference (`&`): Further restricts the set of operations and the guarantees/invariants over the referenced object. You have read-only access to the object's data. - -Programmable transaction blocks (PTBs) do not currently allow the use of object references returned from one of its transaction commands. You can use input objects to the PTB, objects created by the PTB (like `MakeMoveVec`), or returned from a transaction command by value as references in subsequent transaction commands. If a transaction command returns a reference, however, you can't use that reference in any call, significantly limiting certain common patterns in Move. - -## The borrow module - -The IOTA framework includes a [borrow](https://github.com/iotaledger/iota/blob/main/crates/iota-framework/docs/borrow.md) module that offers a solution to the reference problem. The module provides access to an object by value but builds a model that makes it impossible to destroy, transfer, or wrap the object retrieved. The borrow module exposes a `Referent` object that wraps another object (the object you want to reference). The module uses the [hot potato pattern](../../move-overview/patterns/hot-potato.mdx) (via a `Borrow` instance) to allow retrieval of the wrapped object by value. Within the same PTB, the module then forces the object to be returned to the `Referent`. The `Borrow` instance guarantees that the object returned is the same that was retrieved. - -As an example, consider the following module stub that exposes an object (`Asset`) and a function (`use_asset`) to use that object. - -```rust -module a_module { - struct Asset has key, store { - … // some data - } - - public fun use_asset(asset: &Asset) { - …. // some code - } -} -``` - -The function `use_asset` takes an immutable reference to the asset (`&Asset`), which is a common pattern in a an API definition. - -Now consider another module that uses this asset. - -```rust -module another_module { - struct AssetManager has key { - asset: Asset, - } - - public fun get_asset(manager: &AssetManager): &Asset { - &manager.asset - } -} -``` - -This module creates an object (`AssetManager`) that references the object (`Asset`) created in the previous module (`a_module`). - -You could then write a Move function that retrieves an object by reference and passes it to the `use_asset` function. - -```rust -fun do_something(manager: &AssetManager) { - let asset = another_module::get_asset(manager); - a_module::use_asset(asset); -} -``` - -The two functions in `do_something` are not valid within a PTB, however, because PTBs do not support a reference returned by a function and passed to another function. - -To make this operation valid within a PTB, you would need to include functionality from the borrow module. Consequently, you could change the `another_module` code to the following: - -```rust -module another_module { - struct AssetManager has key { - asset: Referent, - } - - public fun get_asset(manager: &mut AssetManager): (Asset, Borrow) { - borrow::borrow(&mut manager.asset) - } - - - public fun return_asset( - manager: &mut AssetManager, - asset: Asset, - b: Borrow) { - borrow::put_back(&mut manager.asset, asset, b) - } -} -``` - -Now the PTB can retrieve the asset, use it in a call to `use_asset`, and return the asset. - -## Considerations - -The `Borrow` object is the key to the guarantees the borrow module offers. The definition of `Borrow` is -`struct Borrow { ref: address, obj: ID }` -which makes it such that you cannot drop or save its instance anywhere, so it must be consumed in the same transaction that retrieves it (hot potato). Moreover, fields in the `Borrow` struct make sure that the object returned is for the same `Referent` and the object that was originally held by the `Referent` instance. In other words, there is no way to either keep the object retrieved or to swap it with another object in a different `Referent`. - -:::caution - -Using a `Referent` is a very explicit and intrusive change. That has to be taken into consideration when designing a solution. - -::: - -Support for references in a PTB is planned, which is a much more natural and proper pattern for APIs. - -You must consider the implications of using the borrow module and whether you have a mechanism to later move to a more natural, reference pattern. - -Finally, the `Referent` model forces the usage of a mutable reference and returns an object by value. Both have significant implications when designing an API. You must be careful in what logic your modules provide and how objects are exposed. - -## Example - -Extending the previous example, a PTB that calls `use_asset` is written as follows: - -```rust -// initialize the PTB -const txb = new TransactionBlock(); -// load the assetManager -const assetManager = txb.object(assetManagerId); -// retrieve the asset -const [asset, borrow] = txb.moveCall({ - target: "0xaddr1::another_module::get_asset", - arguments: [ assetManager ], -}); -// use the asset -txb.moveCall({ - target: "0xaddr2::a_module::use_asset", - arguments: [ asset ], -}); -// return the asset -txb.moveCall({ - target: "0xaddr1::another_module::return_asset", - arguments: [ assetManager, asset, borrow ], -}); -... -``` - - \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/ptb/working-with-ptbs.mdx b/docs/content/developer/iota-101/transactions/ptb/working-with-ptbs.mdx deleted file mode 100644 index ea045d4c2e8..00000000000 --- a/docs/content/developer/iota-101/transactions/ptb/working-with-ptbs.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Working with Programmable Transaction Blocks ---- - -Programmable transaction blocks (PTBs) are key elements of the IOTA ecosystem. Understanding PTBs and using them correctly are key fundamentals to creating efficient and cost-effective smart contracts. See [Programmable Transaction Blocks](prog-txn-blocks.mdx) to learn about the structure of PTBs on IOTA. - -The topics in this section focus on effectively utilizing PTBs in your smart contracts. - -## Building Programmable Transaction Blocks - -To fully appreciate the possibilities PTBs offer, you must build them. Using tools like the [IOTA TypeScript SDK](../../../../references/ts-sdk/typescript/index.mdx), you can begin to understand the power and flexibility they provide. - -Go to [Building Programmable Transaction Blocks](building-ptb.mdx). - -## Coin Management - -`Coin` objects on IOTA are different than other blockchains in that they are [owned objects](../../objects/object-ownership/address-owned.mdx). Whether you need your smart contract to utilize IOTA for gas payments or deal with generic coins, understanding coin management is crucial. Smart contracts use common patterns to accept coins and the PTBs you create must provide the correct interface to those smart contracts to facilitate successful transactions. - -Go to [Coin Management](coin-mgt.mdx). - -## Simulating References - -The [`borrow` module](/references/framework/iota-framework/borrow.mdx) of the IOTA framework offers some features you can use when your PTBs use objects by reference. - -Go to [Simulating References](simulating-refs.mdx). - -## Related links - -Review this content for a complete picture of PTBs on IOTA. - -- [Programmable Transaction Blocks](prog-txn-blocks.mdx): Conceptual overview of the PTB architecture. -- [Life of a Transaction](../../../../about-iota/iota-architecture/transaction-lifecycle.mdx): Discover the life of a transaction from inception to finality. diff --git a/docs/content/developer/iota-101/transactions/sign-and-send-txn.mdx b/docs/content/developer/iota-101/transactions/sign-and-send-transactions.mdx similarity index 61% rename from docs/content/developer/iota-101/transactions/sign-and-send-txn.mdx rename to docs/content/developer/iota-101/transactions/sign-and-send-transactions.mdx index e0ccd46b29a..f94e360b26e 100644 --- a/docs/content/developer/iota-101/transactions/sign-and-send-txn.mdx +++ b/docs/content/developer/iota-101/transactions/sign-and-send-transactions.mdx @@ -1,45 +1,62 @@ --- -title: Signing and Sending Transactions +description: A guide on how to construct, sign, and submit transactions in a Move-based blockchain. --- import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/sign-and-send-txn.json'; +import questions from '/json/developer/iota-101/transactions/sign-and-send-transactions.json'; -Transactions in IOTA represent calls to specific functionality (like calling a smart contract function) that execute on inputs to define the result of the transaction. +# Signing and Submitting Transactions -Inputs can either be an object reference (either to an owned object, an immutable object, or a shared object), or an encoded value (for example, a vector of bytes used as an argument to a Move call). After a transaction is constructed, usually through using [programmable transaction blocks](ptb/building-ptb.mdx) (PTBs), the user signs the transaction and submits it to be executed on chain. -The signature is provided with the private key owned by the wallet, and its public key must be consistent with the transaction sender's IOTA address. +In Move-based blockchains, transactions are the primary means to interact with the network, such as invoking smart contract functions or transferring assets. -IOTA uses a `IotaKeyPair` to produce the signature, which commits to the Blake2b hash digest of the intent message (`intent || bcs bytes of tx_data`). The signature schemes currently supported are `Ed25519 Pure`, `ECDSA Secp256k1`, `ECDSA Secp256r1` and `Multisig`. +## Understanding Transactions in Move -You can instantiate `Ed25519 Pure`, `ECDSA Secp256k1`, and `ECDSA Secp256r1` using `IotaKeyPair` and use it to sign transactions. Note that this guide does not apply to `Multisig`, please refer to it's [own page](../../cryptography/transaction-auth/multisig.mdx) for instructions. +Transactions in Move represent function calls or operations that affect the state of the blockchain. They are executed based on provided inputs, which can be: -With a signature and the transaction bytes, a transaction can be submitted to be executed. +- **Object References**: These refer to [owned objects](../objects/object-ownership/address-owned.mdx), [immutable objects](../objects/object-ownership/immutable.mdx), or [shared objects](../objects/object-ownership/shared.mdx) within the blockchain state. +- **Encoded Values**: For example, a [vector](../../../references/framework/move-stdlib/vector.mdx) of bytes used as arguments in a Move function call. -## Workflow +After constructing a transaction—typically using [Programmable Transaction Blocks](ptb/building-programmable-transaction-blocks-ts-sdk.mdx) (PTBs)—you need to sign it and submit it to the network. -The following high-level process describes the overall workflow for constructing, signing and executing an on-chain transaction: +The signature must be generated using your private key, and the corresponding public key must match the sender's blockchain address. -- Construct the transaction data by creating a `Transaction` where multiple transactions are chained. See [Building Programmable Transaction Blocks](ptb/building-ptb.mdx) for more information. -- The [SDK's](../../../references/iota-sdks.mdx) built-in gas estimation and coin selection picks the gas coin. -- Sign the transaction to generate a [signature](../../cryptography/transaction-auth/signatures.mdx). -- Submit the `Transaction` and its signature for on-chain execution. +IOTA uses a `IotaKeyPair` for signature generation, committing to the Blake2b hash of the intent message (`intent || serialized transaction data`). Supported signature schemes include `Ed25519`, `ECDSA Secp256k1`, `ECDSA Secp256r1`, and `Multisig`. + +You can instantiate key pairs for `Ed25519`, `ECDSA Secp256k1`, and `ECDSA Secp256r1` using `IotaKeyPair` and use them to sign transactions. + +:::info Multisig + +For `Multisig`, please refer to its [dedicated guide](../../cryptography/transaction-auth/multisig.mdx). + +::: + +Once you have the signature and the transaction bytes, you can submit the transaction for execution on-chain. + +## Workflow Overview + +Here is a high-level overview of the steps involved in constructing, signing, and executing a transaction: + +1. **Construct the Transaction Data**: Create a `Transaction` where you can chain multiple operations. Refer to [Creating Programmable Transaction Blocks](ptb/building-programmable-transaction-blocks-ts-sdk.mdx) for guidance. +2. **Select Gas Coin and Estimate Gas**: Use the [SDK's](../../../references/iota-sdks.mdx) built-in tools for gas estimation and coin selection. +3. **Sign the Transaction**: Generate a [signature](../../cryptography/transaction-auth/signatures.mdx) using your private key. +4. **Submit the Transaction**: Send the `Transaction` and its signature to the network for execution. :::info -If you want to use a specific gas coin, first find the gas coin object ID to be used to pay for gas, and explicitly use that in the PTB. If there is no gas coin object, use the [splitCoin](ptb/building-ptb.mdx#available-transactions) transaction to create a gas coin object. The split coin transaction should be the first transaction call in the PTB. +If you wish to specify a particular gas coin, locate the gas coin object ID that you own and include it explicitly in the PTB. If you don't have a gas coin object, use the [splitCoin](ptb/building-programmable-transaction-blocks-ts-sdk.mdx#txbsplitcoinscoin-amounts) operation to create one. Ensure that the split coin transaction is the first call in the PTB. ::: ## Examples -The following examples demonstrate how to sign and execute transactions using Rust, TypeScript, or the IOTA CLI. + +Below are examples demonstrating how to sign and submit transactions using Rust, TypeScript, or the Move CLI. - + -There are various ways to instantiate a key pair and to derive its public key and IOTA address using the [IOTA TypeScript SDK](../../../references/ts-sdk/typescript/index.mdx). +There are several methods to create a key pair and derive its public key and Move address using the [Move TypeScript SDK](../../../references/ts-sdk/typescript/index.mdx). ```tsx import { fromHEX } from '@iota/bcs'; @@ -55,13 +72,13 @@ const kp_rand_1 = new Secp256k1Keypair(); const kp_rand_2 = new Secp256r1Keypair(); const kp_import_0 = Ed25519Keypair.fromSecretKey( - fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'), +fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'), ); const kp_import_1 = Secp256k1Keypair.fromSecretKey( - fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'), +fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'), ); const kp_import_2 = Secp256r1Keypair.fromSecretKey( - fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'), +fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'), ); // $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, e.g. "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](../cryptography/transaction-auth/keys-addresses.mdx) for more. @@ -105,9 +122,9 @@ console.log(res); -The full code example below can be found under [crates/iota-sdk](https://github.com/iotaledger/iota/blob/develop/crates/iota-sdk/examples/sign_tx_guide.rs). +The complete code example is available in the [iota-sdk](https://github.com/move-language/move/blob/main/language/move-sdk/examples/sign_tx_guide.rs). -There are various ways to instantiate a `IotaKeyPair` and to derive its public key and IOTA address using the [IOTA Rust SDK](../../../references/rust-sdk.mdx). +You can create a `IotaKeyPair` and derive its public key and IOTA address using the [IOTA Rust SDK](../../../references/rust-sdk.mdx). ```rust // deterministically generate a keypair, testing only, do not use for mainnet, use the next section to randomly generate a keypair instead. @@ -131,7 +148,7 @@ There are various ways to instantiate a `IotaKeyPair` and to derive its public k let _ikp_import_no_flag_1 = IotaKeyPair::Ed25519(Ed25519KeyPair::from_bytes( &Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=") .map_err(|_| anyhow!("Invalid base64"))?, - )?); +)?); let _ikp_import_no_flag_2 = IotaKeyPair::Ed25519(Ed25519KeyPair::from_bytes( &Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=") .map_err(|_| anyhow!("Invalid base64"))?, @@ -153,27 +170,27 @@ There are various ways to instantiate a `IotaKeyPair` and to derive its public k let sender = IOTAAddress::from(&pk); ``` -Next, sign transaction data constructed using an example programmable transaction block with default gas coin, gas budget, and gas price. See [Building Programmable Transaction Blocks](ptb/building-ptb.mdx) for more information. +Next, sign transaction data constructed using an example programmable transaction block with default gas coin, gas budget, and gas price. See [Building Programmable Transaction Blocks](ptb/building-programmable-transaction-blocks-ts-sdk.mdx) for more information. ```rust // construct an example programmable transaction. let pt = { - let mut builder = ProgrammableTransactionBuilder::new(); +let mut builder = ProgrammableTransactionBuilder::new(); builder.pay_iota(vec![sender], vec![1])?; - builder.finish() - }; +builder.finish() +}; - let gas_budget = 5_000_000; +let gas_budget = 5_000_000; let gas_price = iota_client.read_api().get_reference_gas_price().await?; // create the transaction data that will be sent to the network. - let tx_data = TransactionData::new_programmable( +let tx_data = TransactionData::new_programmable( sender, - vec![gas_coin.object_ref()], +vec![gas_coin.object_ref()], pt, - gas_budget, - gas_price, - ); +gas_budget, +gas_price, +); ``` Commit a signature to the Blake2b hash digest of the intent message (`intent || bcs bytes of tx_data`). @@ -202,26 +219,26 @@ Finally, submit the transaction with the signature. ```rust let transaction_response = iota_client - .quorum_driver_api() - .execute_transaction_block( +.quorum_driver_api() +.execute_transaction_block( iota_types::transaction::Transaction::from_generic_sig_data( - intent_msg.value, +intent_msg.value, Intent::iota_transaction(), vec![GenericSignature::Signature(iota_sig)], - ), +), IOTATransactionResponseOptions::default(), - None, - ) - .await?; +None, +) +.await?; ``` -When using the [IOTA CLI](../../../references/cli.mdx) for the first time, it creates a local file in `~/.iota/keystore` on your machine with a list of private keys (encoded as Base64 encoded `flag || 32-byte-private-key`). You can use any key to sign transactions by specifying its address. Use `iota keytool list` to see a list of addresses. +When you first use the [IOTA CLI](../../../references/cli.mdx), it creates a keystore at `~/.move/keystore` on your machine, storing your private keys. You can sign transactions using any key by specifying its address. Use `move keytool list` to view your addresses. -There are three ways to initialize a key: +You can initialize a key in three ways: ```shell # generate randomly. @@ -254,11 +271,11 @@ iota client execute-signed-tx --tx-bytes $TX_BYTES --signatures $SERIALIZED_SIGN -### Notes +### Additional Notes -1. This guide demonstrates how to sign with a single private key. Refer to [Multisig](../../cryptography/transaction-auth/multisig.mdx) when it is preferred to set up more complex signing policies. -2. If you decide to implement your own signing mechanisms instead of using the previous tools, see the [Signatures](../../cryptography/transaction-auth/signatures.mdx) doc on the accepted signature specifications for each scheme. -3. Flag is one byte that differentiates signature schemes. See supported schemes and its flag in [Signatures](../../cryptography/transaction-auth/signatures.mdx). -4. The `execute_transaction_block` endpoint takes a list of signatures, so it should contain exactly one user signature, unless you are using sponsored transaction that a second signature for the gas object can be provided. See [Sponsored Transactions](sponsored-transactions.mdx) for more information. +1. This tutorial focuses on signing with a single private key. For more complex signing policies, refer to the [Multisig](../../cryptography/transaction-auth/multisig.mdx) guide. +2. If you implement your own signing mechanism, consult the [Signatures](../../cryptography/transaction-auth/signatures.mdx) documentation for accepted specifications. +3. The 'flag' is a byte that differentiates signature schemes. See [Signatures](../../cryptography/transaction-auth/signatures.mdx) for supported schemes and their flags. +4. The `execute_transaction_block` endpoint accepts a list of signatures, typically requiring only one user signature unless using sponsored transactions. For more information, see [Sponsored Transactions](sponsored-transactions/about-sponsored-transactions.mdx). - \ No newline at end of file + diff --git a/docs/content/developer/iota-101/transactions/sponsor-txn.mdx b/docs/content/developer/iota-101/transactions/sponsor-txn.mdx deleted file mode 100644 index dcbf2cf3d54..00000000000 --- a/docs/content/developer/iota-101/transactions/sponsor-txn.mdx +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: Sponsored Transaction ---- -import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/sponsor-txn.json'; - -Sponsored transactions are a primitive on the IOTA blockchain that enable the execution of a transaction without a user paying the gas. It also discusses the roles in Sponsored Transaction, and a few common use cases. Then it discusses the flow of Sponsored Transaction, mostly for developers who are interested in building a Gas Station or integrate with one. Finally it talks about risk considerations of Sponsored Transaction. - -# Overview -A transaction on IOTA takes a payment to execute. The payment, also known as gas, is a list of `0x2::coin::Coin<0x2::iota::IOTA>` objects, and paid to IOTA validators to secure the network. Although gas is a critical piece in [IOTA tokenomics](../../../about-iota/tokenomics/tokenomics.mdx), it sometimes adds challenges when new users start to navigate on IOTA, especially for web 2.0 users. - -Sponsored transactions can reduce the onboarding friction for users because the feature streamlines the process for end users. Using sponsored transactions, you can execute a transaction without requiring the user to pay it themselves. Instead, you can act as a sponsor of the transaction, offering your own payment gas objects for the transaction. - -## Roles in sponsored transactions - -In a sponsored transaction there are three roles: the user, the gas station, and the sponsor. - -* The user is the entity who wants to execute a transaction. -* The gas station is the entity that fulfills the sponsorship request for the user's transaction by providing the gas payment they own. -* The sponsor is entity that funds the gas station for its operations. - -It's not uncommon for the gas station and the sponsor to be the same entity. For example, a web3 gaming studio could run its own gas station to sponsor users with real free-to-play gaming experiences at its user acquisition stage. Because it's not always trivial to maintain a gas station for teams of any size, that gaming studio could also leverage third-party gas stations to sponsor transactions they want to promote. - -The remainder of this guide assumes the sponsor uses their own gas station. - -## Use cases - -The following sections describe some common scenarios where sponsored transactions offer an improved user experience. - -### App-specific sponsorship - -In this scenario, the sponsor has a specific set of applications they want to sponsor. - -- If the transaction is initialized by the user, the sponsor examines the transaction to make sure it's within the set of approved applications before agreeing to provide the gas payment. -- If the transaction is proposed by the sponsor, the user must examine the transaction and decide if they want to execute it. Examples of this type of transaction might include a rewards claim transaction of a campaign or a "try it out" advertisement transaction. - -### Wildcard sponsorship - -In this scenario, the sponsor has few restrictions on the type of transactions the gas payment can be used for. - -- If the sponsor is a gasless wallet, it may agree to sponsor any valid transactions proposed by its users. -- In the form of a reward or discount, the sponsor could offer the user a wildcard gas payment, expressly promising to execute any transactions with that gas payment. - -A sponsored transaction is not restricted to these use cases. Essentially, a sponsored transaction is any transaction jointly made by the user and the sponsor. As long as the stakeholders can agree on the transaction details, then the number of possible ways to provide sponsored transactions is limited only by the imagination. Because at least two stakeholders are involved in a sponsored transaction, however, there are some [additional risks](#risk} that you should take steps to mitigate. - - -## Sponsored transaction flow - -This section is mostly for developers who are interested in building a gas station or integrating with one. - -The data structure of a transaction resembles the following: - -```rust - -pub struct SenderSignedTransaction { - pub intent_message: IntentMessage, - /// A list of signatures signed by all transaction participants. - /// 1. non participant signature must not be present. - /// 2. signature order does not matter. - pub tx_signatures: Vec, -} - -pub struct TransactionDataV1 { // <-- A variant of `TransactionData` - pub kind: TransactionKind, // <-- This is the actual transaction details - pub sender: IOTAAddress, - pub gas_data: GasData, - pub expiration: TransactionExpiration, -} - -pub struct GasData { - pub payment: Vec, - pub owner: IOTAAddress, - pub price: u64, - pub budget: u64, -} - -``` - -A few details of note for the preceding code: - -- `sender` in `TransactionDataV1` (a variant of `TransactionData`) is the user address. -- `gas_data` in `TransactionDataV1` is the gas payment. -- `GasData` allows a list of gas objects, but the same address must own them, namely the `owner` in `GasData` (the sponsor). When `owner` is equal to `sender`, then it is a regular/non-sponsored transaction. -- `tx_signatures` in `SenderSignedTransaction` is a list of signatures. For a sponsored transaction, the list needs to contain both signatures of the user and the sponsor in some order. The signatures are signed over the entire `TransactionData`, including `GasData`. - -So, to construct a correct sponsored transaction, you must first build a `TransactionData` object. If you are neither the user or the sponsor, you would then pass the transaction to both parties to sign. If you're the sponsor, you would sign the transaction and then pass it and the signature to the other party (in the form of `SenderSignedTransaction`) for them to sign. In practice, the latter is the more common scenario. - -There are three flows of sponsored transaction. - -**User proposed transaction** - -([swimlane link](https://swimlanes.io/d/wAcnOpA_h)) - -![](https://static.swimlanes.io/b090340af36c8a4af6c36d4479a4d04f.png) - -**Sponsor proposed transaction** - -([swimlane link](https://swimlanes.io/#ZZE9T8QwDIb3/ApvLIWBsQMSEh8DEkI6mInb+O6itk4VOxInxH8nqe76AVki+X392G+iXnuq4fYGdmNgCRHeYhiDkIP3iCzYqg9syuGg2WmstWZMDYjG1Ora9YCK8G0gn2LoPLt6rb/kQjXLQuwo1rBL/t65SCKLdkD5dJlWwzNKwS4SfY0+YsFt2I9zuTI/047mEuj67pKthjmlHj13ki+CD6EIA56gIfCslFfRHN8zIOgyoQJkB2G/pyhZsefNLKDAiKeBWDczC7YG++d97EQ5264ExB8YNUUyZtpjbmyP1Hbb+cX7H7g0PqW+fw0uf5KkZvAKLmE/deU4K9Iv)) - -![](https://static.swimlanes.io/d917884a263c494bb6127102d0f64840.png) - - -**Wildcard gas payment** - - -([swimlane link](https://static.swimlanes.io/ee3962b3ac3cc5d34f317cecdde125b0.png)) - -![](https://static.swimlanes.io/ee3962b3ac3cc5d34f317cecdde125b0.png) - - -## Risk considerations {#risk} - -Because at least two stakeholders are involved in a sponsored transaction, you should take steps to mitigate risk. - -### Client equivocation risk - -Client equivocation happens when more than one legit transaction that shares at least one owned object (such as a gas coin object) at a certain version are submitted to the network simultaneously. On IOTA, before a transaction is executed, owned objects in this transaction are locked on validators at specific versions. An honest validator only accepts one transaction and rejects others. Depending on the order validators receive these transactions, validators might accept different transactions. In the event of no single transaction getting accepted by at least 2/3rds of validators, the owned object is locked until end of the epoch. - -Practically speaking, client equivocation is rare, mostly caused by buggy client software. After all, no one has incentives to lock their own objects. However, sponsored transactions come with counterparty risks. For example, a malicious user could equivocate the gas station's gas coin object by submitting another transaction that uses one owned object in the gas station signed transaction at the same version. Similarly, a Byzantine gas station could do the same to the user owned objects. - -Although this risk might seem trivial, it is helpful to be aware of it. Your gas station should actively monitor user behavior and alert on anything abnormal. Whether you're a user taking advantage of sponsored transactions or a developer integrating with a gas station, consider your reputation to minimize the risk of client equivocation. - -Both the user and the sponsor need to sign over the entire `TransactionData`, including `GasData` because otherwise a third party (such as a malicious Full node) could snip the partially signed data and cause client equivocation and locking of owned objects. - -### Censorship risk - -If you chooses to submit the dual-signed transaction to the sponsor or gas station rather than a Full node, the transaction might be subject to sponsor or gas station censorship. Namely, the sponsor might choose not to submit the transaction to the network, or delay the submission. - -You can mitigate this risk by submitting the transaction directly to a Full node. - - \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/sponsored-transactions.mdx b/docs/content/developer/iota-101/transactions/sponsored-transactions.mdx deleted file mode 100644 index 3aaea983603..00000000000 --- a/docs/content/developer/iota-101/transactions/sponsored-transactions.mdx +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: Sponsored Transactions ---- -import Quiz from '@site/src/components/Quiz'; -import questions from '/json/developer/iota-101/transactions/sponsored-transactions.json'; - -An IOTA sponsored transaction is one where an IOTA address (the sponsor's) pays the gas fees for a transaction that another address (the user's) initializes. You can use sponsored transactions to cover the fees for users on your site or app so that they don't get charged for them. This removes a significant obstacle that web 2.0 users encounter when entering web3, as they often have to purchase tokens to perform a transaction on chain. For example, you could sponsor gamers' early transactions to increase conversion rates. - -Sponsored transactions also facilitate asset management as you don't need to maintain multiple accounts with IOTA tokens to transfer funds. - -You can use IOTA sponsored transactions to: -- Sponsor (pay gas fees for) a transaction a user initiates. -- Sponsor transactions you initiate as the sponsor. -- Provide a wildcard `GasData` object to users. The object covers the gas fees for a user transaction. The `GasData` object covers any fee amount determined for the transaction as long as the budget is sufficient. - -## Potential risks using sponsored transactions - -The most significant potential risk when using sponsored transactions is [equivocation](/references/iota-glossary.mdx#equivocation). In some cases under certain conditions, a sponsored transaction can result in all associated owned objects, including gas in a locked state when examined by IOTA validators. To avoid double spending, validators lock objects as they validate transactions. An equivocation occurs when an owned object's pair (`ObjectID`, `SequenceNumber`) is concurrently used in multiple non-finalized transactions. - -To equivocate, either the user or the sponsor signs and submits another transaction that attempts to manipulate an owned object in the original transaction. Because only the object owner can use an owned object, only the user and sponsor can cause this condition. - -## Create a user-initiated sponsored transaction - -A user-initiated sponsored transaction involves the following steps: - - 1. A user initializes a `GasLessTransactionData` transaction. - 1. The user sends `GasLessTransactionData` to the sponsor. - 1. The sponsor validates the transaction, constructs `TransactionData` with gas fees, and then signs `TransactionData`. - 1. The sponsor sends the signed `TransactionData` and the sponsor `Signature` back to the user. - 1. The user verifies and then signs `TransactionData` and sends the dual-signed transaction to IOTA network through a Full node or the sponsor. - -### GasLessTransactionData - -`GasLessTransactionData` is basically `TransactionData` without `GasData`. It is not a `iota-core` data structure, but it is only an interface between user and sponsor. - -The following example constructs a `GasLessTransactionData` object. - -```rust -pub struct GasLessTransactionData { - pub kind: TransactionKind, - sender: IotaAddress, - … -} -``` - -## Create a sponsor-initiated sponsored transaction - -A sponsor-initiated sponsored transaction involves the following steps: - 1. A sponsor constructs a `TransactionData` object that contains the transaction details and associated gas fee data. The sponsor signs it to generate a `Signature` before sending it to a user. You can send the unsigned `TransactionData` via email, SMS, or an application interface. - 1. The user checks the transaction and signs it to generate the second `Signature` for the transaction. - 1. The user submits the dual-signed transaction to a IOTA Full node or sponsor to execute it. - -You can use a sponsor-initiated sponsored transaction as an advertiser, or to incentivize specific user actions without requiring the user to pay for gas fees. - -## Create sponsored transactions using a GasData object - -To use a `GasData` object to sponsor the gas fees for a transaction, create a `GasData` object that covers the fees determined for the transaction. This is similar to providing a blank check to a user that can be used only to cover gas fees. The user doesn't need to know how much the fee is or approve it. - - A sponsor transaction using a `GasData` object involves the following steps: - 1. The sponsor provides a `GasData` object to a user. - 1. The user constructs `TransactionData` and signs it to generate a `Signature`. - 1. The user sends the `TransactionData` and the `Signature` to the sponsor. - 1. The sponsor confirms the `TransactionData` and then signs it. - 1. The sponsor submits the dual-signed `TransactionData` to a Full node to execute the transaction. - -## Create an IOTA gas station - -On IOTA, a gas station is a concept to describe where you set up processes to sponsor user transactions. You can customize a IOTA gas station to support the specific user-facing functionality you need. Some example use cases for a IOTA gas station include: - -- Monitor real-time gas prices on the network to determine the gas price that the station provides. -- Track usage of gas provided to users on the network. -- Gas pool management, such as using specific gas objects to minimize costs or reduce the risk of a large amount of locked objects that remain illiquid while locked. - -### Authorization and rate limiting - -Depending on the nature of your gas station, you can apply different authorization rules to avoid being spammed by bad actors. Possible policies include: - -- Rate limit gas requests per account or per IP address -- Only accept requests with a valid authorization header, which has separate rate limits - -### Abuse detection - -For all gas objects that you provide as a sponsor, you should track if users ever try to equivocate and lock objects. If you detect such behavior, block the user or requester accordingly. - -## Code examples to create a IOTA gas station - -The following Rust SDK code examples demonstrate how to implement a IOTA gas station that supports each of the sponsored transaction types described previously. - -### User-initiated sponsored transactions - -Use the API endpoint to receive `GaslessTransaction` transactions and return a sole-signed `SenderSignedData` object. - -```rust -pub fn request_gas_and_signature(gasless_tx: GaslessTransaction) -> Result; -``` - -### Sponsored transactions with GasData objects - -Use the API endpoint to receive sole-signed `SenderSignedData` and return the result of the transaction. - -```rust -pub fn submit_sole_signed_transaction(sole_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>; -``` - -Alternatively, use the API endpoint to return a GasData object. - -```rust -pub fn request_gas(/*requirement data*/) -> Result; -``` - -### User and sponsor-initiated transaction - -Use the API endpoint to receive dual-signed `SenderSignedData` and return the result of the transaction. - -```rust -pub fn submit_dual_signed_transaction(dual_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>; -``` - -For user and sponsor-initiated transactions, users can submit the dual-signed transaction via either a sponsor or a Full node. - -## Sponsored transaction data structure - -The following code block describes the `TransactionData` structure for sponsored transactions and `GasObject`. You can view the [source code](https://github.com/iotaledger/iota/blob/develop/crates/iota-types/src/messages.rs) in the IOTA GitHub repository. - -**`TransactionData` Structure** -```rust -#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] -pub struct TransactionDataV1 { -pub kind: TransactionKind, -pub sender: IotaAddress, -pub gas_data: GasData, -pub expiration: TransactionExpiration, -} -``` - -**`GasData` Structure** -```rust -#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] -pub struct GasData { - pub payment: Vec, - pub owner: IOTAAddress, - pub price: u64, - pub budget: u64, -} -``` - -To learn more about transactions in IOTA, see [Transactions](transactions.mdx). - - \ No newline at end of file diff --git a/docs/content/developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions.mdx b/docs/content/developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions.mdx new file mode 100644 index 00000000000..090939ee168 --- /dev/null +++ b/docs/content/developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions.mdx @@ -0,0 +1,151 @@ +--- +description: A guide to understanding and implementing sponsored transactions on the IOTA blockchain, including roles, use cases, workflows, and risk considerations. +--- +import Quiz from '@site/src/components/Quiz'; +import questions from '/json/developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions.json'; + +# Sponsored Transactions on IOTA + +Sponsored transactions are a powerful feature in the IOTA blockchain that allow transactions to be executed without the user directly paying for gas. + +## Understanding Sponsored Transactions + +On the IOTA network, executing a transaction requires a gas payment, which is a list of [`0x2::coin::Coin<0x2::iota::IOTA>`](../../../../references/framework/iota-framework/coin.mdx) objects paid to validators to secure the network. +While gas fees are essential to the [IOTA tokenomics](../../../../about-iota/tokenomics/tokenomics.mdx), they can present challenges for new users, especially those accustomed to traditional web applications. + +Sponsored transactions aim to simplify the onboarding process by allowing transactions to be executed without the user having to provide the gas payment themselves. Instead, a sponsor covers the gas fees, enhancing the user experience and reducing friction. + +## Key Roles in Sponsored Transactions + +There are three main participants in a sponsored transaction: + +- **User**: The individual or entity initiating the transaction. +- **Gas Station**: The service that processes sponsorship requests and provides the gas payment owned by the sponsor. +- **Sponsor**: The entity funding the gas station, supplying the gas payments used in transactions. + +In many cases, the gas station and the sponsor are the same entity. +For example, a web3 gaming company might operate its own gas station to sponsor user transactions, +offering a seamless, free-to-play experience to attract new players. +Alternatively, they might use third-party gas stations to promote certain transactions. + +For the purposes of this guide, we'll assume that the sponsor operates their own gas station. + +## Common Use Cases + +Sponsored transactions enhance the user experience in various scenarios. Here are some typical examples: + +### Application-Specific Sponsorship + +In this scenario, the sponsor targets specific applications or transactions for sponsorship. + +- **User-Initiated Transactions**: The sponsor reviews the user's transaction to ensure it aligns with approved applications before providing the gas payment. +- **Sponsor-Initiated Transactions**: The user reviews a transaction proposed by the sponsor and decides whether to execute it. Examples include claiming rewards from a campaign or testing out a new feature. + +### General Sponsorship + +Here, the sponsor places minimal restrictions on the types of transactions they are willing to sponsor. + +- **Gasless Wallets**: The sponsor may agree to cover gas fees for any valid transactions initiated by users. +- **Rewards and Promotions**: The sponsor provides a wildcard gas payment, allowing users to execute transactions freely as part of a promotion or loyalty program. + +Sponsored transactions are not limited to these scenarios. Essentially, any transaction that involves collaboration between the user and the sponsor can be facilitated through sponsorship, provided both parties agree on the details. However, involving multiple stakeholders introduces certain [risks](#risk-considerations) that need to be managed. + +## Workflow of Sponsored Transactions + +This section is intended for developers interested in building or integrating with a gas station. + +### Transaction Data Structure + +A transaction in IOTA has a specific data structure, which can be represented as: + +```rust +pub struct SenderSignedTransaction { +pub intent_message: IntentMessage, +/// A list of signatures signed by all transaction participants. +/// 1. Non-participant signatures must not be present. +/// 2. Signature order does not matter. +pub tx_signatures: Vec, +} + +pub struct TransactionDataV1 { // <-- A variant of `TransactionData` +pub kind: TransactionKind, // <-- This contains the transaction details +pub sender: IOTAAddress, +pub gas_data: GasData, +pub expiration: TransactionExpiration, +} + +pub struct GasData { +pub payment: Vec, +pub owner: IOTAAddress, +pub price: u64, +pub budget: u64, +} +``` + +Key points to note: + +- The `sender` in `TransactionDataV1` represents the user's address. +- The `gas_data` field contains the gas payment information. +- `GasData` can include multiple gas objects, but they must all be owned by the same address—the `owner` field, which is the sponsor. If the `owner` and `sender` are the same, it's a regular (non-sponsored) transaction. +- The `tx_signatures` array must include signatures from both the user and the sponsor for a sponsored transaction. These signatures cover the entire `TransactionData`, including `GasData`. + +To create a valid sponsored transaction: + +1. Build the `TransactionData` object. +2. Both the user and the sponsor must sign the transaction. + +Typically, the sponsor signs the transaction first and then sends it to the user to add their signature. Alternatively, if a third party is coordinating, they would collect signatures from both parties. + +### Transaction Flows + +There are three common workflows for sponsored transactions: + +#### User-Proposed Transaction + +In this flow, the user initiates the transaction and requests sponsorship. + +![User-Proposed Transaction](/img/developer/iota-101/transactions/sponsored-transactions/user-proposed-txn.png) + +#### Sponsor-Proposed Transaction + +Here, the sponsor initiates the transaction, and the user must approve and sign it. + +![Sponsor-Proposed Transaction](/img/developer/iota-101/transactions/sponsored-transactions/sponsored-txn.png) + +#### Wildcard Gas Payment + +In this scenario, the sponsor provides a gas payment that the user can use for any transaction. + +![Wildcard Gas Payment](/img/developer/iota-101/transactions/sponsored-transactions/wildcard-txn.png) + +## Risk Considerations + +When multiple stakeholders are involved in a transaction, it's important to be aware of potential risks and take steps to mitigate them. + +### Client Equivocation Risk + +Client equivocation occurs when multiple valid transactions sharing at least one owned object (e.g., a gas coin) are submitted to the network at the same time. +On IOTA, owned objects are locked at specific versions before transaction execution. +Honest validators will only accept one transaction and reject the rest. +If validators receive transactions in different orders, it can lead to inconsistencies, +and if no transaction is accepted by at least two-thirds of validators, the owned object remains locked until the end of the epoch. + +While client equivocation is rare and often due to software bugs, sponsored transactions introduce counterparty risks. +A malicious user might submit conflicting transactions using the sponsor's gas coin, +or a rogue gas station could do the same with the user's owned objects. + +To mitigate this risk: + +- Gas stations should monitor user activity and flag any suspicious behavior. +- Both parties must sign the entire `TransactionData`, including `GasData`, to prevent third parties from intercepting and altering the transaction data, which could cause client equivocation. + +### Censorship Risk + +If you submit the dual-signed transaction to the sponsor or gas station instead of directly to a full node, +there's a risk of censorship or delayed submission by the sponsor. + +To avoid this: + +- Submit the fully signed transaction directly to a full node yourself. + + diff --git a/docs/content/developer/iota-101/transactions/sponsored-transactions/use-sponsored-transactions.mdx b/docs/content/developer/iota-101/transactions/sponsored-transactions/use-sponsored-transactions.mdx new file mode 100644 index 00000000000..59a8fd18849 --- /dev/null +++ b/docs/content/developer/iota-101/transactions/sponsored-transactions/use-sponsored-transactions.mdx @@ -0,0 +1,129 @@ +import Quiz from '@site/src/components/Quiz'; +import questions from '/json/developer/iota-101/transactions/sponsored-transactions/use-sponsored-transactions.json'; + +# Use Sponsored Transactions + +[Sponsored transactions](about-sponsored-transactions.mdx) in IOTA enable one address (the sponsor) to pay the gas fees for a [transaction](../transactions.mdx) initiated by another address (the user). + +You can leverage IOTA sponsored transactions to: + +- **Sponsor user-initiated transactions**: Pay the gas fees for transactions that users initiate. +- **Sponsor your own transactions**: Cover the gas fees for transactions you start. +- **Provide a wildcard `GasData` object**: Offer users a `GasData` object that covers gas fees for their transactions, as long as the budget is sufficient. + + +:::danger Risks of Sponsored Transactions + +While sponsored transactions offer significant benefits, they come with [potential risks](about-sponsored-transactions.mdx#client-equivocation-risk), the most notable being [equivocation](/references/iota-glossary.mdx#equivocation). Under certain conditions, a sponsored transaction can cause all associated owned objects, including gas, to become locked when processed by IOTA validators. + +::: + +## User-Initiated Sponsored Transaction Flow + +To set up a user-initiated sponsored transaction, follow these steps: + +1. **User creates a `GasLessTransactionData` transaction**: The user initializes a transaction without gas data. +2. **User sends `GasLessTransactionData` to the sponsor**: The user forwards the transaction data to the sponsor. +3. **Sponsor validates and signs**: The sponsor reviews the transaction, constructs `TransactionData` with gas fees, and signs it. +4. **Sponsor returns signed transaction**: The sponsor sends the signed `TransactionData` and their `Signature` back to the user. +5. **User verifies and signs**: The user verifies the transaction, signs the `TransactionData`, and submits the dual-signed transaction to the IOTA network via a Full node or through the sponsor. + +### Understanding `GasLessTransactionData` + +`GasLessTransactionData` is essentially a `TransactionData` structure without the `GasData` field. It is not an official `iota-core` data structure but serves as an interface between the user and the sponsor. + +Here is an example of how a `GasLessTransactionData` object might be constructed: + +```rust +pub struct GasLessTransactionData { +pub kind: TransactionKind, +sender: IotaAddress, +… +} +``` + +## IOTA Gas Station + +An IOTA gas station is a concept where you set up processes to sponsor user transactions. +You can customize a gas station to provide specific functionalities based on your needs. Some examples include: + +- **Monitoring gas prices**: Keep track of real-time gas prices on the network to determine the gas price offered. +- **Tracking gas usage**: Monitor how the gas provided to users is utilized on the network. +- **Managing gas pools**: Use specific gas objects to minimize costs or reduce the risk of having a large number of locked objects that remain illiquid while locked. + +### Implement Authorization and Rate Limiting + +To prevent abuse, you can implement various authorization rules: + +- **Rate limiting**: Limit gas requests per account or IP address. +- **Authentication**: Accept requests only with a valid authorization header, each having separate rate limits. + +### Detect and Prevent Abuse + +Monitor all gas objects provided as a sponsor to detect if users attempt to equivocate or lock objects. If such behavior is detected, block the user or requester accordingly. + +### Code Examples for Creating an IOTA Gas Station + +The following Rust SDK code examples demonstrate how to implement an IOTA gas station that supports the various types of sponsored transactions discussed earlier. + +#### User-Initiated Sponsored Transactions + +Use the API endpoint to receive `GaslessTransaction` transactions and return a sole-signed `SenderSignedData` object: + +```rust +pub fn request_gas_and_signature(gasless_tx: GaslessTransaction) -> Result; +``` + +#### Sponsored Transactions with `GasData` Objects + +Use the API endpoint to receive a sole-signed `SenderSignedData` and return the result of the transaction: + +```rust +pub fn submit_sole_signed_transaction(sole_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>; +``` + +Alternatively, provide a `GasData` object via an API endpoint: + +```rust +pub fn request_gas(/*requirement data*/) -> Result; +``` + +#### Sponsor-Initiated Transactions + +Use the API endpoint to receive dual-signed `SenderSignedData` and return the transaction result: + +```rust +pub fn submit_dual_signed_transaction(dual_signed_data: SenderSignedData) -> Result<(Transaction, CertifiedTransactionEffects), Error>; +``` + +For user and sponsor-initiated transactions, users can submit the dual-signed transaction either through the sponsor or directly to a Full node. + +#### Data Structures for Sponsored Transactions + +The following code blocks describe the `TransactionData` structure for sponsored transactions and the `GasData` structure. You can view the [source code](https://github.com/iotaledger/iota/blob/develop/crates/iota-types/src/messages.rs) in the IOTA GitHub repository. + +##### `TransactionData` Structure + +```rust +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +pub struct TransactionDataV1 { +pub kind: TransactionKind, +pub sender: IotaAddress, +pub gas_data: GasData, +pub expiration: TransactionExpiration, +} +``` + +##### `GasData` Structure + +```rust +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +pub struct GasData { +pub payment: Vec, +pub owner: IOTAAddress, +pub price: u64, +pub budget: u64, +} +``` + + diff --git a/docs/content/developer/iota-101/transactions/transactions.mdx b/docs/content/developer/iota-101/transactions/transactions.mdx index 445ec5ae7a2..905a528a5dc 100644 --- a/docs/content/developer/iota-101/transactions/transactions.mdx +++ b/docs/content/developer/iota-101/transactions/transactions.mdx @@ -1,44 +1,66 @@ --- -title: Transactions +description: An introduction to transactions, their types, metadata, and execution flow in Move-based blockchain networks. --- import Quiz from '@site/src/components/Quiz'; import questions from '/json/developer/iota-101/transactions/transactions.json'; -All updates to the IOTA database happen via transactions. This topic describes the transaction types supported by IOTA and explains how their execution changes the ledger. ´There are only two kinds of transactions on IOTA: +# Transactions -- Programmable transaction blocks, which anyone can submit on the network. For information on these transactions, see [Programmable Transaction Blocks](ptb/prog-txn-blocks.mdx). -- System transactions, which only validators can directly submit and are responsible for keeping the network running (changing epochs, starting checkpoints, and so on). +Transactions are fundamental to updating the ledger in Move-based blockchains. -## Transaction metadata +## Types of Transactions -All IOTA transactions have the following common metadata: +In Move, there are two primary types of transactions: -- **Sender address:** The [address](../../getting-started/get-address.mdx) of the user sending this transaction. -- **Gas input:** An object reference pointing to the object that will be used to pay for this transaction's execution and storage. This object must be owned by the user and must be of type `iota::coin::Coin` (i.e., the IOTA native currency). -- **Gas price:** An unsigned integer specifying the number of native tokens per gas unit this transaction will pay. The gas price must always be nonzero. -- **Maximum gas budget:** The maximum number of gas units that can be expended by executing this transaction. If this budget is exceeded, transaction execution will abort and have no effects other than debiting the gas input object. Consequently, the gas input object must have a value higher than the gas price multiplied by the max gas, and this product is the maximum amount that the gas input object will be debited for the transaction. -- **Epoch:** The IOTA epoch this transaction is intended for. -- **Type:** A call, publish, or native transaction and its type-specific-data (see below). -- **Authenticator:** A cryptographic signature and a public key that both verifies against the signature and is cryptographically committed to by the sender address. -- **Expiration:** An epoch reference that sets a deadline after which validators will no longer consider the transaction valid. The optional expiration epoch reference enables users to define transactions that either execute and commit by a set time (current epoch less than or equal to expiration epoch), or never execute after the deadline passes. By default, there is no deadline for when a transaction must execute. +- **User Transactions**: [Programmable transaction blocks](ptb/programmable-transaction-blocks) that any user can submit to the network. They allow you to execute custom logic, interact with smart contracts, and transfer assets. +- **System Transactions**: Exclusive to validators, these transactions are essential for network operations like epoch transitions and checkpointing. They help maintain the blockchain's integrity and performance. -## Transactions flow - example +## Key Components of a Transaction -Here's an example showing how objects and transactions are connected to each other in IOTA. +Every transaction in Move includes essential metadata: -In the following example there are two objects: -- Object A is a coin of type IOTA with a total balance of 5 IOTA -- Object B with 2 IOTA coins that belongs to John +- **Sender Address**: The [account address](../../getting-started/get-address.mdx) initiating the transaction. +- **Gas Payment Object**: An object reference used to pay for the transaction's execution and storage costs. This object must be owned by the sender and be of the type `move::coin::Coin`, representing the native currency. +- **Gas Price**: The amount of native tokens per unit of gas the sender is willing to pay. This must be a positive integer. +- **Maximum Gas Budget**: The upper limit of gas units the transaction can consume. Exceeding this budget will abort the transaction, affecting only the gas payment object by deducting the consumed gas. +- **Target Epoch**: The specific epoch for which the transaction is intended. +- **Transaction Type**: Specifies whether it's a call, publish, or native transaction, along with its associated data. +- **Authenticator**: A cryptographic signature and public key pair that verifies the sender's identity. +- **Expiration Epoch**: An optional deadline after which validators will consider the transaction invalid if not yet executed. By default, transactions have no expiration. -Tom decides to send 1 IOTA coin to Alice. In this case, Object A is the input to this transaction and 1 IOTA coin is debited from this object. The output of the transaction is two objects: -- Object A with 4 IOTA coins that still belongs to Tom -- new created Object C with 1 IOTA coin that belongs now to Alice +## Transaction Workflow Example -At the same time, John decides to send 2 IOTA coins to Anna. Because the relationship between objects and transactions is written in a directed acyclic graph (DAG), and both transactions interact with different objects, this transaction executes in parallel with the transaction that sends coins from Tom to Alice. This transaction changes only the owner of Object B from John to Anna. +To illustrate how transactions interact with objects, consider the following scenario involving IOTA: -After receiving 2 IOTA coins, Anna sent them immediately to Tom. Now Tom has 6 IOTA coins (4 from Object A and 2 from Object B). +### Initial Setup -Finally, Tom sends all of his IOTA coins to John. For this transaction, the input is actually two objects (Object A and Object B). Object B is destroyed, and its value is added to Object A. As a result, the transaction's output is only Object A with a value of 6 IOTA. +- **Object A**: Contains 5 IOTA and belongs to Tom. +- **Object B**: Holds 2 IOTA and belongs to John. + +### 1: Tom Sends 1 IOTA to Alice + +Tom initiates a transaction to send 1 IOTA to Alice using Object A. The transaction results in: + +- **Object A**: Now has 4 IOTA and remains with Tom. +- **Object C**: A new object with 1 IOTA belonging to Alice. + +### 2: John Sends 2 IOTA to Anna + +Simultaneously, John sends his 2 IOTA to Anna. Since this transaction involves different objects, it executes in parallel, transferring ownership of Object B to Anna. + +### 3: Anna Sends 2 IOTA to Tom + +Anna immediately sends the 2 IOTA to Tom. Tom now possesses: + +- **Object A**: 4 IOTA. +- **Object B**: 2 IOTA (ownership transferred to Tom). + +### 4: Tom Sends All IOTA to John + +Finally, Tom decides to send all his IOTA to John. The transaction consumes both Object A and Object B, combining them into: + +- **Object D**: A new object with 6 IOTA belonging to John. +- **Object A** and **Object B**: Consumed and removed from the ledger. ```mermaid flowchart LR @@ -49,24 +71,30 @@ flowchart LR id5(Object B\nfa:fa-coins 2 IOTA\n fa:fa-person Anna):::object-b; id6(Object B\nfa:fa-coins 2 IOTA\n fa:fa-person Tom):::object-b; id7(Object A\nfa:fa-coins 6 IOTA\n fa:fa-person John):::object-a; - id1-->|tx-1|id2; - id1-->|tx-1|id3; - id4-->|tx-2|id5; - id5-->|tx-3|id6; - id3-->|tx-4|id7; - id6-->|tx-4|id7; - classDef object-a fill:#f225; - classDef object-b fill:#ff43; +id1-->|tx-1|id2; +id1-->|tx-1|id3; +id4-->|tx-2|id5; +id5-->|tx-3|id6; +id3-->|tx-4|id7; +id6-->|tx-4|id7; +classDef object-a fill:#f225; +classDef object-b fill:#ff43; ``` -## Limits on transactions, objects, and data +## Transaction and Data Constraints + +Move imposes certain limits to ensure network stability and security: + +- **Maximum Transaction Size**: Transactions have a byte-size limit to prevent excessive resource consumption. +- **Object Limits**: There's a cap on the number of objects a transaction can read or modify. +- **Data Size Restrictions**: Limits are placed on the size of data payloads within transactions. -IOTA has some limits on transactions and the data used in them, such as a maximum size and number of objects used. You can find the these limits in the [`iota-protocol-config` crate](https://github.com/iotaledger/iota/blob/develop/crates/iota-protocol-config/src/lib.rs) of the IOTA repo. The limits are defined in the `ProtocolConfig` struct and values set in the `get_for_version_impl` function. +You can find detailed limits in the [`protocol-config` module](https://github.com/move-language/move/blob/main/language/move-prover/bytecode/src/protocol_config.rs) of the Move repository, defined in the `ProtocolConfig` struct. -## Transactions Execution Flow +## How Transactions Are Executed -The following diagram shows the transaction execution flow reflected by functions called inside different modules. +The following diagram outlines the execution flow of a transaction, showing the sequence of function calls across various modules: -![Transactions execution flow](/img/concepts/execution-architecture/tx-exec-flow.svg) +![Transaction Execution Flow](/img/concepts/execution-architecture/tx-exec-flow.svg) - \ No newline at end of file + diff --git a/docs/content/developer/iota-move-ctf/challenge_4.mdx b/docs/content/developer/iota-move-ctf/challenge_4.mdx index 2442c2b30a8..20a4656ed6a 100644 --- a/docs/content/developer/iota-move-ctf/challenge_4.mdx +++ b/docs/content/developer/iota-move-ctf/challenge_4.mdx @@ -35,7 +35,7 @@ This challnege can be solved with IOTA PTBs, which will also help you in further - [Coin Standard](../standards/coin.mdx) - [Object Model](../iota-101/objects/object-model.mdx) -- [Programmable Transaction Blocks](../iota-101/transactions/ptb/working-with-ptbs.mdx) +- [Programmable Transaction Blocks](../iota-101/transactions/ptb/programmable-transaction-blocks-overview.mdx) diff --git a/docs/content/developer/iota-move-ctf/challenge_7.mdx b/docs/content/developer/iota-move-ctf/challenge_7.mdx index 2e534975317..586a34c327f 100644 --- a/docs/content/developer/iota-move-ctf/challenge_7.mdx +++ b/docs/content/developer/iota-move-ctf/challenge_7.mdx @@ -26,7 +26,7 @@ Package: 0x202d65a2b1d2de4ba90e9eeb51ef4e16fafdaaa5c8b1dc3cbd8a935e5eb4d25c This challenge will introduce you to the PTB standard and how to use the Move CLI to interact with it. You should be familiar with the PTB standard and how to use the Move CLI to call the `ptb` function. -- [PTB Standard](../iota-101/transactions/ptb/working-with-ptbs.mdx) +- [PTB Standard](../iota-101/transactions/ptb/programmable-transaction-blocks-overview.mdx) - [IOTA CLI reference](../../references/cli/ptb.mdx) diff --git a/docs/content/developer/iota-move-ctf/challenge_8.mdx b/docs/content/developer/iota-move-ctf/challenge_8.mdx index 66cf831c1b7..86e3ddfded1 100644 --- a/docs/content/developer/iota-move-ctf/challenge_8.mdx +++ b/docs/content/developer/iota-move-ctf/challenge_8.mdx @@ -7,7 +7,7 @@ import ChallengeVerifier from '@site/src/components/CTF/ctf-verifier'; # Challenge 8: Flash! In this challenge, you will explore a decentralized exchange (DEX) with a critical flaw you can exploit to capture the flag. This exchange operates with two tokens—CTFA and CTFB—and features a vault that allows users to take flash loans. Your objective is to manipulate the token balances effectively to obtain the flag by using the vulnerabilities in the DEX's flash loan mechanism. -To solve this challenge, you will have to have a deep understanding of [programmable transaction blocks (PTBs)](../iota-101/transactions/ptb/prog-txn-blocks.mdx) and how to build them using the [TS SDK](../iota-101/transactions/ptb/building-ptb.mdx) or the [CLI](../../references/cli/ptb.mdx). +To solve this challenge, you will have to have a deep understanding of [programmable transaction blocks (PTBs)](../iota-101/transactions/ptb/programmable-transaction-blocks-overview.mdx) and how to build them using the [TS SDK](../iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx) or the [CLI](../../references/cli/ptb.mdx). ## Deployed Contract Addresses: ``` @@ -40,9 +40,9 @@ This challenge will test your understanding of the Object Model, the Coin Standa - [Coin Standard](../standards/coin.mdx) - [Object Model](../iota-101/objects/object-model.mdx) -- [Programmable Transaction Blocks](../iota-101/transactions/ptb/working-with-ptbs.mdx) +- [Programmable Transaction Blocks](../iota-101/transactions/ptb/programmable-transaction-blocks-overview.mdx) - [IOTA CLI reference](../../references/cli/ptb.mdx) -- [IOTA TS SDK reference](../iota-101/transactions/ptb/building-ptb.mdx) +- [IOTA TS SDK reference](../iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx) diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index f95616c4afe..471298bb3e1 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -7,7 +7,7 @@ import MigrationWarning from '../../_snippets/migration-warning.mdx'; -As detailed in the [Stardust Move Models](move-models.mdx), Stardust assets are represented as Move objects within the ledger. Claiming these assets involves enabling original owners to utilize a [Programmable Transaction Block](../iota-101/transactions/ptb/prog-txn-blocks.mdx) to "unlock" assets such as IOTA, custom [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin)s, or even `Alias` and `Nft` objects. +As detailed in the [Stardust Move Models](move-models.mdx), Stardust assets are represented as Move objects within the ledger. Claiming these assets involves enabling original owners to utilize a [Programmable Transaction Block](../iota-101/transactions/ptb/programmable-transaction-blocks) to "unlock" assets such as IOTA, custom [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin)s, or even `Alias` and `Nft` objects. This process takes advantage of Move's unique features to ensure that assets are transferred and unlocked securely and efficiently to their rightful owners. @@ -21,7 +21,7 @@ Below, you will find one or more examples of claiming each Output type. ## Examples of Stardust Asset Claim Transactions Here are some examples of transactions for claiming Stardust assets. -Different commands in a [PTB](../iota-101/transactions/ptb/prog-txn-blocks.mdx) are used depending on the claiming scenario, +Different commands in a [PTB](../iota-101/transactions/ptb/programmable-transaction-blocks) are used depending on the claiming scenario, which varies based on the Stardust Output type and composition. ### Basic Output diff --git a/docs/content/developer/stardust/claiming/alias.mdx b/docs/content/developer/stardust/claiming/alias.mdx index 6145a9e8968..7b248a77505 100644 --- a/docs/content/developer/stardust/claiming/alias.mdx +++ b/docs/content/developer/stardust/claiming/alias.mdx @@ -59,7 +59,7 @@ Next, check the native tokens that might be held by this output. A [`Bag`](../.. ### 3. Create the PTB -Finally, create a [Programmable Transaction Block (PTB)](../../iota-101/transactions/ptb/building-ptb.mdx) +Finally, create a [Programmable Transaction Block (PTB)](../../iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx) using the `alias_output_object_ref` as input along with the native token keys. An `AliasOutput` differs from an `NftOutput` or a `BasicOutput` because it contains the `Alias` object. The main purpose of claiming is to extract the `Alias` object from the `AliasOutput`. diff --git a/docs/content/developer/stardust/claiming/nft.mdx b/docs/content/developer/stardust/claiming/nft.mdx index c71927bc4b9..ae33a5c6d54 100644 --- a/docs/content/developer/stardust/claiming/nft.mdx +++ b/docs/content/developer/stardust/claiming/nft.mdx @@ -66,7 +66,7 @@ representing the [`OTW`](../../iota-101/move-overview/one-time-witness.mdx) used ### 3. Create the PTB Finally, -you can create a [Programmable Transaction Block (PTB)](../../iota-101/transactions/ptb/building-ptb.mdx) using the `nft_output` as an input, +you can create a [Programmable Transaction Block (PTB)](../../iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx) using the `nft_output` as an input, along with the `Bag` keys to iterate over the extracted native tokens. The primary goal of this process is to extract the `Nft` object from the `NftOutput`. diff --git a/docs/content/developer/stardust/claiming/self-sponsor.mdx b/docs/content/developer/stardust/claiming/self-sponsor.mdx index 9b29c875c8b..2d473fb1141 100644 --- a/docs/content/developer/stardust/claiming/self-sponsor.mdx +++ b/docs/content/developer/stardust/claiming/self-sponsor.mdx @@ -36,7 +36,7 @@ Use the IOTA `coin_type` to derive the sponsor and sender addresses. ### 2. Create the PTB for Claiming -Next, create a [Programmable Transaction Block (PTB)](../../iota-101/transactions/ptb/building-ptb.mdx) +Next, create a [Programmable Transaction Block (PTB)](../../iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.mdx) to claim a `BasicOutput` owned by the derived Iota address. This process is similar to the one outlined in the [Basic Output](basic.mdx) guide. diff --git a/docs/content/references/cli/client.mdx b/docs/content/references/cli/client.mdx index 26625324eac..87060609c08 100644 --- a/docs/content/references/cli/client.mdx +++ b/docs/content/references/cli/client.mdx @@ -206,7 +206,7 @@ Both `pay` and `transfer` have a few sister commands: `pay-iota`, `pay-all-iota` The differences between these commands are: - commands that end in `-iota` deal with Iota's native coin, and they use the input coints to pay for gas and for transferring IOTA or the object. -- `pay-` commands typically deal with coins and handle gas smashing for you, whereas `transfer` commands can handle the transfer of any object that has public transfer, meaning any object that has the `store` ability. +- `pay-` commands typically deal with coins and handle (coin merging)[../../developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.mdx] for you, whereas `transfer` commands can handle the transfer of any object that has public transfer, meaning any object that has the `store` ability. - `pay` commands allow you to send coins to multiple recipients, whereas `transfer` commands only accept one recipient. - `pay-all-iota` is a special case of `pay-iota` that offers a way to transfer the entire balance after smashing. - `transfer-iota` is a legacy command and has been entirely superseded by `pay-iota` or `pay-all-iota` depending on whether an amount is specified or not. diff --git a/docs/content/references/cli/ptb.mdx b/docs/content/references/cli/ptb.mdx index c85de0d95fa..7f50f2d925c 100644 --- a/docs/content/references/cli/ptb.mdx +++ b/docs/content/references/cli/ptb.mdx @@ -51,7 +51,7 @@ Options: The main philosophy behind the CLI PTB support is to enable a user to build and execute a PTB from the command line. Bash scripts can be used to construct and execute the PTB just as you would do from the command line, providing great flexibility when it comes to automating different tasks. -Besides using existing [traditional PTB](../../developer/iota-101/transactions/ptb/prog-txn-blocks.mdx) related concepts, we introduce a few new and important concepts for this command. +Besides using existing [traditional PTB](../../developer/iota-101/transactions/ptb/programmable-transaction-blocks) related concepts, we introduce a few new and important concepts for this command. :::warning diff --git a/docs/content/references/ts-sdk/typescript/owned-object-pool/overview.mdx b/docs/content/references/ts-sdk/typescript/owned-object-pool/overview.mdx index 731e2ebb40b..7e44ffa7bc9 100644 --- a/docs/content/references/ts-sdk/typescript/owned-object-pool/overview.mdx +++ b/docs/content/references/ts-sdk/typescript/owned-object-pool/overview.mdx @@ -14,7 +14,7 @@ benefit of the lower latency those objects provide. On top of that, they are imp completely avoid because the transaction's gas coin is an owned object. Finally, the situation is exacerbated by -[gas smashing](../../../../developer/iota-101/transactions/gas-smashing.mdx) and the IOTA TypeScript SDK's +[coin-merging](../../../../developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.mdx) and the IOTA TypeScript SDK's default coin selection logic, which uses all the `0x2::coin::Coin<0x2::iota::IOTA>` objects owned by an address for every transaction's gas payment. These defaults make sending transactions from your wallet straightforward (doing so automatically cleans up coin dust), but means that developers diff --git a/docs/content/sidebars/developer.js b/docs/content/sidebars/developer.js index f35cb4a423f..b79c31189c1 100644 --- a/docs/content/sidebars/developer.js +++ b/docs/content/sidebars/developer.js @@ -129,25 +129,32 @@ const developer = [ id: 'developer/iota-101/transactions/transactions', }, items: [ - 'developer/iota-101/transactions/sign-and-send-txn', + 'developer/iota-101/transactions/sign-and-send-transactions', { type: 'category', label: 'Sponsored Transactions', link: { type: 'doc', - id: 'developer/iota-101/transactions/sponsor-txn', + id: 'developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions', }, - items: ['developer/iota-101/transactions/sponsored-transactions'], + items: [ + 'developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions', + 'developer/iota-101/transactions/sponsored-transactions/use-sponsored-transactions'], }, - 'developer/iota-101/transactions/gas-smashing', { type: 'category', label: 'Working with PTBs', + link: { + type: 'doc', + id:'developer/iota-101/transactions/ptb/programmable-transaction-blocks-overview', + }, items: [ - 'developer/iota-101/transactions/ptb/prog-txn-blocks', - 'developer/iota-101/transactions/ptb/building-ptb', - 'developer/iota-101/transactions/ptb/coin-mgt', - 'developer/iota-101/transactions/ptb/simulating-refs', + 'developer/iota-101/transactions/ptb/programmable-transaction-blocks', + 'developer/iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk', + 'developer/iota-101/transactions/ptb/simulating-references', + 'developer/iota-101/transactions/ptb/coin-management', + 'developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging', + ], }, ], diff --git a/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/sponsored-txn.png b/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/sponsored-txn.png new file mode 100644 index 00000000000..87d82ea870f Binary files /dev/null and b/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/sponsored-txn.png differ diff --git a/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/user-proposed-txn.png b/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/user-proposed-txn.png new file mode 100644 index 00000000000..bf41cde4e1f Binary files /dev/null and b/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/user-proposed-txn.png differ diff --git a/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/wildcard-txn.png b/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/wildcard-txn.png new file mode 100644 index 00000000000..9521b65d653 Binary files /dev/null and b/docs/site/static/img/developer/iota-101/transactions/sponsored-transactions/wildcard-txn.png differ diff --git a/docs/site/static/json/developer/iota-101/transactions/ptb/building-ptb.json b/docs/site/static/json/developer/iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/ptb/building-ptb.json rename to docs/site/static/json/developer/iota-101/transactions/ptb/building-programmable-transaction-blocks-ts-sdk.json diff --git a/docs/site/static/json/developer/iota-101/transactions/ptb/coin-mgt.json b/docs/site/static/json/developer/iota-101/transactions/ptb/coin-management.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/ptb/coin-mgt.json rename to docs/site/static/json/developer/iota-101/transactions/ptb/coin-management.json diff --git a/docs/site/static/json/developer/iota-101/transactions/gas-smashing.json b/docs/site/static/json/developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/gas-smashing.json rename to docs/site/static/json/developer/iota-101/transactions/ptb/optimizing-gas-with-coin-merging.json diff --git a/docs/site/static/json/developer/iota-101/transactions/ptb/prog-txn-blocks.json b/docs/site/static/json/developer/iota-101/transactions/ptb/programmable-transaction-blocks.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/ptb/prog-txn-blocks.json rename to docs/site/static/json/developer/iota-101/transactions/ptb/programmable-transaction-blocks.json diff --git a/docs/site/static/json/developer/iota-101/transactions/ptb/simulating-refs.json b/docs/site/static/json/developer/iota-101/transactions/ptb/simulating-references.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/ptb/simulating-refs.json rename to docs/site/static/json/developer/iota-101/transactions/ptb/simulating-references.json diff --git a/docs/site/static/json/developer/iota-101/transactions/sign-and-send-txn.json b/docs/site/static/json/developer/iota-101/transactions/sign-and-send-transactions.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/sign-and-send-txn.json rename to docs/site/static/json/developer/iota-101/transactions/sign-and-send-transactions.json diff --git a/docs/site/static/json/developer/iota-101/transactions/sponsored-transactions.json b/docs/site/static/json/developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/sponsored-transactions.json rename to docs/site/static/json/developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions.json diff --git a/docs/site/static/json/developer/iota-101/transactions/sponsor-txn.json b/docs/site/static/json/developer/iota-101/transactions/sponsored-transactions/use-sponsored-transactions.json similarity index 100% rename from docs/site/static/json/developer/iota-101/transactions/sponsor-txn.json rename to docs/site/static/json/developer/iota-101/transactions/sponsored-transactions/use-sponsored-transactions.json