- Hey guys, welcome to this webinar,
where we show you some cool coding
that you can do with solidity.
And this specific webinar, we are going to be looking
at the ERC721 interface
also known as non fungible tokens.
So ERC721 is a standard interface which came from
something called an EIP
an ethereum improvement proposal,
where anyone can suggest an improvement
and one of the improvement was this ERC721 interface
that would define a standard set of methods
that you can call on a ERC721 on a non fungible token
in order to faster interoperability between different
tokens in the ethereum ecosystem.
So first before we are going to the code,
let's take a second to appreciate the difference
between a fungible token and a non-fungible token.
So a fungible token,
you can think of as something that can be interchanged,
right, so if you think of a physical fungible asset,
you can think of a dollar, the US dollar.
So each dollar bill is interchangeable
for any other dollar bill
and it doesn't make a difference,
you still have the same value.
But an example of a non-fungible physical token
would be something like Pokemon cards
or trading cards because each of those has
you know specific set of powers or skills
that are associated to that card, that token,
and that's why it's non-fungible,
it's not interchangeable for each other
like each of them is unique and has a set of unique
sort of powers or abilities or characteristics.
Whereas with fungible tokens, they are interchangeable
and it doesn't matter which specific one you have
because it doesn't make a difference.
So what I talked about were two physical examples,
but in the digital world
you can also find these examples.
So something like bit coin or ether
or any crypto currency is interchangeable,
not any cryptocurrency but ethereum and bit coin have
sort of fungible tokens, right, so it doesn't matter
which bit coin you have
as long as you have one bit coin.
Similarly with ether,
it doesn't matter which specific ether you have,
but as long as your balance says that you have
one ether, the value is the same.
But if you look a non-fungible token in the digital world,
so an example of non-fungible token,
the most popular example is a CryptoKitties.
So CryptoKitties you can think of as a set of unique
trading cards where each of them represents
a digital kitty that has some special characteristics.
It has you know a special look to it,
it might have some super powers,
it might have some other cool stuff
that you can add in your game.
But the point is that they are not interchangeable.
Each of them is unique like a trading card
and has its own unique value
and therefore you can just trade CrptoKitty for another,
because each of them is unique.
So that being said,
one of the fungible interface examples,
most popular one is ERC20.
So if you are doing an ICO for a token
that you want to be,
you wanted to be listed on other exchanges,
you would probably implement the ERC20 token,
because that would allow other operators
or other exchangers to operate against an interface
without having to know the particular details
about your specific token.
So the reasons behind these interfaces are
to faster interoperability and allow these contracts
and tokens to talk to each other
and also with services outside of ethereum.
So one organization that is a part of building these
standards in ethereum community is OpenZeppelin.
So if you just go to openzeppelin.org,
we can see that it's a framework ,it's a open source,
and it's a framework of reusable
and audited smart contract.
So that's what makes them secure.
They are audited by the community
and they provide these open source implementations
for developers like yourself to use in your projects
and avoid common sort of security mistakes
and just use their code as sort of like a starting point.
So one of the things they provide,
so people can get started, it will take us to their get hub
and if we just look at their contracts folder,
you'll see that they have a bunch of other stuff as well
but if it's over move over to the contracts folder
we'll see a bunch of contracts
and if we go into the token folder,
we'll see folders for each of these various formats.
So we mentioned ERC20 and ERC721,
827 is another interface, but for this webinar,
we are only gonna be looking at ERC721.
And we can that so if we start from this ERC721 basic,
so this just defines the interface,
so this is an abstract class that defines the functions
that your contract needs to implement
if it's going to be ERC721 compliant.
And you can see there is ERC721 basic,
which consist of these functions.
So where do these functions come from?
They came from the EIP and just at the top
you can see a link to the EIP, which you can copy paste.
Let's go and take a look at the EIP real quick.
And it's the complete proposal explaining
you know what are non-fungible tokens,
why is this interface needed,
and what are some of the use cases for us?
So I encourage you to read through this
to learn more about the proposal,
but for now we are just gonna be looking at this code.
But this interface is basically defined in that proposal
and it defines these functions that
you should be able to execute on your token.
So at the top, we see there is a bunch of events
that basically indicates transfers of ownership,
so if you have an ERC721 token
that you wanna give to another person
then there would be events
related to that particular action
and we also have these approval events.
An approvals are basically,
you are authorizing someone else to
you know act on your behalf.
And we will talk about why you might wanna do that
but that is the basic idea behind these approval events,
where you're giving someone else approval to
you know make transfers or take actions on your behalf.
So some of the functions that we have,
just quickly to query,
what is the balance of a particular account,
who is the owner of a particular token,
so each token has an ID
that you can use to query and get information about
who the owners and other information as well.
And you can also just check
whether a token actually exist or not.
Then we have a bunch of functions around approvals.
So you can see with approve you can specify
which address you wanna approve and a particular
token ID that you want to approve them for.
And getapprove will just return whoever is the currently
approved person for a particular token
so you can check if someone is actually approved.
And approval, so the difference between
approve and approval for all is that
you are giving approval for all of your tokens
instead of a particular one in approve.
So in approve for all, you can give someone
authority to act on all of your tokens
and one use case for that might be if you are creating
a new let's say a board game
where you have a bunch of sort of magic cards
and you want to give someone a deck
of like 20 magic cards and you want to approve them
to be able to use that.
So that's one example
of where you could use approvals.
And you also have three functions
related to transferring and we will take a look
at these implementations in a bit
and what the differences between safe
transfer and non-safe transfers.
But this is just the interface,
this is just the events and functions
that we need to implement.
And so opens up link provides us a
basic implementation called ERC721 basic token.
And as you can see it extends
the ERC721 basic interface we were just looking at
and it also use some other libraries to do safe math
and to provide some utility functions on addresses.
But so we're not go into too much details
in this particular implementation,
but we are just gonna take a look at what sort
of information is being tracked in this basic token.
So we are keeping track of
who were the token owners are,
so it's a mapping from a token ID to an address, right,
so we can quickly query,
who the owner is of a particular token.
We are keeping track of token ID to approvals,
right to know whether someone is authorized
to act on a particular token.
And we are also keeping track of
how many tokens a particular address owns.
And we also have another mapping
to keep track of more approvals,
you can have multiple approvals,
so this mapping helps us keep track of that.
And we have some modifiers that we'll see
how to use in a second,
but this is just implementing the interface that we saw
and it provides us some basic functionality
that we can extend to create our game.
So let's see what that would look like, right,
well let's try to create our own game,
which is creating an ERC721 token, a non-fungible token
and we will call it cryptomonsters.
And let's see how we can use this code provided by
OpenZeppelin to implement our own
CryptoKitties like game.
So I am just gonna go over to remix
and let's see what we have here, so I have some of the code
that I have loaded in from here,
so I basically got these source code
directly from this github.
So these files over here I just downloaded
and I opened them in remix
and I just the main one that we're interested in
is this basic token,
but the rest are dependencies for this contract.
But we are gonna write a new one
called cryptomonsters,
so let's open up our new contract.
We are gonna declare the solidity version,
so let's go with .422 and we can see that in the URL like
it supports up to point .4.23,
but 22 is good enough for our use cases.
So we are gonna call this cryptomonsters
and we are gonna say is we are gonna extend the
functionality of the ERC721 basic token.
And it's gonna complain that it doesn't know
which token you are talking about.
So that just mean you have to import
this specific contract into your,
into this cryptomonsters contract.
So all we did is declared our own token
called cryptomonsters that extends
and basically takes the functionality provided
by this ERC721 basic token
and allow us to just focus on hour-specific games logic,
instead of worrying about the details of implementing
the interface and the intricacies of that.
So let me just bring this up here,
so the first thing we'll do is start by defining
a struct for our monster.
And we just call this struct monster
and some of the characteristics
that we are gonna have for our monster
and this can be anything you like depending
on the rules of your game,
but let's give our monster a name.
We will also give them a level or a rank
and they also have some attack power
as well as some defense power.
And we are gonna use this powers
to make our monsters battle
and try to level up our monsters
and get them to be really strong.
So we have defined our monsters struct
and now we are just gonna keep track
of all the monsters that exist in the world.
So we are just gonna define an array,
which is gonna keep track of our monsters.
You could also use something like a mapping,
but we are just gonna use a simple array
and use the index of the array
as the ID of that particular monster.
So the first monster is gonna have index zero.
And another thing will do is keep track of the owner,
of the owner's address
and we'll record this in our constructor
and we'll do this so that only the owner
can create a crypto monsters.
So okay we have our constructor
clear public to make the warning go away.
Here, what's this other warning,
it says, it's duplicated, use constructor, okay.
So this is telling me to use the constructor keyword,
we won't get into that right now.
I am gonna leave that as function
and let you dig deeper into that warning.
But what we are gonna define is the function
to actually create our monsters, right.
So let's define a create monster function
and this gonna be public
and we only want the owner to be calling this
because as the owner of the game,
you should be or you can create any rules you want,
but for this particular game only
the owner will be able to create monsters.
So if we just get the new monster's ID, right,
and which is gonna be the length of the monsters Array.
So when it's empty, the length is gonna be zero,
so the ID of the first monster is gonna be zero
and then we are just gonna say monsters.push,
this new monster and we are gonna give it
some default values to begin with.
So we need to give it a name,
we need to give it a level,
so we'll start with level one
and you can pick any values you want
for the attack power or defense power,
but we are just gonna stick with hundred
as the initial defaults for our monster.
So let's also add a parameter where you can specify
the name of the monster you wanna create.
And we are also going to specify
which address we are assigning this monster to.
So this function will basically create a new monster
and give ownership of that monster that NFT that
ERC721 token to the address specified by this variable.
And so right now, we haven't written any codes so far
in these three lines that actually associates
the monster to this address.
So what we are gonna do for that
is actually use one of the functions provided by
this ERC721 basic token called mint.
And what mint does is actually create
a new token for us and assigns it
to the address that we specify.
So let's go and take a look at this function
in here little quick.
Mint internal function to mint a new token,
reverts if the token ID already exists,
and the two is the address that will own the token
and second parameter is the ID of the token.
And you can see that this logic is already provided,
is already written for us,
so we don't need to worry about building
that logic for ourselves.
Another thing to notice over here is that
when we are emitting
or when we are firing this transfer event
we actually added this emit keyword.
And this is to make it clear that
we are actually firing an event
because if we take out this keyword
then it kind of looks like we are calling a function.
It's indistinguishable from this add token to function.
So in recent version, we added this a emit keyword
to make it more obvious that you are actually firing
an event and not calling a function.
By we are just using that same emit function over here
to say hey emit a new token
and assign it to this to address
and the token will have this particular ID.
And so that's all we need for creating monsters.
So we have created a function
where only the owner of the game,
the owner of cryptomonsters can issue people
new monsters and give that monster a name,
okay cool.
Now we want to provide a way
for the actual game mechanics, right.
So our actual logic of the game,
we want our monsters to battle.
So let's write a battle function
and see what that would look like.
And we are gonna make it public.
And we are going to give it the monster ID
to use as well as the target ID of the other monster
that is engaged in the battle
Right, and we are gonna use one of the modifiers
from our parent classes here called the only owner of
and we are gonna pass it this monster ID.
So only the owner,
only the person who is the owner of this monster ID
can make it battle,
can call this battle function on that particular token.
And so what we are gonna do,
I am just gonna minimize this terminal real quick,
so what we are gonna do is get a reference
to our monsters that are specified
and we are gonna say monster1
equals monsters specified by this ID.
Right and that's just the index in the array.
And the second monster,
monster2 is the target monster.
Okay, cool, so we have got reference
to our two monsters
and now we need to write the logic
that will actually make them battle
and effect their characteristics
like their level, their attack power,
and the defense power.
You can write whatever logic you want, right,
that's up to your creativity, but for this example,
we are just gonna keep it really simple
and we are gonna say that if we attack power
of the first monster is greater than or equal to
the other monster defense power.
Then monster1 will be the winner,
because it has the stronger attack power
than the other monster's defense power.
So in this case what we are gonna say is
monster1.level and just incremented by one
so you level up because you would win this battle
and monster1s attack power would also go up
as a result of this victory.
And let's say it goes up by ten.
But if this condition isn't met, right,
then monster2 will win, right,
so monster2.level will increment by 1.
And its defense power will increase by 10 as well.
So this is, these are the changes
that happens if monster1 wins and
these are the changes that happens if monster2 wins,
but basically with every battle that you win,
you level up and it effects either
your attack power or your defense power.
Sweet, this is all the logic you really need
for a really simple ERC721 token
and we have written a very simplified version
of crypto kitties called crypto monsters where you can
create monsters as the owner of the game
and you can get them to battle each other.
So let's see what this would look like if we run it.
We gonna bring up our JavaScript's VM test block chain
and we are just going to deploy
our crypto monsters' contract
and there is no parameters initially
so just create, right,
and you will see that all the functions that are provided
there is a lot more than we declared in our contract,
right, and that's because we are inheriting from
this ERC721 basic token all this other functionality.
But, okay let's create a monster
and so the owner is the account that deployed
and if create a monster called Pikapika
and we assign it to ourselves,
all right, so this is the address
and we are gonna say create monster,
let's bring up our terminal to see whether our cause
are going through this with the create monster.
And it's succeeded, right,
so now if I check, you know if I just paste
that same address into
here into the balance of function,
we get one, right, because I have one token
that is assigned to me
and we can also check the exist function
so it's the first one,
so the token ID will be zero
And we can check that it exist and the owner
also matches up with our address over here awesome.
Cools so now let's create a second monster as well
and we'll call it Dumbledore,
so create monster and now I wanna check my balance,
I should have two because I have
two monsters assigned to me
and okay so that now I have two monsters,
I can actually make them battle.
The monsters don't have to be owned by
two different addresses
So let's see what it looks like
if we make these monsters battle.
So I am gonna take the monster ID of zero and one
Right, and let's just check our monster statistics
before we go forward.
So for Pikapika and for Dumbledore
the value should be default
because they haven't been in battle
so they are both level one.
But let's bring up,
let's keep a track of Pikapika's power.
So when I click battle,
Pikapika is gonna battle Dumbledore
and I click battle,
so make sure the transaction went through, right,
it was successful and so now,
so my balance will stay the same, right,
because still have the same number of tokens,
but if I check the stats over here of Pikapika,
so if I refresh this I click monsters again,
I see that Pikapika actually won,
so it leveled up and its attack power went up by ten.
So similarly you can battle your different monsters
and level up or and gain more power
and so each of these monsters
through each of their battles,
they will have different levels,
different attack powers, different defense power,
which will make them unique
and you could also attach something
like a cool image to each of these tokens.
But we basically created a simple game
using ERC721 interface
using an implementation provided by open zeppelin.
And if you want to transfer as well,
so let's try transferring one of our tokens.
So Pikapika has the most attack power in the game,
so you know someone is interested
and you know trading something for Pikapika,
so I am gonna so let's say they gave me
like five bucks in person and now I can transfer them
ownership of Pikapika,
so let's just look at our say if transfer from,
I just wanna get look at the parameters that it has
now it's over,
so from to and token ID, right.
So the from is gonna be our author, right
or it's gonna be us, who is the owner, right
and let's say we wanna transfer to the second account
and pass that and zero for the token ID of Pikapika, right
and so right now the owner of Pikapika,
which is token ID zero is the CA35 account,
but after I go through this transfer function
and it succeeds or execution failed.
Let's see why execution had failed,
oh okay, because I didn't copy the to address properly
so let's try this again.
Oh, because I am actually not
Oh I did from the wrong account,
so let me try that again,
I'll try to fix this arguments as well.
So the first argument should be CA35, right
and then second argument should be 147 something,
so this argument is wrong,
so just delete that and I just try this again,
copy and paste 160C, okay so that looks correct
and transferring zero for Pikapika
and we have to do it from the owner account, right
so now if I try to click say if transfer,
I'll do this again, all right
and okay this time execution succeeded,
so now if I check the ownership of
the zero token ID again, this should be different
because I just transferred it over to the second account.
So I click it and I get back the new owner of this token.
So yeah, that's just a very simple example
of how to build a non-fungible token
and how to build a game using some
you know cool game mechanics
that you can dream up of.
So I highly encourage you to check out
this ERC721 basic token further
and read through the EIP
to understand the use cases better
and yeah also check out the code
for crypto kitties a lot of it
but not all of it is available
on places like etherscan,
so you definitely check that out
and learn from that as well
and see how you know they are approaching
their game mechanics.
But yeah, thanks for watching
this simple ERC721 example
and we hope to see you next time.
Không có nhận xét nào:
Đăng nhận xét