Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(move/examples): implementation of kiosk marketplace extension using kiosk rules #4156

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/examples/move/nft_marketplace/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[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"

119 changes: 119 additions & 0 deletions docs/examples/move/nft_marketplace/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Marketplace Guide

The `nft_marketplace.move` module provides a straightforward implementation of a marketplace extension. To utilize it, follow the steps outlined below.
The `item_for_market.move` contains mocked item data for use within the marketplace.
nonast marked this conversation as resolved.
Show resolved Hide resolved
The `rental_extension.move` is an extension adds functionality to enable item rentals.


## Steps to Use the Marketplace

### 1. Connect to the Network
Connect to the IOTA network (e.g., using a faucet to obtain tokens).

### 2. Install Kiosk
Run the following command to install the Kiosk module:
miker83z marked this conversation as resolved.
Show resolved Hide resolved

```bash
iota client call \
--package 0x2 \
--module kiosk \
--function default
```

### 3. Publish `item_for_market.move`

```bash
iota client publish
```

### 4. Create an Item

Create an item, for instance:

```bash
iota client call \
--package $M_ITEMS_ID \
--module market_items \
--function new_jeans
```

After creation, export the following variables:

miker83z marked this conversation as resolved.
Show resolved Hide resolved
- PACKAGE_ID
- ITEM_ID
- PUBLISHER_ID

### 5. Create a Transfer Policy

Set up a transfer policy for the created item using the command:
miker83z marked this conversation as resolved.
Show resolved Hide resolved
```bash
iota client call \
--package 0x2 \
--module transfer_policy \
--function default \
--gas-budget 10000000 \
--args $PUBLISHER_ID \
--type-args "$ITEM_FOR_MARKET_PACKAGE_ID::market_items::Jeans"
```

### 6. Publish rules and marketplace extension

Publish Kiosk rules modules:
miker83z marked this conversation as resolved.
Show resolved Hide resolved
```bash
iota client publish iota/kiosk/Move.toml
```

Publish the nft_marketplace.move module:
```bash
iota client publish iota/docs/examples/move/nft_marketplace/sources/nft_marketplace.move`
```

### 7. Install the Extension on the Kiosk

Install the marketplace extension on the created kiosk using the command:
miker83z marked this conversation as resolved.
Show resolved Hide resolved
```bash
iota client call \
--package $MARKETPLACE_ID \
--module nft_marketplace \
--function install \
--args $KIOSK_ID $KIOSK_CAP_ID
valeriyr marked this conversation as resolved.
Show resolved Hide resolved
```

### 8. Set a Price for the Item

Set the price for the item:
```bash
iota client call \
--package $MARKETPLACE_ID \
--module nft_marketplace \
--function set_price \
--args $KIOSK_ID $KIOSK_CAP_ID $ITEM_ID 50000 \
--type-args "&ITEM_FOR_MARKET_PACKAGE_ID::market_items::Jeans"

```

### 9.(Optional) Set Royalties

Set royalties for the item:
miker83z marked this conversation as resolved.
Show resolved Hide resolved

```bash
iota client call \
--package $MARKETPLACE \
--module nft_marketplace \
--function setup_royalties \
--args $ITEM_TRANS_POLICY_ID $ITEM_TRANS_POLICY_CAP_ID 5000 2000 \
--type-args "ITEM_FOR_MARKET_PACKAGE_ID::market_items::Jeans"
```

### 10. Buy an Item:

To purchase the item:
miker83z marked this conversation as resolved.
Show resolved Hide resolved
```bash
iota client call \
--package $MARKETPLACE \
--module nft_marketplace \
--function buy_item \
--args $KIOSK_ID $ITEM_TRANS_POLICY_ID &ITEM_ID $COIN_ID \
--type-args "ITEM_FOR_MARKET_PACKAGE_ID::market_items::Jeans"

```
58 changes: 58 additions & 0 deletions docs/examples/move/nft_marketplace/sources/item_for_market.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module nft_marketplace::market_items {
miker83z marked this conversation as resolved.
Show resolved Hide resolved
use iota::package;
/// One Time Witness.
public struct MARKET_ITEMS has drop {}


fun init(otw: MARKET_ITEMS, 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());
}
}
117 changes: 117 additions & 0 deletions docs/examples/move/nft_marketplace/sources/nft_marketplace.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
module nft_marketplace::nft_marketplace {
// iota imports
use iota::{
kiosk::{Kiosk, KioskOwnerCap, purchase},
kiosk_extension,
bag,
transfer_policy::{Self, TransferPolicy, TransferPolicyCap, has_rule},
coin::Coin,
iota::IOTA,
};

// rules imports
// use kiosk::floor_price_rule::Rule as FloorPriceRule;
// use kiosk::personal_kiosk_rule::Rule as PersonalRule;
use kiosk::royalty_rule::Rule as RoyaltyRule;
use kiosk::royalty_rule;
// use kiosk::witness_rule::Rule as WitnessRule;


// === Errors ===
const EExtensionNotInstalled: u64 = 0;
const EObjectNotExist: u64 = 1;

// === Constants ===
const PERMISSIONS: u128 = 11;
miker83z marked this conversation as resolved.
Show resolved Hide resolved

/// Extension Key for Kiosk Marketplace extension.
public struct Marketplace has drop {}

/// Used as a key for the item that has been up for sale that's placed in the Extension's Bag.
public struct Listed has store, copy, drop { id: ID }

public struct ItemPrice<T: key + store> has store {
/// Total amount of time offered for renting in days.
price: u64,
}

/// Enables someone to install the Marketplace extension in their Kiosk.
public fun install(
kiosk: &mut Kiosk,
cap: &KioskOwnerCap,
ctx: &mut TxContext,
) {
kiosk_extension::add(Marketplace {}, kiosk, cap, PERMISSIONS, ctx);
}

/// Remove the extension from the Kiosk. Can only be performed by the owner,
/// The extension storage must be empty for the transaction to succeed.
public fun remove(kiosk: &mut Kiosk, cap: &KioskOwnerCap, _ctx: &mut TxContext) {
kiosk_extension::remove<Marketplace>(kiosk, cap);
}

public fun setup_royalties<T: key + store>(policy: &mut TransferPolicy<T>, cap: &TransferPolicyCap<T>, amount_bp: u16, min_amount: u64, ctx: &mut TxContext) {
miker83z marked this conversation as resolved.
Show resolved Hide resolved
royalty_rule::add<T>(policy, cap, amount_bp, min_amount);
}
Dkwcs marked this conversation as resolved.
Show resolved Hide resolved

/// Buy listed item and pay royalties if needed
miker83z marked this conversation as resolved.
Show resolved Hide resolved
public fun buy_item<T: key + store>(kiosk: &mut Kiosk, policy: &mut TransferPolicy<T>, item_id: object::ID, mut payment: Coin<IOTA>, ctx: &mut TxContext) {
assert!(kiosk_extension::is_installed<Marketplace>(kiosk), EExtensionNotInstalled);
let item_price = take_from_bag<T, Listed>(kiosk, Listed { id: item_id });
let ItemPrice { price } = item_price;
miker83z marked this conversation as resolved.
Show resolved Hide resolved
let payment_amount = payment.split(price, ctx);
miker83z marked this conversation as resolved.
Show resolved Hide resolved
let payment_amount_value = payment_amount.value();
let (item, mut transfer_request) = purchase(kiosk, item_id, payment_amount);
if (policy.has_rule<T, RoyaltyRule>()) {
let royalties_value = royalty_rule::fee_amount(policy, payment_amount_value);
let royalties_coin = payment.split(royalties_value, ctx);
royalty_rule::pay(policy, &mut transfer_request, royalties_coin);
};
transfer_policy::confirm_request(policy, transfer_request);
transfer::public_transfer<T>(item, ctx.sender());
miker83z marked this conversation as resolved.
Show resolved Hide resolved
// Send a leftover back to buyer
transfer::public_transfer(payment, ctx.sender());
}


public fun set_price<T: key + store>(
Dkwcs marked this conversation as resolved.
Show resolved Hide resolved
kiosk: &mut Kiosk,
cap: &KioskOwnerCap,
item: T,
price: u64) {
assert!(kiosk_extension::is_installed<Marketplace>(kiosk), EExtensionNotInstalled);

let id = object::id(&item);
kiosk.place_and_list<T>(cap, item, price);

let item_price = ItemPrice {
price,
};

place_in_bag<T, Listed>(kiosk, Listed { id }, item_price);
}


// === Private Functions ===

fun take_from_bag<T: key + store, Key: store + copy + drop>(
kiosk: &mut Kiosk,
item_key: Key,
) : ItemPrice<T> {
let ext_storage_mut = kiosk_extension::storage_mut(Marketplace {}, kiosk);
assert!(bag::contains(ext_storage_mut, item_key), EObjectNotExist);
miker83z marked this conversation as resolved.
Show resolved Hide resolved
bag::remove<Key, ItemPrice<T>>(
ext_storage_mut,
item_key,
)
}

fun place_in_bag<T: key + store, Key: store + copy + drop>(
kiosk: &mut Kiosk,
item_key: Key,
item_price: ItemPrice<T>,
) {
let ext_storage_mut = kiosk_extension::storage_mut(Marketplace {}, kiosk);
bag::add(ext_storage_mut, item_key, item_price);
}
}
Loading
Loading