-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(move/examples): implementation of kiosk marketplace extension us…
…ing kiosk rules (#4156) * feat(move/examples): implementation of kiosk nft marketplace, rental extensions using kiosk rules --------- Co-authored-by: Mirko Zichichi <[email protected]> Co-authored-by: Lucas Tortora <[email protected]>
- Loading branch information
1 parent
34b0e77
commit 2c7de01
Showing
8 changed files
with
1,499 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
[package] | ||
name = "nft_marketplace" | ||
edition = "2024.beta" | ||
|
||
[dependencies] | ||
Iota = { local = "../../../../crates/iota-framework/packages/iota-framework" } | ||
Kiosk = { local = "../../../../kiosk" } | ||
|
||
# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`. | ||
# Revision can be a branch, a tag, and a commit hash. | ||
# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" } | ||
|
||
# For local dependencies use `local = path`. Path is relative to the package root | ||
# Local = { local = "../path/to" } | ||
|
||
# To resolve a version conflict and force a specific version for dependency | ||
# override use `override = true` | ||
# Override = { local = "../conflicting/version", override = true } | ||
|
||
[addresses] | ||
nft_marketplace = "0x0" | ||
|
||
# Named addresses will be accessible in Move as `@name`. They're also exported: | ||
# for example, `std = "0x1"` is exported by the Standard Library. | ||
# alice = "0xA11CE" | ||
|
||
[dev-dependencies] | ||
# The dev-dependencies section allows overriding dependencies for `--test` and | ||
# `--dev` modes. You can introduce test-only dependencies here. | ||
# Local = { local = "../path/to/dev-build" } | ||
|
||
[dev-addresses] | ||
# The dev-addresses section allows overwriting named addresses for the `--test` | ||
# and `--dev` modes. | ||
# alice = "0xB0B" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
# Marketplace Guide | ||
|
||
## Modules | ||
|
||
The [`marketplace_extension.move`](https://github.com/iotaledger/iota/blob/develop/docs/examples/move/nft_marketplace/sources/marketplace_extension.move) | ||
module provides a straightforward implementation of a marketplace extension. To use it, follow the | ||
[steps outlined below](#how-to-use-the-marketplace). | ||
|
||
The [`clothing_store.move`](https://github.com/iotaledger/iota/blob/develop/docs/examples/move/nft_marketplace/sources/clothing_store.move) module contains mocked item data for | ||
use within the marketplace. | ||
|
||
The [`rental_extension.move`](https://github.com/iotaledger/iota/blob/develop/docs/examples/move/nft_marketplace/sources/rental_extension.move) module adds the functionality to enable item rentals. | ||
|
||
## How To Use the Marketplace | ||
|
||
### 1. Install the IOTA CLI and Connect to the Network | ||
|
||
The first thing you'll need to do is [install the IOTA CLI](https://docs.iota.org/developer/getting-started/install-iota), [connect to an IOTA network](https://docs.iota.org/developer/getting-started/connect) and [get some test tokens](https://docs.iota.org/developer/getting-started/get-coins) to pay for [gas](https://docs.iota.org/about-iota/tokenomics/gas-in-iota). | ||
|
||
### 2. Install Kiosk | ||
|
||
You can install the Kiosk by creating a `Kiosk` object, which will also create its `OwnerCap`, and then transferring | ||
them to the caller. | ||
|
||
Run the following command to install the Kiosk module: | ||
|
||
```bash | ||
iota client call \ | ||
--package 0x2 \ | ||
--module kiosk \ | ||
--function default | ||
``` | ||
|
||
After publishing, export the following variables: | ||
|
||
- `KIOSK_ID`: The ID of the installed Kiosk object. | ||
- `KIOSK_CAP_ID`: The ID of the installed Kiosk's owner cap | ||
|
||
### 3. Publish `nft_marketplace` package | ||
|
||
#### 3.1(Optional) Publish Kiosk rules modules if these are not present in the network you are using | ||
|
||
You can publish Kiosk rules modules(package) using the following command: | ||
|
||
```bash | ||
iota client publish $IOTA_REPO_DIR/kiosk | ||
``` | ||
|
||
After publishing, export the following variable: | ||
|
||
- `RULES_PACKAGE_ID`: The ID of the rules package. | ||
|
||
#### 3.2 Publish the `nft_marketplace` Package | ||
|
||
```bash | ||
iota client publish $IOTA_REPO_DIR/docs/examples/move/nft_marketplace | ||
``` | ||
|
||
After publishing, export the following variables: | ||
|
||
- `MARKETPLACE_PACKAGE_ID`: The ID of the whole marketplace package. | ||
- `MARKETPLACE_PUBLISHER_ID`: The ID of the publisher object created during marketplace package publishing." | ||
|
||
### 4. Create a Clothing Store Item | ||
|
||
Next, you should use the functions in the `clothing_store` module to create an item, for instance: | ||
|
||
```bash | ||
iota client call \ | ||
--package $MARKETPLACE_PACKAGE_ID \ | ||
--module clothing_store \ | ||
--function new_jeans | ||
``` | ||
|
||
After creation, export the following variable: | ||
|
||
- `CLOTHING_STORE_ITEM_ID`: The ID of the published item (in this case, Jeans). | ||
|
||
### 5. Create a Transfer Policy | ||
|
||
`TransferPolicy` is a generic-shared object that acts as a central authority enforcing that everyone checks their | ||
purchase is valid against the defined policy before the purchased item is transferred to the buyer. The object is | ||
specified by concrete type: | ||
The `default` function creates a `TransferPolicy` object and a `TransferPolicyCap`, then transfers them to the caller. | ||
|
||
The `TransferPolicyCap` object serves as proof of ownership of the `TransferPolicy` object. | ||
A capability granting the owner permission to `add/remove` rules, `withdraw`, and `destroy_and_withdraw` the `TransferPolicy`. | ||
|
||
You can set up a transfer policy for the created item using the following command: | ||
|
||
```bash | ||
iota client call \ | ||
--package 0x2 \ | ||
--module transfer_policy \ | ||
--function default \ | ||
--gas-budget 10000000 \ | ||
--args $MARKETPLACE_PUBLISHER_ID \ | ||
--type-args "$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans" | ||
``` | ||
|
||
After publishing, export the following variables: | ||
|
||
- `ITEM_TRANS_POLICY`: The ID of the item transfer policy object. | ||
- `ITEM_TRANS_POLICY_CAP`: The ID of the item transfer policy object owner capability" | ||
|
||
### 6. Install the Extension on the Kiosk | ||
|
||
The [`install`](https://github.com/iotaledger/iota/blob/develop/docs/examples/move/nft_marketplace/sources/marketplace_extension.move#L39-L45) function enables the installation of the Marketplace extension in a kiosk. | ||
Under the hood, it invokes `kiosk_extension::add`, which adds an extension to the Kiosk via a [dynamic field](https://docs.iota.org/developer/iota-101/objects/dynamic-fields/). | ||
You can install the marketplace extension on the created kiosk using the following command: | ||
|
||
```bash | ||
iota client call \ | ||
--package $MARKETPLACE_PACKAGE_ID \ | ||
--module marketplace_extension \ | ||
--function install \ | ||
--args $KIOSK_ID $KIOSK_CAP_ID | ||
``` | ||
|
||
### 7. Set a Price for the Item | ||
|
||
You can use the [`set_price`](https://github.com/iotaledger/iota/blob/develop/docs/examples/move/nft_marketplace/sources/marketplace_extension.move#L98-L114) function to set the price for the item: | ||
|
||
```bash | ||
iota client call \ | ||
--package $MARKETPLACE_PACKAGE_ID \ | ||
--module marketplace_extension \ | ||
--function set_price \ | ||
--args $KIOSK_ID $KIOSK_CAP_ID $CLOTHING_STORE_ITEM_ID 50000 \ | ||
--type-args "$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans" | ||
``` | ||
|
||
### 8.(Optional) Set Royalties | ||
|
||
Royalties are a percentage of the item's price or revenue paid to the owner for using or selling their asset. | ||
|
||
You can use the [`set_royalties`](https://github.com/iotaledger/iota/blob/develop/docs/examples/move/nft_marketplace/sources/marketplace_extension.move#L58-L60) function to set royalties for the item: | ||
|
||
```bash | ||
iota client call \ | ||
--package $MARKETPLACE_PACKAGE_ID \ | ||
--module marketplace_extension \ | ||
--function setup_royalties \ | ||
--args $ITEM_TRANS_POLICY $ITEM_TRANS_POLICY_CAP 5000 2000 \ | ||
--type-args "$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans" | ||
``` | ||
|
||
### 9. Buy an Item | ||
|
||
#### 9.1 Get the Item Price | ||
|
||
You can use the following [Programmable Transaction Block](https://docs.iota.org/developer/iota-101/transactions/ptb/programmable-transaction-blocks-overview) to call the | ||
[`get_item_price`](https://github.com/iotaledger/iota/blob/develop/docs/examples/move/nft_marketplace/sources/marketplace_extension.move#L116-L127) | ||
and assign it to an `item_price` variable. In this case, the Jeans item: | ||
|
||
```bash | ||
iota client ptb \ | ||
--move-call $MARKETPLACE_PACKAGE_ID::marketplace_extension::get_item_price "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" @$KIOSK_ID @$CLOTHING_STORE_ITEM_ID --assign item_price \ | ||
``` | ||
|
||
#### 9.2(Optional) Calculate the Royalties For the Item | ||
|
||
You can use the following [move-call](https://docs.iota.org/references/cli/ptb#move-call) to get the royalties for any given product by calling the `kiosk::royalty_rule::fee_amount` function | ||
and assign it to a `royalties_amount` variable. In this case, the Jeans item: | ||
|
||
```bash | ||
--move-call $RULES_PACKAGE_ID::royalty_rule::fee_amount "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" @$ITEM_TRANS_POLICY item_price --assign royalties_amount \ | ||
``` | ||
|
||
#### 9.3 Create a Payment Coin With a Specific Amount (Price + Optional Royalties) | ||
|
||
You can use the following command to [split your gas tokens](https://docs.iota.org/references/cli/ptb#split-destroy-and-merge-coins) to pay for the item's price and royalties: | ||
|
||
```bash | ||
--split-coins gas "[item_price, royalties_amount]" --assign payment_coins \ | ||
--merge-coins payment_coins.0 "[payment_coins.1]" \ | ||
``` | ||
|
||
#### 9.4 Buy an Item Using `payment_coins.0` | ||
|
||
You can use the following [move-call](https://docs.iota.org/references/cli/ptb#move-call) to pay the owner the item's price. | ||
If the royalty rule is enabled, an additional royalty fee, calculated as a percentage of the initial item price, is also | ||
paid. | ||
Once both payments are completed, the item is ready for transfer to the buyer. | ||
|
||
To purchase the item: | ||
|
||
```bash | ||
--move-call $MARKETPLACE_PACKAGE_ID::marketplace_extension::buy_item "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" @$KIOSK_ID @$ITEM_TRANS_POLICY @$CLOTHING_STORE_ITEM_ID payment_coins.0 --assign purchased_item | ||
``` | ||
|
||
#### 9.5 Transfer an Item to the Buyer | ||
|
||
Finally, you can set up the | ||
[public_transfer](https://docs.iota.org/references/framework/iota-framework/transfer#function-public_transfer) to | ||
transfer the purchased item to the buyer: | ||
|
||
```bash | ||
--move-call 0x2::transfer::public_transfer "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" purchased_item @<buyer address> \ | ||
``` | ||
|
||
You can combine all the previous steps into one purchase | ||
[PTB](https://docs.iota.org/developer/iota-101/transactions/ptb/programmable-transaction-blocks-overview) request, | ||
including royalties, which should look like this: | ||
|
||
```bash | ||
iota client ptb \ | ||
--move-call $MARKETPLACE_PACKAGE_ID::marketplace_extension::get_item_price "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" @$KIOSK_ID @$CLOTHING_STORE_ITEM_ID --assign item_price \ | ||
--move-call $RULES_PACKAGE_ID::royalty_rule::fee_amount "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" @$ITEM_TRANS_POLICY item_price --assign royalties_amount \ | ||
--split-coins gas "[item_price, royalties_amount]" --assign payment_coins \ | ||
--merge-coins payment_coins.0 "[payment_coins.1]" \ | ||
--move-call $MARKETPLACE_PACKAGE_ID::marketplace_extension::buy_item "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" @$KIOSK_ID @$ITEM_TRANS_POLICY @$CLOTHING_STORE_ITEM_ID payment_coins.0 --assign purchased_item \ | ||
--move-call 0x2::transfer::public_transfer "<$MARKETPLACE_PACKAGE_ID::clothing_store::Jeans>" purchased_item @<buyer address> | ||
``` |
60 changes: 60 additions & 0 deletions
60
docs/examples/move/nft_marketplace/sources/clothing_store.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/// Module provides `mock` items for using them in marketplace and rental extensions. | ||
#[allow(lint(self_transfer))] | ||
module nft_marketplace::clothing_store { | ||
use iota::package; | ||
/// One Time Witness. | ||
public struct CLOTHING_STORE has drop {} | ||
|
||
|
||
fun init(otw: CLOTHING_STORE, ctx: &mut TxContext) { | ||
package::claim_and_keep(otw, ctx) | ||
} | ||
|
||
public struct TShirt has key, store { | ||
id: UID, | ||
} | ||
|
||
public struct Jacket has key, store { | ||
id: UID, | ||
} | ||
|
||
public struct Shoes has key, store { | ||
id: UID, | ||
} | ||
|
||
public struct Jeans has key, store { | ||
id: UID, | ||
} | ||
|
||
public fun new_tshirt(ctx: &mut TxContext) { | ||
let tshirt = TShirt { | ||
id: object::new(ctx), | ||
}; | ||
|
||
transfer::public_transfer(tshirt, ctx.sender()); | ||
} | ||
|
||
public fun new_jeans(ctx: &mut TxContext) { | ||
let jeans = Jeans { | ||
id: object::new(ctx), | ||
}; | ||
|
||
transfer::public_transfer(jeans, ctx.sender()); | ||
} | ||
|
||
public fun new_shoes(ctx: &mut TxContext) { | ||
let shoes = Shoes { | ||
id: object::new(ctx), | ||
}; | ||
|
||
transfer::public_transfer(shoes, ctx.sender()); | ||
} | ||
|
||
public fun new_jacket(ctx: &mut TxContext) { | ||
let jacket = Jacket { | ||
id: object::new(ctx), | ||
}; | ||
|
||
transfer::public_transfer(jacket, ctx.sender()); | ||
} | ||
} |
Oops, something went wrong.