0

I'm trying to create multi-edition items. My idea was to make a list of each token and then each token edition with the owner. Ok, i create the item and shows like that on blockchain: https://api.ghostnet.tzkt.io/v1/contracts/KT1Kzi3GnphcEb2v4WvKxBCFpZZxQD26jerK/bigmaps/data/keys

It shows exactly what i thought, but when i goes to TzKT to see the item, only show 1 created on supply. How can i set this to multi-edition?

The token on TzKT: https://ghostnet.tzkt.io/KT1DSTBaxwdw7tm4MrRwNENpNsKv2bSuQRxX/tokens/0/transfers

What i want: https://tzkt.io/KT1QgfSE4C1dX9UqrPAXjUaFQ36F9eB4nNkV/tokens/18/holders

Also the token metadata if helps: https://api.ghostnet.tzkt.io/v1/contracts/KT1DSTBaxwdw7tm4MrRwNENpNsKv2bSuQRxX/bigmaps/token_metadata/keys

My code:

import smartpy as sp

FA2 = sp.io.import_script_from_url("https://legacy.smartpy.io/templates/fa2_lib.py")

class Token(FA2.Admin, FA2.MintNft, FA2.Fa2Nft):
    def __init__(self, admin, **kwargs):
        FA2.Fa2Nft.__init__(self, **kwargs)
        FA2.Admin.__init__(self, admin)

class Marketplace(sp.Contract):
    def __init__(self, token, metadata, admin):
        self.init(
            token=token,
            metadata=metadata,
            admin=admin,
            data = sp.big_map(
                tkey=sp.TNat,
                tvalue=sp.TList(
                    sp.TRecord(
                        holder=sp.TAddress,
                        author=sp.TAddress,
                        amount=sp.TMutez,
                        token_id=sp.TNat,
                        editions=sp.TNat,
                        collectable=sp.TBool))),
            token_id=0)

    @sp.entry_point
    def mint(self, params):
        c = sp.contract(
            sp.TList(sp.TRecord(to_=sp.TAddress, metadata=sp.TMap(sp.TString, sp.TBytes)).layout(("to_", "metadata"))),
            self.data.token,
            entry_point="mint").open_some()

        args = [sp.record(to_=params.to_, metadata=params.metadata)]
        sp.transfer(args, sp.mutez(0), c)

        editions_list = sp.local('editions_list', [])

        sp.for i in sp.range(1, params.editions + 1):
            edition_record = sp.record(
                holder=self.data.admin,
                author=params.to_,
                amount=params.amount,
                token_id=self.data.token_id,
                editions=i,
                collectable=True)
            
            editions_list.value.push(edition_record)

        self.data.data[self.data.token_id] = editions_list.value

        self.data.token_id += 1

@sp.add_test(name = "Non Fungible Token")
def test():
    scenario = sp.test_scenario()
    
    admin = sp.test_account("admin")
    mark = sp.test_account("user1")
    elon = sp.test_account("user2")

    token_contract = Token(
        admin=admin.address,
        metadata=sp.utils.metadata_of_url(
            "ipfs://QmW8jPMdBmFvsSEoLWPPhaozN6jGQFxxkwuMLtVFqEy6Fb"))
    scenario += token_contract

    scenario.h1("MarketPlace")
    marketplace = Marketplace(
        token_contract.address,
        sp.utils.metadata_of_url(
            "ipfs://QmW8jPMdBmFvsSEoLWPPhaozN6jGQFxxkwuMLtVFqEy6Fb"
        ), admin.address)
    scenario += marketplace

    token_contract.set_administrator(marketplace.address).run(sender=admin)

    marketplace.mint(
        to_=admin.address,
        amount=sp.tez(100),
        editions=sp.nat(5),
        metadata={"": sp.pack("ipfs://QmbR82U2yKHbhjve45rqSF9h25s5aFKhMcpZkLRvXRnos6")}
    ).run(sender=admin)

    marketplace.mint(
        to_=mark.address,
        amount=sp.tez(100),
        editions=sp.nat(5),
        metadata={"": sp.pack("ipfs://bafyreibwl5hhjgrat5l7cmjlv6ppwghm6ijygpz2xor2r6incfcxnl7y3e/metadata.json")}
    ).run(sender=admin)
    
    marketplace.mint(to_=mark.address, amount=sp.mutez(5_600_000), editions=5, metadata={"": sp.pack("123423")}).run(sender=admin)

1 Answer 1

1

I think what you need is a Semi-Fungible Token. FA2.Fa2Nft is not the correct class to use in this case. Try using FA2.Fa2Fungible. The reason is that TzKt understands the type of token by looking at the ledger of the token contract and some other things. So, using the FA2.Fa2Fungible class changes the ledger type to hold multiple tokens for a user for a token.

This will also change the way you mint, it has a separate mint params type. So, you will need to modify the other contract also.

The contracts will look like this:

import smartpy as sp

FA2 = sp.io.import_script_from_url("https://legacy.smartpy.io/templates/fa2_lib.py")

