Deploy NFT dynamically

Using fuel, is it possible to dynamically deploy an NFT where I can provide the name, symbol, and other required parameters for an NFT?

@calldelegation

1 Like

Hi @shivamlync ,

You can dynamically deploy multiple NFTs from a single contract using the Sway Standard 20 (SRC-20) design.

Please refer to the Sway Standards repo:

You would follow the SRC-20 Native Asset standard:

You will most likely want to follow the SRC-3 Mint and Burn standard:

And the common Meta Data standard SRC-9:

A good example contract for a multi-asset contract is found in the Sway Playground (if you select it in the drop down on the top right):

Here is the example again for reference:

// ERC1155 equivalent in Sway.
contract;

use standards::src5::{AccessError, SRC5, State};
use standards::src20::SRC20;
use standards::src3::SRC3;
use std::{
    asset::{
        burn,
        mint_to,
    },
    call_frames::msg_asset_id,
    context::this_balance,
    hash::{
        Hash,
    },
    storage::storage_string::*,
    string::String,
};

storage {
    total_assets: u64 = 0,
    total_supply: StorageMap<AssetId, u64> = StorageMap {},
    name: StorageMap<AssetId, StorageString> = StorageMap {},
    symbol: StorageMap<AssetId, StorageString> = StorageMap {},
    decimals: StorageMap<AssetId, u8> = StorageMap {},
    owner: State = State::Uninitialized,
}

abi MultiAsset {
    #[storage(read, write)]
    fn constructor(owner_: Identity);

    #[storage(read, write)]
    fn set_name(asset: AssetId, name: String);

    #[storage(read, write)]
    fn set_symbol(asset: AssetId, symbol: String);

    #[storage(read, write)]
    fn set_decimals(asset: AssetId, decimals: u8);
}

impl MultiAsset for Contract {
    #[storage(read, write)]
    fn constructor(owner_: Identity) {
        require(
            storage
                .owner
                .read() == State::Uninitialized,
            "owner-initialized",
        );
        storage.owner.write(State::Initialized(owner_));
    }

    #[storage(read, write)]
    fn set_name(asset: AssetId, name: String) {
        require_access_owner();
        storage.name.insert(asset, StorageString {});
        storage.name.get(asset).write_slice(name);
    }

    #[storage(read, write)]
    fn set_symbol(asset: AssetId, symbol: String) {
        require_access_owner();
        storage.symbol.insert(asset, StorageString {});
        storage.symbol.get(asset).write_slice(symbol);
    }

    #[storage(read, write)]
    fn set_decimals(asset: AssetId, decimals: u8) {
        require_access_owner();
        storage.decimals.insert(asset, decimals);
    }
}

#[storage(read)]
fn require_access_owner() {
    require(
        storage
            .owner
            .read() == State::Initialized(msg_sender().unwrap()),
        AccessError::NotOwner,
    );
}

impl SRC20 for Contract {
    #[storage(read)]
    fn total_assets() -> u64 {
        storage.total_assets.read()
    }

    #[storage(read)]
    fn total_supply(asset: AssetId) -> Option<u64> {
        storage.total_supply.get(asset).try_read()
    }

    #[storage(read)]
    fn name(asset: AssetId) -> Option<String> {
        storage.name.get(asset).read_slice()
    }

    #[storage(read)]
    fn symbol(asset: AssetId) -> Option<String> {
        storage.symbol.get(asset).read_slice()
    }

    #[storage(read)]
    fn decimals(asset: AssetId) -> Option<u8> {
        storage.decimals.get(asset).try_read()
    }
}

impl SRC3 for Contract {
    #[storage(read, write)]
    fn mint(recipient: Identity, sub_id: SubId, amount: u64) {
        require_access_owner();
        let asset_id = AssetId::new(ContractId::this(), sub_id);
        let supply = storage.total_supply.get(asset_id).try_read();
        if supply.is_none() {
            storage
                .total_assets
                .write(storage.total_assets.try_read().unwrap_or(0) + 1);
        }
        let current_supply = supply.unwrap_or(0);
        storage
            .total_supply
            .insert(asset_id, current_supply + amount);
        mint_to(recipient, sub_id, amount);
    }

    #[payable]
    #[storage(read, write)]
    fn burn(sub_id: SubId, amount: u64) {
        require_access_owner();
        let asset_id = AssetId::new(ContractId::this(), sub_id);
        require(this_balance(asset_id) >= amount, "not-enough-coins");

        let supply = storage.total_supply.get(asset_id).try_read();
        let current_supply = supply.unwrap_or(0);
        storage
            .total_supply
            .insert(asset_id, current_supply - amount);
        burn(sub_id, amount);
    }
}

You will need to add the metadata standard by additionally.

1 Like

Hey @nick ,
Thanks for the reply.
I wanted to know if I could dynamically set the name, symbol, supply, and tokenUri/metadataUri into the contract.

1 Like

Hey, any suggestions how one can speed up the process of building a sway contract using forc binary built using cargo command with a release flag. @nick

good job
but please how can i do to learn sway language

Hey @shivamlync ,

I missed this comment. You can dynamically set them if you use storage for those metdata.

What version are you on @shivamlync ?

Currently using the testnet

TBH I used a staged docker build where first I used the rust image and download the fuel there and then copied the fuel stuff to the later stage and set it to the env. After that using the forc utility

@shivamlync

Can you provide the results of fuelup show so we can get what eversion of the Fuel/Sway you are on.

staged docker build is this something that is publicly available?

Are you stuck on something right now @shivamlync?

Docker provides this. Multi-Stage Build .
Here I had used a rust image first where I installed git and fuel toolchain and then copied the fuel toolchain to the later stage where I had added it to the path and then used the forc utility