diff --git a/docs/content/developer/developer.mdx b/docs/content/developer/developer.mdx index 747e751157a..a5dc0b0e1e3 100644 --- a/docs/content/developer/developer.mdx +++ b/docs/content/developer/developer.mdx @@ -39,7 +39,7 @@ Go to [Cryptography](cryptography.mdx). Utilizing standards while developing applications is very important for composability. In this section you can find out all about the standards within the IOTA Move ecosystem for dealing with tokens, NFT like objects, and wallets. -Go to the [Standards](standards/standards.mdx). +Go to the [Standards](/developer/standards). ## Advanced Topics @@ -69,4 +69,4 @@ Go to [Migrating from IOTA/Shimmer Stardust](stardust/stardust-migration.mdx). This section contains the technical details needed to integrate IOTA on a exchange. -Go to [Exchange integration](exchange-integration/exchange-integration.mdx). \ No newline at end of file +Go to [Exchange integration](exchange-integration/exchange-integration.mdx). diff --git a/docs/content/developer/standards/closed-loop-token.mdx b/docs/content/developer/standards/closed-loop-token.mdx index d8a36fc16fe..cad0586b726 100644 --- a/docs/content/developer/standards/closed-loop-token.mdx +++ b/docs/content/developer/standards/closed-loop-token.mdx @@ -2,13 +2,13 @@ title: Closed-Loop Token --- -Using the Closed-Loop Token standard, you can limit the applications that can use the token and set up custom policies for transfers, spends, and conversions. The [`iota::token` module](https://github.com/iotaledger/iota/blob/main/crates/iota-framework/docs/iota-framework/token.md) in the IOTA framework defines the standard. +Using the Closed-Loop Token standard, you can limit the applications that can use the token and set up custom policies for transfers, spends, and conversions. The [`iota::token` module](../../references/framework/iota-framework/token.mdx) in the IOTA framework defines the standard. ## Background and use cases The [Coin standard](coin.mdx) on IOTA is an example of an open-loop system - coins are free-flowing, [wrappable](../iota-101/objects/object-ownership/wrapped.mdx), [freely transferable](../iota-101/objects/transfers/custom-rules.mdx#the-store-ability-and-transfer-rules) and you can store them in any application. The best real world analogy would be cash - hardly regulated and can be freely used and passed. -Some applications, however, require constraining the scope of the token to a specific purpose. For example, some applications might need a token that you can only use for a specific service, or that an authorized account can only use, or a token that you can block certain accounts from using. A real-world analogy would be a bank account - regulated, bank-controlled, and compliant with certain rules and policies. +Some applications, however, require constraining the scope of the token to a specific purpose. For example, some applications might need a token that you can only use for a specific service, or that only an authorized account can use, or a token that you can block certain accounts from using. A real-world analogy would be a bank account - regulated, bank-controlled, and compliant with certain rules and policies. ## Difference with Coin diff --git a/docs/content/developer/standards/closed-loop-token/action-request.mdx b/docs/content/developer/standards/closed-loop-token/action-request.mdx index 2bc9f9a9f91..582fb781440 100644 --- a/docs/content/developer/standards/closed-loop-token/action-request.mdx +++ b/docs/content/developer/standards/closed-loop-token/action-request.mdx @@ -146,7 +146,7 @@ tx.moveCall({ ### Confirming with TokenPolicyCap -Use `TokenPolicyCap` to confirm action requests. A convenient approach when the `TreasuryCap` is wrapped in another object, and `TokenPolicy` does not allow certain action or has rules that make the default way of confirming impossible. +Use `TokenPolicyCap` to confirm action requests. A convenient approach when the `TreasuryCap` is wrapped in another object, and `TokenPolicy` does not allow a certain action or has rules that make the default way of confirming impossible. :::info @@ -192,7 +192,7 @@ tx.moveCall({ ## Approving actions -`ActionRequest`s can collect approvals - witness stamps from applications or rules. They carry the confirmation that a certain module or a rule has approved the action. This mechanic allows gating actions behind certain requirements. +`ActionRequest`s can collect approvals - witness stamps from applications or rules. They carry the confirmation that a certain module or a rule has approved the action. This mechanism allows gating actions behind certain requirements. The signature for the `token::add_approval` function is: diff --git a/docs/content/developer/standards/closed-loop-token/rules.mdx b/docs/content/developer/standards/closed-loop-token/rules.mdx index 3fd38e46619..f9bb9488633 100644 --- a/docs/content/developer/standards/closed-loop-token/rules.mdx +++ b/docs/content/developer/standards/closed-loop-token/rules.mdx @@ -10,7 +10,7 @@ A rule is represented as a witness - a type with a `drop` ability. You can eithe ```move /// The Rule type -struct Rule has drop {} +public struct Rule has drop {} ``` After you [add a rule](token-policy.mdx#adding-rules) to an action in the `TokenPolicy`, the action requires a stamp of the rule to pass confirmation. @@ -25,11 +25,10 @@ A rule module is a regular module with a `verify`-like function that typically t ```move module example::pass_rule { - use iota::tx_context; use iota::token::{Self, ActionRequest, TokenPolicy}; /// The Rule type - struct Pass has drop {} + public struct Pass has drop {} /// Add approval from the Pass rule to the ActionRequest public fun verify( diff --git a/docs/content/developer/standards/closed-loop-token/spending.mdx b/docs/content/developer/standards/closed-loop-token/spending.mdx index 5d28ee32023..5f09f149eb4 100644 --- a/docs/content/developer/standards/closed-loop-token/spending.mdx +++ b/docs/content/developer/standards/closed-loop-token/spending.mdx @@ -28,7 +28,7 @@ Normally, the `spend` action should have at least one rule assigned to it to pre ```move /// Rule-like witness to stamp the ActionRequest -struct GiftShop has drop {} +public struct GiftShop has drop {} /// Spend the token and return a Gift + ActionRequest public fun buy_gift( diff --git a/docs/content/developer/standards/coin-manager.mdx b/docs/content/developer/standards/coin-manager.mdx index 88fdf2f3041..f554129df08 100644 --- a/docs/content/developer/standards/coin-manager.mdx +++ b/docs/content/developer/standards/coin-manager.mdx @@ -21,15 +21,15 @@ By having your `Coin` managed by a `CoinManager`, you gain the following functio - Both management caps can transparently be renounced through the `CoinManager` so that everyone on-chain can know that the supply and metadata are immutable, Offering end-users clarity and assurance. - A `CoinManagerTreasuryCap`-owned object will allow you to set a maximum supply for a coin. This will ensure that once set (a one-time, irrevocable action), there will never be any more tokens minted as the provided maximum supply. This offers assurances to the end-user about the asset they are interested in. - A custom additional Metadata object can be provided, allowing coins to have more data than just the standard fields provided by the default `Metadata` object. The existing metadata object will stay the same and remain fully compatible. -- The total supply of a given `Coin` type, the maximum supply, and the metadata can be transparently queried through the `CoinManager` by anyone interested, not just the `TreauryCap` owner. +- The total supply of a given `Coin` type, the maximum supply, and the metadata can be transparently queried through the `CoinManager` by anyone interested, not just the `TreasuryCap` owner. -With a `CoinManager` in place, you can offer assurances to whoever uses your `Coin` that can not be offered with just a regular `Coin` and `TreasuryCap`. We recommend every new coin utilize the `CoinManager.` Any existing coin can be managed by a `CoinManager` as long as the `TreasuryCap` object for that `Coin` is still in possession. +With a `CoinManager` in place, you can offer assurances to whoever uses your `Coin` that can not be offered with just a regular `Coin` and `TreasuryCap`. We recommend every new coin utilize the `CoinManager`. Any existing coin can be managed by a `CoinManager` as long as the `TreasuryCap` object for that `Coin` is still in possession. -## How To Manage A `Coin` With A `CoinManager` +## How To Manage a `Coin` With a `CoinManager` ### New `Coin` Assets -When you are starting a new `Coin` type and wish to use the `CoinManager` you can use the `CoinManager` directly to create this `Coin`. You will receive back the `CoinManagerTreasuryCap` and a `CoinManagerMetadataCap` to perform any follow-up management actions: +When you are creating a new `Coin` type and wish to use the `CoinManager` you can use the `CoinManager` directly to create this `Coin`. You will receive back the `CoinManagerTreasuryCap` and a `CoinManagerMetadataCap` to perform any follow-up management actions: ```move module example::exclusive_coin { @@ -37,10 +37,9 @@ module example::exclusive_coin { public struct EXCLUSIVE_COIN has drop {} - #[allow(lint(share_owned))] fun init(witness: EXCLUSIVE_COIN, ctx: &mut TxContext) { // Create a `Coin` type and have it managed. - let (cm_treasury_cap, cm_meta_cap, mut manager) = coin_manager::create( + let (cm_treasury_cap, cm_meta_cap, manager) = coin_manager::create( witness, 0, b"EXCL", @@ -50,13 +49,13 @@ module example::exclusive_coin { ctx ); - // Returning a new `CoinManagerTreasuryCap` to the creator of the `Coin` + // Transfer the `CoinManagerTreasuryCap` to the creator of the `Coin`. transfer::public_transfer(cm_treasury_cap, ctx.sender()); - // Returning a new `CoinManagerMetadataCap` to the creator of the `Coin` + // Transfer the `CoinManagerMetadataCap` to the creator of the `Coin`. transfer::public_transfer(cm_meta_cap, ctx.sender()); - // Publicly sharing the `CoinManager` object for convenient usage by anyone interested + // Publicly share the `CoinManager` object for convenient usage by anyone interested. transfer::public_share_object(manager); } } @@ -71,12 +70,12 @@ If you already have an existing `Coin`, you can create the `CoinManager` object If you already froze the `Metadata` object you can only migrate to a `CoinManager` that has immutable metadata from the start. You will not receive a `CoinManagerMetadataCap` in return, but you will get a `CoinManagerTreasuryCap`: ```move -let (cm_treasury_cap, mut manager) = coin_manager::new_with_immutable_metadata(cap, &meta, ctx); +let (cm_treasury_cap, manager) = coin_manager::new_with_immutable_metadata(cap, &meta, ctx); -// Returning a new `CoinManagerTreasuryCap` to the creator of the `Coin` +// Transfer the `CoinManagerTreasuryCap` to the creator of the `Coin`. transfer::public_transfer(cm_treasury_cap, ctx.sender()); -// Publicly sharing the `CoinManager` object for convenient usage by anyone interested +// Publicly share the `CoinManager` object for convenient usage by anyone interested. transfer::public_share_object(manager); ``` #### With mutable metadata @@ -84,15 +83,15 @@ transfer::public_share_object(manager); If the metadata object is still owned, you can take advantage of the full functionality of the `CoinManager` with mutable `Metadata`: ```move -let (cm_treasury_cap, cm_meta_cap, mut manager) = coin_manager::new(cap, meta, ctx); +let (cm_treasury_cap, cm_meta_cap, manager) = coin_manager::new(cap, meta, ctx); -// Returning a new `CoinManagerTreasuryCap` to the creator of the `Coin` +// Transfer the `CoinManagerTreasuryCap` to the creator of the `Coin`. transfer::public_transfer(cm_treasury_cap, ctx.sender()); -// Returning a new `CoinManagerMetadataCap` to the creator of the `Coin` +// Transfer the `CoinManagerMetadataCap` to the creator of the `Coin`. transfer::public_transfer(cm_meta_cap, ctx.sender()); -// Publicly sharing the `CoinManager` object for convenient usage by anyone interested +// Publicly share the `CoinManager` object for convenient usage by anyone interested. transfer::public_share_object(manager); ``` @@ -103,10 +102,10 @@ Once the `CoinManager` has been created and publicly shared you can make use of ### Retrieving metadata ```move -/// Get the decimals for a Coin managed by this manager, without needing the Metadata object +/// Get the decimals for the `Coin` of this manager, without needing the Metadata object. let decimals = manager.decimals(); -/// See if the Metadata is immutable or if it can still be changed later: +/// See if the Metadata is immutable or if it can still be changed later. let immutabe = manager.metadata_is_immutable(); ``` @@ -118,17 +117,17 @@ let max_supply = manager.maximum_supply(); let remaining_supply = manager.available_supply(); let has_maximum_supply = manager.has_maximum_supply(); let supply_reference = manager.supply_immut(); -let immutabe = manager.supply_is_immutable(); +let supply_is_immutable = manager.supply_is_immutable(); ``` ### Minting ```move -/// Minting more coins, if allowed +/// Mint more coins, if allowed. let coin = coin_manager_treasury_cap.mint(&mut manager, 100, ctx); ``` -Next to minting the same functionality is available that usually is available on the `TreasuryCap` like `burn`, `mint_balance`, and `mint_and_transfer`. +Next to minting, the same functionality is available that usually is available on the `TreasuryCap` like `burn`, `mint_balance` and `mint_and_transfer`. ### Updating Metadata @@ -143,10 +142,10 @@ coin_manager_metadata_cap.update_symbol(&mut manager, "NEW"); By renouncing ownership (handing in your cap(s)), you provide assurances for your `Coin` type to its users. Users can check if a `Coin` type has an immutable supply or metadata on-chain. ```move -/// Turns the supply immutable, no more minting or max. supply changes +/// Turns the supply immutable. No more minting or maximum supply changes. coin_manager_treasury_cap.renounce_ownership(&mut manager); -/// Turns the metadata immutable +/// Turns the metadata immutable. coin_manager_metadata_cap.renounce_ownership(&mut manager); ``` @@ -169,15 +168,14 @@ let version = wrapper.additional_metadata().version; If you wish to update or replace your custom Metadata object with a new one (of the same type or another), you can do so using the `replace_additional_metadata` function, which returns the old Metadata object: ```move - public struct NewCustomMetadata has store { - website: Url, - is_amazing: bool + website: Url, + is_amazing: bool } let new_meta = NewCustomMetadata { - website: url::new_unsafe(string(b"https://iota.org")), - is_amazing: true + website: url::new_unsafe(string(b"https://iota.org")), + is_amazing: true }; let oldmeta = metacap.replace_additional_metadata(&mut wrapper, new_meta); diff --git a/docs/content/developer/standards/coin.mdx b/docs/content/developer/standards/coin.mdx index 5276c3597dd..3a566ba903a 100644 --- a/docs/content/developer/standards/coin.mdx +++ b/docs/content/developer/standards/coin.mdx @@ -174,14 +174,15 @@ let deny_list = ptb.obj(ObjectArg::SharedObject { mutable: true, })?; let deny_cap = ptb.obj(ObjectArg::ImmOrOwnedObject(deny_cap))?; -let address = ptb.pure(cmd.address())?; -ptb.command(Command::move_call( +let address_to_ban = IotaAddress::from_str("0x...")?; +let address_to_ban_arg = ptb.pure(address_to_ban)?; +ptb.programmable_move_call( IOTA_FRAMEWORK_PACKAGE_ID, Identifier::from(COIN_MODULE_NAME), - Identifier::from_str("deny_list_add".to_string())?, + Identifier::from_str("deny_list_add")?, vec![], - vec![deny_list, deny_cap, address], -)); + vec![deny_list, deny_cap, address_to_ban_arg], +); let builder = ptb.finish(); ``` @@ -191,7 +192,7 @@ let builder = ptb.finish(); - `SequenceNumber` is the `initial_shared_version` of the `DenyList` singleton. - `deny_cap` is the `ObjectRef` (`(ObjectID, SequenceNumber, ObjectDigest)`) of the `DenyCap` the publisher has received. - `otw_type` is the `TypeTag` created from `::regulated_coin::REGULATED_COIN` type. -- `cmd.address()` returns the address to ban as a `IOTAAddress`. +- `address_to_ban` is the address to ban as an `IotaAddress`. @@ -266,6 +267,6 @@ Check out the following content for more information about coins and tokens on I - [Create a Coin](../iota-101/create-coin/create-coin.mdx): Guide for creating coins and regulated coins in your smart contracts. - [Closed-Loop Token Standard](closed-loop-token.mdx): Details the Token standard on IOTA. -- [`coin` module rustdoc documentation](https://github.com/iotaledger/iota/blob/main/crates/iota-framework/docs/iota-framework/coin.md): Automated documentation output for the IOTA framework `coin` module. -- [`token` module rustdoc documentation](https://github.com/iotaledger/iota/blob/main/crates/iota-framework/docs/iota-framework/token.md): Automated documentation output for the IOTA framework `token` module. +- [`coin` module documentation](../../references/framework/iota-framework/coin.mdx): Automated documentation output for the IOTA framework `coin` module. +- [`token` module documentation](../../references/framework/iota-framework/token.mdx): Automated documentation output for the IOTA framework `token` module. - [Tokenomics](../../about-iota/tokenomics.mdx): Discover the IOTA ecosystem and where IOTA coins fit within it. \ No newline at end of file diff --git a/docs/content/developer/standards/display.mdx b/docs/content/developer/standards/display.mdx index 5a69306960e..8c4d35c1f26 100644 --- a/docs/content/developer/standards/display.mdx +++ b/docs/content/developer/standards/display.mdx @@ -7,7 +7,7 @@ The IOTA Object Display standard is a template engine that enables on-chain mana Use a `Publisher` object that you own to set `iota::display` for a type. For more information about `Publisher` objects, see [Publisher](https://examples.iota.io/basics/publisher.html) topic in *IOTA Move by Example*. -In IOTA Move, `Display` represents an object that specifies a set of named templates for the type `T`. For example, for a type `0x2::capy::Capy` the display syntax is: `Display<0x2::capy::Capy>`. +In IOTA Move, `Display` represents an object that specifies a set of named templates for the type `T`. For example, for a type `0x2::hero::Hero` the display syntax is: `Display<0x2::hero::Hero>`. IOTA Full nodes process all objects of the type `T` by matching the `Display` definition, and return the processed result when you query an object with the `{ showDisplay: true }` setting in the query. @@ -45,24 +45,21 @@ The following represents the template the `init` function defines: /// to use it to get the `Display` object - a way to describe a /// type for the ecosystem. module examples::my_hero { - use iota::tx_context::{sender, TxContext}; use std::string::{utf8, String}; - use iota::transfer; - use iota::object::{Self, UID}; // The creator bundle: these two packages often go together. use iota::package; use iota::display; /// The Hero - an outstanding collection of digital art. - struct Hero has key, store { + public struct Hero has key, store { id: UID, name: String, image_url: String, } /// One-Time-Witness for the module. - struct MY_HERO has drop {} + public struct MY_HERO has drop {} /// In the module initializer one claims the `Publisher` object /// to then create a `Display`. The `Display` is initialized with @@ -82,17 +79,17 @@ module examples::my_hero { ]; let values = vector[ - // For `name` one can use the `Hero.name` property + // For `name` one can use the `Hero.name` property. utf8(b"{name}"), - // For `link` one can build a URL using an `id` property + // For `link` one can build a URL using an `id` property. utf8(b"https://iota-heroes.io/hero/{id}"), // For `image_url` use an IPFS template + `image_url` property. utf8(b"ipfs://{image_url}"), // Description is static for all `Hero` objects. utf8(b"A true Hero of the IOTA ecosystem!"), - // Project URL is usually static + // Project URL is usually static. utf8(b"https://iota-heroes.io"), - // Creator field can be any + // Creator field can be any. utf8(b"Unknown IOTA Fan") ]; @@ -100,15 +97,15 @@ module examples::my_hero { let publisher = package::claim(otw, ctx); // Get a new `Display` object for the `Hero` type. - let display = display::new_with_fields( + let mut display = display::new_with_fields( &publisher, keys, values, ctx ); // Commit first version of `Display` to apply changes. display::update_version(&mut display); - transfer::public_transfer(publisher, sender(ctx)); - transfer::public_transfer(display, sender(ctx)); + transfer::public_transfer(publisher, ctx.sender()); + transfer::public_transfer(display, ctx.sender()); } /// Anyone can mint their `Hero`! @@ -137,17 +134,17 @@ After you create the `Display`, you can modify it. The following code sample dem ```move module iota::display { - /// Sets multiple fields at once + /// Sets multiple fields at once. public fun add_multiple( self: &mut Display, keys: vector, values: vector ) { /* ... */ } - /// Edit a single field + /// Edit a single field. public fun edit(self: &mut Display, key: String, value: String) { /* ... */ } - /// Remove a key from Display + /// Remove a key from Display. public fun remove(self: &mut Display, key: String ) { /* ... */ } } ``` @@ -168,14 +165,15 @@ module iota::display { In IOTA, utility objects enable authorization for capabilities. Almost all modules have features that can be accessed only with the required capability. Generic modules allow one capability per application, such as a marketplace. Some capabilities mark ownership of a shared object on-chain, or access the shared data from another account. With capabilities, it is important to provide a meaningful description of objects to facilitate user interface implementation. This helps avoid accidentally transferring the wrong object when objects are similar. It also provides a user-friendly description of items that users see. -The following example demonstrates how to create a capy capability: +The following example demonstrates how to create a pet capability: ```move -module capy::utility { - /// A capability which grants Capy Manager permission to add - /// new genes and manage the Capy Market - struct CapyManagerCap has key, store { - id: UID } +module pet::utility { + /// A capability which grants Pet Manager permission to add + /// new genes and manage the Pet Market. + public struct PetManagerCap has key, store { + id: UID + } } ``` @@ -184,36 +182,36 @@ module capy::utility { A common case with in-game items is to have a large number of similar objects grouped by some criteria. It is important to optimize their size and the cost to mint and update them. Typically, a game uses a single source image or URL per group or item criteria. Storing the source image inside of every object is not optimal. In some cases, users mint in-game items when a game allows them or when they purchase an in-game item. To enable this, some IPFS/Arweave metadata must be created and stored in advance. This requires additional logic that is usually not related to the in-game properties of the item. -The following example demonstrates how to create a Capy: +The following example demonstrates how to create a Pet: ```move -module capy::capy_items { - /// A wearable Capy item. For some items there can be an - /// unlimited supply. And items with the same name are identical. - struct CapyItem has key, store { +module pet::pet_items { + /// A wearable Pet item. For some items there can be an + /// unlimited supply. And items with the same name are identical. + public struct PetItem has key, store { id: UID, name: String - } + } } ``` ## Unique objects with dynamic representation -IOTA Capys use dynamic image generation. When a Capy is born, its attributes determine the Capy’s appearance, such as color or pattern. When a user puts an item on a Capy, the Capy’s appearance changes. When users put multiple items on a Capy, there’s a chance of a bonus for a combination of items. +IOTA Pets use dynamic image generation. When a Pet is born, its attributes determine the Pet’s appearance, such as color or pattern. When a user puts an item on a Pet, the Pet’s appearance changes. When users put multiple items on a Pet, there’s a chance of a bonus for a combination of items. -To implement this, the Capys game API service refreshes the image in response to a user-initiated change. The URL for a Capy is a template with the `capy.id`. But storing the full URL - as well as other fields in the Capy object due to their diverse population - also leads to users paying for excess storage and increased gas fees. +To implement this, the Pet's game API service refreshes the image in response to a user-initiated change. The URL for a Pet is a template with the `pet.id`. But storing the full URL - as well as other fields in the Pet object due to their diverse population - also leads to users paying for excess storage and increased gas fees. The following example demonstrates how to implement dynamic image generation: ```move -module capy::capy { - /// A Capy - very diverse object with different combination - /// of genes. Created dynamically + for images a dynamic SVG - /// generation is used. - struct Capy has key, store { - id: UID, - genes: vector - } +module pet::pet { + /// A Pet - very diverse object with different combination + /// of genes. Created dynamically. For images, a dynamic SVG + /// generation is used. + public struct Pet has key, store { + id: UID, + genes: vector + } } ``` @@ -223,13 +221,13 @@ This is the simplest scenario - an object represents everything itself. It is ve ```move module iota::devnet_nft { - /// A Collectible with a static data. URL, name, description are - /// set only once on a mint event - struct DevNetNFT has key, store { - id: UID, - name: String, - description: String, - url: Url, - } + /// A Collectible with static data. + /// URL, name and description are set only once during minting. + public struct DevNetNFT has key, store { + id: UID, + name: String, + description: String, + url: Url, + } } ``` diff --git a/docs/content/developer/standards/kiosk-apps.mdx b/docs/content/developer/standards/kiosk-apps.mdx index 2580f81172d..ce5d149ec86 100644 --- a/docs/content/developer/standards/kiosk-apps.mdx +++ b/docs/content/developer/standards/kiosk-apps.mdx @@ -12,24 +12,21 @@ There are two types of apps: ## Basic apps -Basic Kiosk apps do not require Kiosk Apps API to function. They usually serve the purpose of adding custom metadata to a kiosk or wrapping/working with existing objects such as `Kiosk` or `KioskOwnerCap`. An example of an app that does not require the API is the Personal Kiosk app. +Basic Kiosk apps do not require the Kiosk Apps API to function. They usually serve the purpose of adding custom metadata to a kiosk or wrapping/working with existing objects such as `Kiosk` or `KioskOwnerCap`. An example of an app that does not require the API is the Personal Kiosk app. -### UID access via the uid_mut +### UID access via `uid_mut` Kiosk has an `id: UID` field like all objects on IOTA, which allows this object to be uniquely identified and carry custom dynamic fields and dynamic object fields. The Kiosk itself is built around dynamic fields and features like place and list are built around dynamic object fields. -### The uid_mut_as_owner function +### The `uid_mut_as_owner` function -Kiosk can carry additional dynamic fields and dynamic object fields. The `uid_mut_as_owner` function allows the Kiosk owner to mutably access the UID of the Kiosk object and use it to add or remove custom fields. +A Kiosk can carry additional dynamic fields and dynamic object fields. The `uid_mut_as_owner` function allows the Kiosk owner to mutably access the `UID` of the Kiosk object and use it to add or remove custom fields. -Function signature: +Its function signature is: `kiosk::uid_mut_as_owner(self: &mut Kiosk, cap: &KioskOwnerCap): &mut UID` +### The public UID getter -`kiosk::uid_mut_as_owner(self: &mut Kiosk, cap: &KioskOwnerCap): &mut UID` - -### The public uid getter - -Anyone can read the `uid` of kiosks. This allows third party modules to read the fields of the kiosk if they're allowed to do so. Therefore enabling the object capability and other patterns. +Anyone can read the `UID` of kiosks. This allows third party modules to read the fields of the kiosk if they're allowed to do so. Therefore enabling the object capability and other patterns. ### Basic app ideas @@ -38,20 +35,19 @@ You can attach custom dynamic fields to your kiosks that anyone can then read (b ```move module examples::kiosk_name_ext { use std::string::String; - use std::option::{Self, Option}; use iota::dynamic_field as df; use iota::kiosk::{Self, Kiosk, KioskOwnerCap}; - /// The dynamic field key for the Kiosk Name Extension - struct KioskName has copy, store, drop {} + /// The dynamic field key for the Kiosk Name extension. + public struct KioskName has copy, store, drop {} - /// Add a name to the Kiosk (in this implementation can be called only once) + /// Add a name to the Kiosk (in this implementation it can be called only once). public fun add(self: &mut Kiosk, cap: &KioskOwnerCap, name: String) { let uid_mut = kiosk::uid_mut_as_owner(self, cap); df::add(uid_mut, KioskName {}, name) } - /// Try read the name of the Kiosk - if set - return Some(String), if not - None + /// Return `some(String)` as the name of the Kiosk if it's set, otherwise `none`. public fun name(self: &Kiosk): Option { if (df::exists_(kiosk::uid(self), KioskName {})) { option::some(*df::borrow(kiosk::uid(self), KioskName {})) @@ -101,15 +97,16 @@ The signature of the `kiosk_extension::add` function requires the app witness, m ```move module examples::letterbox_ext { - // ... dependencies + use iota::kiosk_extension; + use iota::kiosk::{Kiosk, KioskOwnerCap}; /// The expected set of permissions for extension. It requires `place`. const PERMISSIONS: u128 = 1; /// The Witness struct used to identify and authorize the extension. - struct Extension has drop {} + public struct Extension has drop {} - /// Install the Mallbox extension into the Kiosk. + /// Install the Letterbox extension into the Kiosk. public fun add(kiosk: &mut Kiosk, cap: &KioskOwnerCap, ctx: &mut TxContext) { kiosk_extension::add(Extension {}, kiosk, cap, PERMISSIONS, ctx) } @@ -143,15 +140,16 @@ It's considered good practice to define a constant containing permissions of the ```move module examples::letterbox_ext { - // ... dependencies + use iota::kiosk_extension; + use iota::kiosk::{Kiosk, KioskOwnerCap}; /// The expected set of permissions for the app. It requires `place`. const PERMISSIONS: u128 = 1; - /// The witness struct used to identify and authorize the app. - struct Extension has drop {} + /// The Witness struct used to identify and authorize the app. + public struct Extension has drop {} - /// Install the Mallbox app into the kiosk and request `place` permission. + /// Install the Letterbox extension into the Kiosk. public fun add(kiosk: &mut Kiosk, cap: &KioskOwnerCap, ctx: &mut TxContext) { kiosk_extension::add(Extension {}, kiosk, cap, PERMISSIONS, ctx) } @@ -164,16 +162,25 @@ If an app requests and is granted permissions (and isn't disabled), it can acces ```move module examples::letterbox_ext { - // ... + use iota::kiosk_extension; + use iota::kiosk::Kiosk; + use iota::transfer_policy::TransferPolicy; + + /// The Witness struct used to identify and authorize the app. + public struct Extension has drop {} + /// Our example object we want to place in the kiosk. + public struct Letter has key, store { + id: UID, + } /// Emitted when trying to place an item without permissions. const ENotEnoughPermissions: u64 = 1; /// Place a letter into the kiosk without the `KioskOwnerCap`. - public fun place(kiosk: &mut Kiosk, letter: Letter, policy: &TransferPolicy) { - assert!(kiosk_extension::can_place(kiosk), ENotEnoughPermissions) + public fun place(kiosk: &mut Kiosk, letter: Letter, policy: &TransferPolicy) { + assert!(kiosk_extension::can_place(kiosk), ENotEnoughPermissions); - kiosk_extension::place(Extension {}, kiosk, letter, policy) + kiosk_extension::place(Extension {}, kiosk, letter, policy); } } ``` @@ -211,15 +218,15 @@ Use the `disable(kiosk: &mut Kiosk, cap: &KioskOwnerCap)` function to disab **Example PTB** -```move -let txb = new TransactionBuilder(); -let kioskArg = tx.object(''); -let capArg = tx.object(''); +```javascript +let txb = new TransactionBlock(); +let kioskArg = txb.object(''); +let capArg = txb.object(''); txb.moveCall({ target: '0x2::kiosk_extension::disable', arguments: [ kioskArg, capArg ], - typeArguments: '::letterbox_ext::Extension' + typeArguments: [ '::letterbox_ext::Extension' ] }); ``` @@ -231,14 +238,14 @@ The call fails if the storage is not empty. **Example PTB** -```move -let txb = new TransactionBuilder(); -let kioskArg = tx.object(''); -let capArg = tx.object(''); +```javascript +let txb = new TransactionBlock(); +let kioskArg = txb.object(''); +let capArg = txb.object(''); txb.moveCall({ target: '0x2::kiosk_extension::remove', arguments: [ kioskArg, capArg ], - typeArguments: '::letterbox_ext::Extension' + typeArguments: [ '::letterbox_ext::Extension' ] }); ``` diff --git a/docs/content/developer/standards/kiosk.mdx b/docs/content/developer/standards/kiosk.mdx index bb79df2e85b..b38601d6f57 100644 --- a/docs/content/developer/standards/kiosk.mdx +++ b/docs/content/developer/standards/kiosk.mdx @@ -19,7 +19,7 @@ See the [Kiosk SDK documentation](../../references/ts-sdk/kiosk/index.mdx) for e ## IOTA Kiosk owners -Anyone can create a IOTA Kiosk. Ownership of a kiosk is determined by the owner of the `KioskOwnerCap`, a special object that grants full access to a single kiosk. As the owner, you can sell any asset with a type (T) that has a shared `TransferPolicy` available, or you can use a kiosk to store assets even without a shared policy. You can’t sell or transfer any assets from your kiosk that do not have an associated transfer policy available. +Anyone can create an IOTA Kiosk. Ownership of a kiosk is determined by the owner of the `KioskOwnerCap`, a special object that grants full access to a single kiosk. As the owner, you can sell any asset with a type (T) that has a shared `TransferPolicy` available, or you can use a kiosk to store assets even without a shared policy. You can’t sell or transfer any assets from your kiosk that do not have an associated transfer policy available. To sell an item, if there is an existing transfer policy for the type (T), you just add your assets to your kiosk and then list them. You specify an offer amount when you list an item. Anyone can then purchase the item for the amount of IOTA specified in the listing. The associated transfer policy determines what the buyer can do with the purchased asset. @@ -35,7 +35,7 @@ A Kiosk owner can: A buyer is a party that purchases (or - more generally - receives) items from Kiosks, anyone on the network can be a Buyer (and, for example, a Kiosk Owner at the same time). -** Benefits:** +**Benefits:** * Buyers get access to global liquidity and can get the best offer * Buyers can place bids on collections through their Kiosks * Most of the actions performed in Kiosks are free (gas-less) for Buyers @@ -55,7 +55,7 @@ As a marketplace operator, you can implement IOTA Kiosk to watch for offers made As a creator, IOTA Kiosk supports strong enforcement for transfer policies and associated rules to protect assets and enforce asset ownership. IOTA Kiosk gives creators more control over their creations, and puts creators and owners in control of how their works can be used. -Creator is a party that creates and controls the TransferPolicy for a single type. For example, the authors of IOTAFrens are the Creators of the `IOTAFren` type and act as creators in the Kiosk ecosystem. Creators set the policy, but they might also be the first sellers of their assets through a Kiosk. +A creator is a party that creates and controls the TransferPolicy for a single type. Creators set the policy, but they might also be the first sellers of their assets through a Kiosk. **Creators can:** * Set any rules for trades @@ -90,17 +90,17 @@ In practice, these guarantees mean that: IOTA Kiosk is a shared object that can store heterogeneous values, such as different sets of asset collectibles. When you add an asset to your kiosk, it has one of the following states: * PLACED - an item placed in the kiosk using the `kiosk::place` function. The Kiosk Owner can withdraw it and use it directly, borrow it (mutably or immutably), or list an item for sale. -* LOCKED - an item placed in the kiosk using the `kiosk::lock` function. You can’t withdraw a Locked item from a kiosk, but you can borrow it mutably and list it for sale. Any item placed in a kiosk that has an associated Kiosk Lock policy have a LOCKED state. +* LOCKED - an item placed in the kiosk using the `kiosk::lock` function. You can’t withdraw a Locked item from a kiosk, but you can borrow it mutably and list it for sale. Any item placed in a kiosk that has an associated Kiosk Lock policy has a LOCKED state. * LISTED - an item in the kiosk that is listed for sale using the `kiosk::list` or `kiosk::place_and_list` functions. You can’t modify an item while listed, but you can borrow it immutably or delist it, which returns it to its previous state. * LISTED EXCLUSIVELY - an item placed or locked in the kiosk by an extension that calls the `kiosk::list_with_purchase_cap` function. Only the kiosk owner can approve calling the function. The owner can only borrow it immutably. The extension must provide the functionality to delist / unlock the asset, or it might stay locked forever. Given that this action is explicitly performed by the Owner - it is the responsibility of the Owner to choose verified and audited extensions to use. When someone purchases an asset from a kiosk, the asset leaves the kiosk and ownership transfers to the buyer’s address. -## Open a IOTA Kiosk +## Open an IOTA Kiosk -To use a IOTA Kiosk, you must create one and have the `KioskOwnerCap` that matches the `Kiosk` object. You can create a new kiosk using a single transaction by calling the `kiosk::default` function. The function creates and shares a `Kiosk`, and transfers the `KioskOwnerCap` to your address. +To use an IOTA Kiosk, you must create one and have the `KioskOwnerCap` that matches the `Kiosk` object. You can create a new kiosk using a single transaction by calling the `kiosk::default` function. The function creates and shares a `Kiosk`, and transfers the `KioskOwnerCap` to your address. -### Create a IOTA Kiosk using programmable transaction blocks +### Create an IOTA Kiosk using programmable transaction blocks ```javascript let tx = new TransactionBlock(); @@ -109,7 +109,7 @@ tx.moveCall({ }); ``` -### Create a IOTA Kiosk using the IOTA CLI +### Create an IOTA Kiosk using the IOTA CLI ```shell iota client call \ @@ -119,15 +119,17 @@ iota client call \ --gas-budget 1000000000 ``` -### Create a IOTA Kiosk with advanced options +### Create an IOTA Kiosk with advanced options For more advanced use cases, when you want to choose the storage model or perform an action right away, you can use the programmable transaction block (PTB) friendly function kiosk::new. Kiosk is designed to be shared. If you choose a different storage model, such as owned, your kiosk might not function as intended or not be accessible to other users. You can make sure your Kiosk works by testing it on IOTA Testnet. -### Create a IOTA Kiosk with advanced options using programmable transaction blocks +### Create an IOTA Kiosk with advanced options using programmable transaction blocks ```javascript let tx = new TransactionBlock(); +let sender = "0x..."; + let [kiosk, kioskOwnerCap] = tx.moveCall({ target: '0x2::kiosk::new' }); @@ -136,13 +138,22 @@ tx.transferObjects([ kioskOwnerCap ], sender); tx.moveCall({ target: '0x2::transfer::public_share_object', arguments: [ kiosk ], - typeArguments: '0x2::kiosk::Kiosk' + typeArguments: ['0x2::kiosk::Kiosk'] }) ``` -### Create a IOTA Kiosk with advanced options using the IOTA CLI +### Create an IOTA Kiosk with advanced options using a PTB in the IOTA CLI + +```shell +iota client ptb \ + --gas-budget 1000000000 \ + --move-call 0x2::kiosk::new \ + --assign kiosk \ + --move-call 0x2::transfer::public_share_object "<0x2::kiosk::Kiosk>" kiosk.0 \ + --transfer-objects [kiosk.1] +``` -IOTA CLI does not support PTBs and transaction chaining yet. You can use the `kiosk::default` function instead. +Since `0x2::kiosk::new` returns a tuple, `kiosk.0` refers to the `Kiosk` itself and `kiosk.1` to the `KioskOwnerCap`. ## Place items in and take items from your kiosk @@ -208,7 +219,18 @@ let item = tx.moveCall({ ### Take an item from a kiosk using the IOTA CLI -The `kiosk::take` function is built to be PTB friendly and returns the asset. The IOTA CLI does not yet support transaction chaining. +The `kiosk::take` function is built to be PTB friendly and returns the asset. In this IOTA CLI PTB example we transfer the taken item to the given address. + +```shell +iota client ptb \ + --gas-budget 1000000000 \ + --assign kiosk @ \ + --assign kiosk_owner_cap @ \ + --assign item @ \ + --move-call 0x2::kiosk::take "" kiosk kiosk_owner_cap item \ + --assign taken_item \ + --transfer-objects [taken_item] +``` ## Lock items in a kiosk @@ -264,12 +286,12 @@ IOTA Kiosk provides basic trading functionality. As a kiosk owner, you can list * `kiosk::delist` - remove an existing listing * `kiosk::purchase` - purchase an asset listed for sale -Anyone on the network can purchase an item listed from a IOTA Kiosk. To learn more about the purchase flow, see the [Purchase section](#purchase). To learn more about asset states and what can be done with a listed item, see the [Asset States](#asset-states) section. +Anyone on the network can purchase an item listed from an IOTA Kiosk. To learn more about the purchase flow, see the [Purchase section](#purchase). To learn more about asset states and what can be done with a listed item, see the [Asset States](#asset-states) section. ### List an item from a kiosk -As a kiosk owner, you can use the `kiosk::list` function to list any asset you added to your kiosk. Include the item to sell and the list price as arguments. All listings on IOTA are in IOTA tokens. -When you list an item, IOTA emits a `kiosk::ItemListed` event that contains the Kiosk ID, Item ID, type of the Item, and the list price. +As a kiosk owner, you can use the `kiosk::list` function to list any asset you added to your kiosk by including the item to sell and the list price as arguments. All listings on IOTA are in IOTA coins. +When you list an item, IOTA emits a `kiosk::ItemListed` event that contains the Kiosk ID, Item ID, the type of the Item, and the list price. ### List an item using programmable transaction blocks @@ -309,7 +331,7 @@ When you delist an item, IOTA returns to the kiosk owner the gas fees charged to When you delist an item, IOTA emits a `kiosk::ItemDelisted` event that contains the Kiosk ID, Item ID, and the type of the item. -### Delist an item using the programmable transaction blocks +### Delist an item using programmable transaction blocks ```javascript let tx = new TransactionBlock(); @@ -339,12 +361,35 @@ iota client call \ ## Purchase an item from a kiosk {#purchase} -Anyone that has an address on the IOTA network can purchase an item listed from a IOTA Kiosk. To purchase an item, you can use the `kiosk::purchase` function. Specify the item to purchase and pay the list price set by the Kiosk Owner. +Anyone that has an address on the IOTA network can purchase an item listed from an IOTA Kiosk. To purchase an item, you can use the `kiosk::purchase` function. Specify the item to purchase and pay the list price set by the Kiosk Owner. You can discover the items listed on the network with the `kiosk::ItemListed` event. When you use the `kiosk::purchase` function, it returns the purchased asset and the `TransferRequest` for the type associated with the asset. To complete the purchase, you must meet the terms defined in the `TransferPolicy` applied to the asset. +### Purchase an item using the IOTA CLI + +```shell +iota client ptb \ + --gas-budget 1000000000 \ + --assign kiosk @ \ + --assign item @ \ + --assign coin @ \ + --assign transfer_policy @ \ + # We have to purchase the item with a `Coin` whose balance matches the item price exactly. + # In this example we take one of our coins as input and split off a `Coin` with the required balance. + # The remainder will be sent back to us. + --split-coins coin [] \ + --assign coins \ + --move-call 0x2::kiosk::purchase '' kiosk item coins.0 \ + # This is a tuple consisting of the purchased item and a `TransferRequest` that we need to resolve in this PTB. + --assign purchase_result \ + # Resolve the `TransferRequest` by confirming it with `TransferPolicy`. + --move-call 0x2::transfer_policy::confirm_request '' transfer_policy purchase_result.1 \ + # Transfer the purchased item to ourselves. + --transfer-objects [purchase_result.0] +``` + ## Borrow an item from a kiosk As a kiosk owner, you can access an asset placed or locked in a kiosk without taking the asset from the kiosk. You can always borrow the asset immutably. Whether you can mutably borrow an asset depends on the state of the asset. For example, you can’t borrow a listed asset because you can’t modify it while listed. The functions available include: @@ -376,7 +421,7 @@ You can mutably borrow an asset from a kiosk if it is not listed. You can use th ### Mutably borrow an asset using IOTA Move ```move -module examples::mutable_borrow +module examples::mutable_borrow { use iota::object::ID; use iota::kiosk::{Self, Kiosk, KioskOwnerCap}; @@ -388,9 +433,9 @@ module examples::mutable_borrow } ``` -### Mutable borrow with borrow_val +### Mutable borrow with `borrow_val` -You can use the PTB-friendly kiosk::borrow_val function. It allows you to take an asset and place it back in the same transaction. To make sure the asset is placed back into the kiosk, the function "obliges" the caller with a “Hot Potato”. +You can use the PTB-friendly `kiosk::borrow_val` function. It allows you to take an asset and place it back in the same transaction. To make sure the asset is placed back into the kiosk, the function "obliges" the caller with a “Hot Potato”. ### Mutable borrow with `borrow_val` using programmable transaction blocks @@ -408,9 +453,9 @@ let [item, promise] = tx.moveCall({ typeArguments: [ itemType ], }); -// freely mutate or reference the `item` -// any calls are available as long as they take a reference -// `returnValue` must be explicitly called +// Freely mutate or reference the `item`. +// Any calls are available as long as they take a reference. +// `return_val` must be explicitly called. tx.moveCall({ target: '0x2::kiosk::return_val', @@ -421,7 +466,7 @@ tx.moveCall({ ## Withdraw proceeds from a completed sale -When someone purchases an item, IOTA stores the proceeds from the sale in the Kiosk. As the kiosk owner, you can withdraw the proceeds at any time by calling the `kiosk::withdraw` function. The function is simple, but because it is PTB friendly it is not currently supported in the IOTA CLI. +When someone purchases an item, IOTA stores the proceeds from the sale in the Kiosk. As the kiosk owner, you can withdraw the proceeds at any time by calling the `kiosk::withdraw` function. ### Withdraw proceeds using programmable transaction blocks @@ -453,4 +498,14 @@ let coin = tx.moveCall({ ### Withdraw proceeds using the IOTA CLI -Due to the function being PTB friendly, it is not currently supported in the CLI environment. +```shell +iota client ptb \ + --assign kiosk @ \ + --assign kiosk_owner_cap @ \ + --move-call 0x2::kiosk::withdraw kiosk kiosk_owner_cap none \ + --assign withdrawn_coin \ + --transfer-objects [withdrawn_coin] \ + --gas-budget 1000000000 +``` + +Passing `none` withdraws all profits, while passing `some(x)` will attempt to withdraw a `Coin` with a balance of `x`. This amount `x` can be smaller or equal to the contained profits. diff --git a/docs/content/developer/standards/standards.mdx b/docs/content/developer/standards/standards.mdx deleted file mode 100644 index 930a8712921..00000000000 --- a/docs/content/developer/standards/standards.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: IOTA Standards Overview -sidebar_label: Overview ---- - -Standards on the IOTA blockchain are features, frameworks, or apps that you can extend or customize. - - - - - - - - - diff --git a/docs/content/developer/standards/wallet-standard.mdx b/docs/content/developer/standards/wallet-standard.mdx index b73f5712381..2aaaa596738 100644 --- a/docs/content/developer/standards/wallet-standard.mdx +++ b/docs/content/developer/standards/wallet-standard.mdx @@ -13,23 +13,23 @@ You need to create a class that represents your wallet. You can use the `Wallet` `@iota/wallet-standard` to help ensure your class adheres to the standard. ```tsx -import { IOTA_DEVNET_CHAIN, Wallet } from '@iota/wallet-standard'; +import { IOTA_TESTNET_CHAIN, Wallet } from '@iota/wallet-standard'; class YourWallet implements Wallet { - get version() { - // Return the version of the Wallet Standard this implements (in this case, 1.0.0). - return '1.0.0'; - } - get name() { - return 'Wallet Name'; - } - get icon() { - return 'some-icon-data-url'; - } - // Return the IOTA chains that your wallet supports. - get chains() { - return [IOTA_DEVNET_CHAIN]; - } + get version() { + // Return the version of the Wallet Standard this implements (in this case, 1.0.0). + return '1.0.0'; + } + get name() { + return 'Wallet Name'; + } + get icon() { + return 'some-icon-data-url'; + } + // Return the IOTA chains that your wallet supports. + get chains() { + return [IOTA_TESTNET_CHAIN]; + } } ``` @@ -64,7 +64,7 @@ import { } from "@iota/wallet-standard"; class YourWallet implements Wallet { - /* ... existing code from above ... */ + /* ... existing code from above ... */ get features(): ConnectFeature & EventsFeature & IOTAFeatures { return { @@ -76,10 +76,10 @@ class YourWallet implements Wallet { version: "1.0.0", on: this.#on, }, - "iota:signPersonalMessage": { + "iota:signPersonalMessage": { version: "1.0.0", - signPersonalMessage: this.#signPersonalMessage, - }, + signPersonalMessage: this.#signPersonalMessage, + }, "iota:signTransactionBlock": { version: "1.0.0", signTransactionBlock: this.#signTransactionBlock, @@ -99,7 +99,7 @@ class YourWallet implements Wallet { // Your wallet's connect implementation }; - #signPersonalMessage: IOTASignPersonalMessageMethod = () => { + #signPersonalMessage: IOTASignPersonalMessageMethod = () => { // Your wallet's signTransaction implementation }; @@ -126,26 +126,26 @@ required interface. import { ReadonlyWalletAccount } from '@iota/wallet-standard'; class YourWallet implements Wallet { - get accounts() { - // Assuming we already have some internal representation of accounts: - return someWalletAccounts.map( - (walletAccount) => - // Return - new ReadonlyWalletAccount({ - address: walletAccount.iotaAddress, - publicKey: walletAccount.pubkey, - // The IOTA chains that your wallet supports. - chains: [IOTA_DEVNET_CHAIN], - // The features that this account supports. This can be a subset of the wallet's supported features. - // These features must exist on the wallet as well. - features: [ - 'iota:signPersonalMessage', - 'iota:signTransactionBlock', - 'iota:signAndExecuteTransactionBlock', - ], - }), - ); - } + get accounts() { + // Assuming we already have some internal representation of accounts: + return someWalletAccounts.map( + (walletAccount) => + // Return + new ReadonlyWalletAccount({ + address: walletAccount.iotaAddress, + publicKey: walletAccount.pubkey, + // The IOTA chains that your wallet supports. + chains: [IOTA_TESTNET_CHAIN], + // The features that this account supports. This can be a subset of the wallet's supported features. + // These features must exist on the wallet as well. + features: [ + 'iota:signPersonalMessage', + 'iota:signTransactionBlock', + 'iota:signAndExecuteTransactionBlock', + ], + }), + ); + } } ``` diff --git a/docs/content/sidebars/developer.js b/docs/content/sidebars/developer.js index 7a49bcd8d9e..f3f3df0a7f7 100644 --- a/docs/content/sidebars/developer.js +++ b/docs/content/sidebars/developer.js @@ -187,8 +187,13 @@ const developer = [ { type: 'category', label: 'Standards', + link: { + type: 'generated-index', + title:'IOTA Standards Overview', + description: 'Standards on the IOTA blockchain are features, frameworks, or apps that you can extend or customize.', + slug: 'developer/standards', + }, items: [ - 'developer/standards/standards', 'developer/standards/coin', 'developer/standards/coin-manager', {