class Token(FA2.Admin, FA2.MintFungible, FA2.Fa2Fungible):
    def __init__(self, admin, **kwargs):
        FA2.Fa2Fungible.__init__(self, **kwargs)
        FA2.Admin.__init__(self, admin)

class Marketplace(sp.Contract):
    def __init__(self, token, metadata, admin):
        self.init(
            token=token,
            metadata=metadata,
            admin=admin,
            data = sp.big_map(
                tkey=sp.TNat,
                tvalue=sp.TList(
                    sp.TRecord(
                        holder=sp.TAddress,
                        author=sp.TAddress,
                        amount=sp.TMutez,
                        token_id=sp.TNat,
                        editions=sp.TNat,
                        collectable=sp.TBool))),
            token_id=0)

    @sp.entry_point
    def mint(self, params):
        c = sp.contract(
            sp.TList(
                sp.TRecord(
                    amount = sp.TNat,
                    to_ = sp.TAddress,
                    token = sp.TVariant(
                        existing = sp.TNat,
                        new = sp.TMap(sp.TString, sp.TBytes)
                    ).layout(("existing", "new"))
                ).layout(("to_", ("token", "amount")))
            ),
            self.data.token,
            entry_point="mint").open_some()

        args = [
            sp.record(
                amount = params.editions,
                to_=params.to_,
                token=params.token
            )
        ]
        sp.transfer(args, sp.mutez(0), c)

        editions_list = sp.local('editions_list', [])

        sp.for i in sp.range(1, params.editions + 1):
            edition_record = sp.record(
                holder=self.data.admin,
                author=params.to_,
                amount=params.amount,
                token_id=self.data.token_id,
                editions=i,
                collectable=True)
            
            editions_list.value.push(edition_record)

        self.data.data[self.data.token_id] = editions_list.value

        self.data.token_id += 1

@sp.add_test(name = "Non Fungible Token")
def test():
    scenario = sp.test_scenario()
    
    admin = sp.test_account("admin")
    mark = sp.test_account("user1")
    elon = sp.test_account("user2")

    token_contract = Token(
        admin=admin.address,
        metadata=sp.utils.metadata_of_url(
            "ipfs://QmW8jPMdBmFvsSEoLWPPhaozN6jGQFxxkwuMLtVFqEy6Fb"))
    scenario += token_contract

    scenario.h1("MarketPlace")
    marketplace = Marketplace(
        token_contract.address,
        sp.utils.metadata_of_url(
            "ipfs://QmW8jPMdBmFvsSEoLWPPhaozN6jGQFxxkwuMLtVFqEy6Fb"
        ), admin.address)
    scenario += marketplace

    token_contract.set_administrator(marketplace.address).run(sender=admin)

    marketplace.mint(
        to_=admin.address,
        amount=sp.tez(100),
        editions=sp.nat(5),
        token=sp.variant('new', {"": sp.pack("ipfs://QmbR82U2yKHbhjve45rqSF9h25s5aFKhMcpZkLRvXRnos6")})
    ).run(sender=admin)

    marketplace.mint(
        to_=mark.address,
        amount=sp.tez(100),
        editions=sp.nat(5),
        token=sp.variant('new', {"": sp.pack("ipfs://bafyreibwl5hhjgrat5l7cmjlv6ppwghm6ijygpz2xor2r6incfcxnl7y3e/metadata.json")})
    ).run(sender=admin)
    
    marketplace.mint(
        to_=mark.address,
        amount=sp.mutez(5_600_000),
        editions=5,
        token=sp.variant('new', {"": sp.pack("123423")})
    ).run(sender=admin)

These are the deployed contracts:

5
  • Thank you again, worked very well!!! Just a question, when i'm gonna mint a new item(not a existing one), i need to set the next id right? And you know about any docs i can search for to learn more about Smartpy? Sep 22 at 15:40
  • I already understood. I pass "new" before the object with { "" : bytes }, and "existing" if already exists. Only more question, in sp.range of editions, why set holder to self.data.admin? Sep 22 at 17:20
  • @borderlessdev No need to worry about the next ID in this case. There is already a last_token_id variable that takes care of that, by incrementing for every new mint. You may read the docs here legacy.smartpy.io/docs/guides/FA/FA2/overview. For holders, I'm not sure... I just tried to keep it as similar as possible to your contract. Sep 22 at 18:38
  • Ok, it's working fine. I'm trying to understand somethings, like, when i see token_metadata shows like this: "api.ghostnet.tzkt.io/v1/contracts/…". Yeah, it's correct, each token with corresponding metadata, but when i look to the marketplace metadata shows like this: api.ghostnet.tzkt.io/v1/contracts/…, as you can see, the token ID keeps rising when i try to mint a existing token, so my idea to transfer by ID and Edition fails here. Sep 22 at 19:00
  • Ah, I see. In this case, you'll need to keep both the token ID in the token contract and marketplace contract in sync for the token ID. So, when a new token is minted we increase the token ID by one. But for existing mints, just increase the editions in the existing token ID data. Sep 22 at 19:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.