Icon LinkDefining the Contract Functions

Finally, it's time to compose our contract functions. Begin by copying and pasting the ABI we outlined earlier. It's crucial to ensure that the functions within the contract exactly align with the ABI; otherwise, the compiler will generate an error. Now, substitute the semicolons at the conclusion of each function with curly brackets. Also, modify abi SwayStore to impl SwayStore for Contract, as demonstrated below:

impl SwayStore for Contract {
	#[storage(read, write)]
	fn list_item(price: u64, metadata: str[20]){
		
	}
 
	#[storage(read, write), payable]
	fn buy_item(item_id: u64) {
		
	}
 
	#[storage(read)]
	fn get_item(item_id: u64) -> Item {
		
	}
 
	#[storage(read, write)]
	fn initialize_owner() -> Identity {
		
	}
 
	#[storage(read)]
	fn withdraw_funds(){
		
	}
 
	#[storage(read)]
	fn get_count() -> u64{
 
	}
}

Icon Link1. Listing an item

Our first function enables sellers to list an item for sale. They can specify the item's price and provide a string that references externally-stored data about the item.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon LinkUpdating storage

The initial step involves incrementing the item_counter in storage, which will serve as the item's ID. In Sway, the standard library provides read(), write(), and try_read() methods to access or manipulate contract storage. It's advisable to use try_read() when possible to prevent potential issues arising from accessing uninitialized storage. In this process, we read the current count of listed items, modify it, and then store the updated count back into storage.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon LinkGetting the message sender

Next, we'll retrieve the Identity of the account listing the item.

In Sway, you can define a variable using either let or const. It's essential to declare types when they can't be automatically inferred by the compiler.

To obtain the Identity, utilize the msg_sender function from the standard library. The msg_sender represents the address of the entity (be it a user address or another contract address) initiating the current function call. This function yields a Result, which is an enum type that can either be OK or an error. Employ the Result type when anticipating a value that might result in an error.

enum Result<T, E> {
	Ok(T),
	Err(E),
}

The msg_sender function yields a Result, which can either be an Identity or, in the event of an error, an AuthError.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

To retrieve the inner value, you can employ the unwrap method. It returns the contained value if the Result is OK and triggers a panic if the result indicates an error.

Icon LinkCreating a new item

You can instantiate a new item using the Item struct. Use the item_counter value from storage as the ID, set the price and metadata based on the input parameters, and initialize total_bought to 0.

Since the owner field requires an Identity type, you should utilize the sender value obtained from msg_sender().

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon LinkUpdating a StorageMap

Lastly, add the item to the item_map within storage using the insert method. Utilize the same ID for the key and designate the item as its corresponding value.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon Link2. Buying an item

Next, we aim to allow buyers to purchase listed items. To achieve this, we'll need to:

  1. Accept the desired item ID from the buyer as a function parameter.
  2. Ensure the buyer is paying the correct price with valid coins.
  3. Increase the total_bought count for that item.
  4. Deduct a contract fee from the item's cost and transfer the remaining amount to the seller.
contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon LinkVerifying payment

We can employ the msg_asset_id function from the standard library to obtain the asset ID of the coins being transferred in the transaction.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Next, we'll utilize the require statement to ensure the sent asset is the correct one.

The require statement accepts two arguments: a condition, and a value that's logged when the condition is false. Should the condition evaluate as false, the entire transaction is rolled back, leaving no changes.

In this case, the condition checks if the asset_id matches the BASE_ASSET_ID—the default asset associated with the base blockchain, imported from the standard library.

If there's a mismatch in the asset, or if, for instance, someone attempts to purchase an item using a different coin, we'll trigger the custom error previously defined, passing along the asset_id.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Next, we can employ the msg_amount function from the standard library to retrieve the quantity of coins transmitted by the buyer within the transaction.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

To ensure the sent amount is not less than the item's price, we should retrieve the item details using the item_id parameter.

To obtain a value for a specific key in a storage map, the get method is handy, wherein the key value is passed. For mapping storage access, the try_read() method is utilized. As this method produces a Result type, the unwrap method can be applied to extract the item value.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

In Sway, all variables are immutable by default, whether declared with let or const. To modify the value of any variable, it must be declared mutable using the mut keyword. Since we plan to update the item's total_bought value, it should be defined as mutable.

Additionally, it's essential to ensure that the quantity of coins sent for the item isn't less than the item's price.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon LinkUpdating storage

We can increase the item's total_bought field value and subsequently reinsert it into the item_map. This action will replace the earlier value with the revised item.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon LinkTransferring payment

Lastly, we can process the payment to the seller. It's recommended to transfer assets only after all storage modifications are completed to prevent re-entrancy attacks .

For items reaching a specific price threshold, a fee can be deducted using a conditional if statement. The structure of if statements in Sway mirrors that in JavaScript.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

In the aforementioned if-condition, we assess if the transmitted amount surpasses 100,000,000. For clarity in large numbers like 100000000, we can represent it as 100_000_000. If the foundational asset for this contract is ETH, this equates to 0.1 ETH given that Fuel employs a 9 decimal system.

Should the amount exceed 0.1 ETH, a commission is determined and then deducted from the total.

To facilitate the payment to the item's owner, the transfer function is utilized. This function, sourced from the standard library, requires three parameters: the coin quantity for transfer, the coin's asset ID, and the Identity to which the coins are dispatched.

Icon Link3. Get an item

To get the details for an item, we can create a read-only function that returns the Item struct for a given item ID.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

To return a value in a function, you can use the return keyword, similar to JavaScript. Alternatively, you can omit the semicolon in the last line to return that value. Although both methods are effective, it's generally better to be explicit.

fn my_function(num: u64) -> u64{
	// returning the num variable
	return num;
	
	// this would also work:
	num;
}

Icon Link4. Initialize the owner

To ensure the owner's Identity is set accurately, we should avoid hard-coding it. Instead, we can utilize this function to determine the owner based on a wallet.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

To ensure that this function can only be called once, specifically right after the contract's deployment, it's imperative that the owner's value remains set to None. We can achieve this verification using the is_none method, which assesses if an Option type is None.

It's also important to note the potential risk of front running Icon Link in this context.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

To assign the owner as the message sender, it's necessary to transform the Result type into an Option type.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Finally, we'll return the Identity of the message sender.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon Link5. Withdraw funds

The withdraw_funds function permits the owner to withdraw any accumulated funds from the contract.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

First, we'll ensure that the owner has been initialized to a specific address.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Next, we'll verify that the individual attempting to withdraw the funds is indeed the owner.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Additionally, we can confirm the availability of funds for withdrawal using the this_balance function from the standard library. This function returns the current balance of the contract.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Lastly, we'll transfer the entire balance of the contract to the owner.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Icon Link6. Get the total items

The final function we'll introduce is get_count. This straightforward getter function returns the value of the item_counter variable stored in the contract's storage.

contract;
 
use std::{
    auth::msg_sender,
    call_frames::msg_asset_id,
    constants::BASE_ASSET_ID,
    context::{
        msg_amount,
        this_balance,
    },
    token::transfer,
    hash::Hash
};
 
struct Item {
    id: u64,
    price: u64,
    owner: Identity,
    metadata: str[20],
    total_bought: u64,
}
 
abi SwayStore {
    // a function to list an item for sale
    // takes the price and metadata as args
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]);
 
    // a function to buy an item
    // takes the item id as the arg
    #[storage(read, write), payable]
    fn buy_item(item_id: u64);
 
    // a function to get a certain item
    #[storage(read)]
    fn get_item(item_id: u64) -> Item;
 
    // a function to set the contract owner
    #[storage(read, write)]
    fn initialize_owner() -> Identity;
 
    // a function to withdraw contract funds
    #[storage(read)]
    fn withdraw_funds();
 
    // return the number of items listed
    #[storage(read)]
    fn get_count() -> u64;
}
 
storage {
    // counter for total items listed
    item_counter: u64 = 0,
    // map of item IDs to Items
    item_map: StorageMap<u64, Item> = StorageMap {},
    // owner of the contract
    owner: Option<Identity> = Option::None,
}
 
enum InvalidError {
    IncorrectAssetId: AssetId,
    NotEnoughTokens: u64,
    OnlyOwner: Identity,
}
 
impl SwayStore for Contract {
    #[storage(read, write)]
    fn list_item(price: u64, metadata: str[20]) {
        // increment the item counter
        storage.item_counter.write(storage.item_counter.try_read().unwrap() + 1);
        //  get the message sender
        let sender = msg_sender().unwrap();
        // configure the item
        let new_item: Item = Item {
            id: storage.item_counter.try_read().unwrap(),
            price: price,
            owner: sender,
            metadata: metadata,
            total_bought: 0,
        };
        // save the new item to storage using the counter value
        storage.item_map.insert(storage.item_counter.try_read().unwrap(), new_item);
    }
 
    #[storage(read, write), payable]
    fn buy_item(item_id: u64) {
        // get the asset id for the asset sent
        let asset_id = msg_asset_id();
        // require that the correct asset was sent
        require(asset_id == BASE_ASSET_ID, InvalidError::IncorrectAssetId(asset_id));
 
        // get the amount of coins sent
        let amount = msg_amount();
 
        // get the item to buy
        let mut item = storage.item_map.get(item_id).try_read().unwrap();
 
        // require that the amount is at least the price of the item
        require(amount >= item.price, InvalidError::NotEnoughTokens(amount));
 
        // update the total amount bought
        item.total_bought += 1;
        // update the item in the storage map
        storage.item_map.insert(item_id, item);
 
        // only charge commission if price is more than 0.1 ETH
        if amount > 100_000_000 {
            // keep a 5% commission
            let commission = amount / 20;
            let new_amount = amount - commission;
            // send the payout minus commission to the seller
            transfer(item.owner, asset_id, new_amount);
        } else {
            // send the full payout to the seller
            transfer(item.owner, asset_id, amount);
        }
    }
 
    #[storage(read)]
    fn get_item(item_id: u64) -> Item {
        // returns the item for the given item_id
        storage.item_map.get(item_id).try_read().unwrap()
    }
 
    #[storage(read, write)]
    fn initialize_owner() -> Identity {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has NOT already been initialized
        require(owner.is_none(), "owner already initialized");
        // get the identity of the sender
        let sender = msg_sender().unwrap(); 
        // set the owner to the sender's identity
        storage.owner.write(Option::Some(sender));
        // return the owner
        sender
    }
 
    #[storage(read)]
    fn withdraw_funds() {
        let owner = storage.owner.try_read().unwrap();
        // make sure the owner has been initialized
        require(owner.is_some(), "owner not initialized");
        let sender = msg_sender().unwrap(); 
        // require the sender to be the owner
        require(sender == owner.unwrap(), InvalidError::OnlyOwner(sender));
 
        // get the current balance of this contract for the base asset
        let amount = this_balance(BASE_ASSET_ID);
 
        // require the contract balance to be more than 0
        require(amount > 0, InvalidError::NotEnoughTokens(amount));
        // send the amount to the owner
        transfer(owner.unwrap(), BASE_ASSET_ID, amount);
    }
 
    #[storage(read)]
    fn get_count() -> u64 {
        storage.item_counter.try_read().unwrap()
    }
}

Was this page helpful?