The very last thing we need to do in order to complete NFT Marketplace Project is to enable the buying 1
of the NFT assets. 2
Now, what's going to happen is we're going to need to take the amount that a NFT costs from the buyer's 3
wallet and transfer it to the original owner of the NFT and then finally transfer the ownership over 4
to the buyer. 5
And in order to do this, we're actually going to tie it back to the token canister that we created 6
earlier on so that we can start charging these DANG tokens and actually using them for something practical, 7
like buying and selling NFTs. 8
At the moment, all that happened when I click on the buy button is the 'Buy was triggered' is console 9
logged. 10
And by the way, if you don't have a NFT that is available to buy here, make sure that you use the 11
creating NFT for testing section 12
to simply mint an NFT that can be bought by this user on the frontend. 13
If we take a look at our item.jsx, we've got this function that we set out called handleBuy and at 14
the moment all it does is console.log 15
the 'Buy was triggered' text which is not very useful, but we're going to update it so that we can 16
actually make it do something. 17
Now, the first thing we need to do is to get our token canister up and running. 18
And hopefully you would have completed the module where we built the token canister and the frontend 19
for managing tokens. 20
But because I know that a lot of you might have not deployed that and also the code might be a little 21
bit different or a little bit messy, 22
I want us to be all working with the same token canister. 23
So in this current Udemy lesson, you should be able to download the token local compressed zip file. 24
And once you've uncompressed it, then you can go ahead and drag that folder into where you've got your 25
ic-projects. 26
Now once it's in here, then I want you to go ahead and open up a new window in VSCode and open up 27
that folder. 28
So mine's in my ic-projects and it's called TOKEN-LOCAL. 29
So now let's expand it so that we can see what's inside. 30
If you remember, we had the token canister and the token asset, 31
so this is the frontend and this is the backend. And it's pretty simple in that it keeps track of everyone's 32
balances. 33
It allows you to query the balance of somebody, 34
it allows you to pay out from the faucet and also transfer to someone else. 35
This is going to be the function that we're going to be tapping into. 36
So you can imagine this is a live deployed project, 37
then this token canister will be available on the internet computer blockchain, and our OpenD will 38
also be on the blockchain. 39
And it's really important for us to learn how to get our canisters to talk to other canisters. Before 40
we go ahead and deploy this token project and use it, 41
I want you to take a look inside the read me and take a look at the deploy section. 42
So the first thing we want to do is to find out our principal ID. 43
So open up a new terminal inside TOKEN-LOCAL so that you're actually working with it here and then paste 44
in that command line command so that you've got hold of your current command line user principal. 45
And then I want you to go into the main.mo which is inside the token folder. 46
And I want you to replace the part where it says owner with your own principal ID, 47
so just simply copy it from the terminal and then paste it in here, making sure that it's inside a 48
set of double quotes, like I've got right here. 49
Now, next, we're going to go ahead and deploy this project with dfx deploy, 50
so making sure that your dfx start is hopefully running on your OpenD project. 51
Now once that's complete, we're going to continue in the next step of the Read Me, which is to go 52
ahead and start the frontend. 53
So I'm going to go ahead and run NPM start. 54
And notice that because we've got our OpenD project running on localhost 8080, automatically npm start 55
will assign this frontend for TOKEN-LOCAL to a different address so it becomes localhost 8081. 56
So you can either copy that or you can simply type it in to the command line. 57
And if you head over there, you'll see our familiar frontend for our token project. Now as we did before, 58
the first thing we need to do with our token project is to load up the token canister with some DANGs. 59
So we're going to check and set the canister ID to a local variable. 60
Go ahead and open up another terminal screen and paste that in there. 61
So now that we've got the principal ID of the token canister saved inside canister public key, we're 62
going to transfer half a billion tokens over by calling the token transfer function. 63
And once that's done, then we can claim the tokens from the faucet by going over here and clicking 64
on Gimme, Gimme. 65
And once that comes back as success, then our anonymous user who's operating the frontend should now 66
have 10,000 67
DANG. 68
And we're going to be using these 10,000 DANG in order to purchase NFTs on our OpenD website. 69
Now I've simplified the project a lot because none of these things are running through internet identity. 70
We don't have to do the login flow, 71
we don't have to deploy our canisters, and we're saving ourselves a lot of money because every time 72
we mint an NFT, that's going to cost us something like $5. 73
So we're doing a lot of testing for now, but everything that works here will work once we deploy it 74
online. 75
And we're just learning how to talk to other canisters that are live and deployed on the Internet computer. 76
Now that we've given our anonymous user our 10,000 DANG, we're going to call the transfer function. 77
We're going to use the functions in our faucet backend in order to transfer the money from the buyer 78
to the seller. In order to access the types and the functions that is in the token backend, we need 79
to copy the declaration file for our token canister. 80
Now if you are plugging into somebody else's live canister, they should provide you with this so that 81
you're able to work with it. 82
But because we made both of these ourselves, then it's pretty easy for us to simply just copy this 83
folder, 84
so right-click copy, and then head over to OpenD. Inside the declarations folder, 85
go ahead, right-click and paste. 86
And now you should see we've got our NFT canister, our 87
opend canister and are token canister. 88
Don't worry about it being red. 89
That's just related to the git commit and it doesn't affect us at all. 90
If you take a look inside the tokendid. 91
js, 92
you can see that we're creating an idlFactory here that defines which methods we can call on this 93
token canister and what data types it expects in the inputs and outputs. 94
This idlFactory is how we're going to be able to work with that canister in our item.js. Similar 95
to before, where we imported the idlFactory from our programmatically created NFT containers, 96
we're going to import the idlFactory from our token.did. 97
So we're going to type import idlFactory from, and then the location is going to be ../ 98
../../declarations/token. 99
In order for these two to not conflict with each other, 100
we're going to give this an alias. 101
So we're going to say import idlFactory as tokenIdlFactory. 102
This way we can use it and we'll know the difference. Down here in our handleBuy button, 103
we're going to create our token actor. 104
So we're going to use the same agent and local host as we have before, but we're going to use the create 105
actor to create our token canister. 106
So heading down here, we're going to create a new constant called tokenActor. 107
And we're going to set it to equal await 108
Actor. 109
create 110
Actor. 111
And inside here, we first are going to provide the token idlFactory. 112
And then we're going to provide a object with the agent, 113
and then comma, 114
and then the canister ID 115
which is going to be set to the actual ID of our token canister. 116
So let's get hold of the token canister for our token local project. 117
And all we have to do is type in dfx canister id token. 118
And hit enter and we should get back our token id, which we're going to go ahead and put into here. 119
So we're going to use the principal.fromText 120
and pass in this id as a string right here. 121
Now, we can use this token actor and tap into it in the same way that we've been using the OpenD, 122
so for example, calling the canister methods like so. The main thing that we're interested in is 123
transferring the money from the buyer to the seller. 124
So first we have to get hold of the seller's principal ID, 125
which I'm going to call the sellerId. 126
And we have a method of getting hold of the original owner of the NFT already in our main.mo, 127
which is called getOriginalOwner, passing in the ID of the NFT. 128
That's what we're going to be calling here. 129
So we're going to say await opend. and then I'm going to paste in that method and pass in the ID 130
of the NFT which is held inside props.id. 131
Now the next thing we need in order to make the transfer is to know the itemPrice, to know how much 132
money to transfer, 133
right? 134
And similarly in our main.mo, we've got a method called getListedNFTPrice and it takes the 135
ID of the NFT 136
so we can check that as well. 137
So await opend. paste in the method name and then pass in props. 138
id 139
once more. 140
Now finally, we're ready to make the actual transfer. 141
So now we can go ahead and use our await, tap into our token actor and call the method here 142
inside our token canister, which is called transfer. 143
Transfer takes a to, which is the first input and the amount as a natural number. 144
If we go ahead and call our transfer and the to is of course the sellerId because they're the ones 145
who should be benefiting from the sale. 146
And then the itemPrice is going to be the amount. 147
And because this transfer function actually returns a piece of text to let us know what's the result 148
of the transfer, we can actually console.log that as well. 149
Now let's try it out. 150
We know that our frontend user has this many DANGs. 151
And we know that this item costs 2 152
DANG. 153
So if I go ahead and click buy, then buy was triggered and hopefully once we get back success from the 154
result of the transfer, then we can head back over to our faucet, check the balance again and see 155
that 2 dang was subtracted from the balance. 156
So that is working as expected. 157
Now all that's left to do is to simply transfer over the ownership of the NFT 158
once this transfer result is successful. We can add an if statement and check that the result is equal 159
to success. 160
And then inside this if statement, we can transfer the ownership. 161
To do that, we have to head over to our main. 162
mo 163
and create a new public function. 164
So it's going to be public shared 165
(msg) func, and we're going to call this completePurchase. 166
It's going to take three inputs. 167
Firstly, is the id of the NFT that's in question, 168
second is the ownerId, 169
who we''re going to transfer it from, 170
and thirdly is the newOwner 171
Id. 172
And finally, we'll return a piece of text as the result, as usual. Adding the semicolon when needed, 173
we should end up with our function ready to go. 174
Now the first thing we have to pull up is the purchased NFT. 175
So we'll pull it out of our map of Nfts and we'll need to unwrap it from its optional type. 176
So I'm going to call this a purchasedNFT, which is going to have the data type of the NFTActorClass 177
.NFT and it's going to be equal to the switch using the mapOfNFTs 178
.get passing in the id. Now when the case is null, we're going to simply exit this function and 179
return a piece of text saying NFT does not exist. 180
But in the case where it does exist, we're going to unwrap the result and return it as the purchased 181
NFT. 182
Now, in order to get rid of all of these errors, I'm just going to add a return success at the end 183
just so that we can continue working on our function unharassed. 184
The next thing we're going to do is we're going to go ahead and transfer the NFT over to the new owner. 185
So we're going to call the transfer method on this particular NFT that we pulled up. 186
So I'm going to say purchasedNFT. 187
and then if we take a look inside our nft.mo, you can see we've got our transfer ownership method 188
that expects a new owner as an input. 189
So we're going to pass over the newOwner 190
Id as the transfer ownership input. And if the result that comes back from transferResult is success, 191
then we're going to continue with deleting this item from our mapOfListings and also removing it from 192
the previous owner's registered list of owned and NFTs. 193
So firstly, we're going to tap into our mapOfListings and we're going to call the delete function 194
that is available in a HashMaps, and we're going to pass over the id of the NFTto delete. 195
The next thing we're going to get hold of is the list of NFTs that the previous owner owned. 196
So we can delete it from that list. 197
So I'm going to create a variable called ownedNFTs which is going to be a list of principals. 198
So List.List, . 199
And we're going to need to use the switch statement in order fetch it from our mapOfOwners using the 200
get method and passing in the ownerId. 201
In the case where it is 202
null, 203
then we are going to simply return an empty list. 204
So List.nil with the data type of Principal. 205
So this is just going to be an empty list, 206
and then add the parentheses to initialize that. 207
And then in the case where we can unwrap it, we are going to unwrap it. 208
Now the final thing we're going to do is we're going to take this list of NFT Ids that the previous 209
owner owned, and we're going to run a function called filter on it. 210
In the documentation, you can see that delete is pretty simple. 211
It simply deletes the entry with a particular key. 212
It doesn't do anything if the key doesn't exist. 213
And on the list data type, we've got something called filter, which will create a new list with 214
only the elements of the original list for which the given function returns 215
true. 216
So basically we can pass in a list and pass in a function. 217
And if that function returns true as it loops through each of the items in the list, then it's going 218
to add that item into the new list. 219
So I'll show you what it looks like in practice. 220
We're going to say List.filter, 221
that's the method call. 222
The first item that we pass in is the list that we want to filter through, which is our ownedNFTs. 223
Now the second input is going to take a function which loops through each of the NFT Ids, which I'm 224
going to call listItemId. 225
And these ids are going to be principal data types. 226
And for each of these ids, it's going to do some stuff checking to see if it matches the id of this 227
current NFT. 228
And if it doesn't, then we're going to return 229
true 230
which means it'll get added to the new list. 231
But if it does, then we'll return 232
false 233
and the simplest way to write this logic is to say return when the listItemId does not equal to the 234
id of the item. 235
So now we can assign this back into the ownedNFT list. 236
So we can say ownedNFTs := 237
and that means that it will update to this filtered list. 238
So you can imagine that as it loops through all of the list items, for each of the list items they get 239
checked. 240
And if that list item does not equal the ID of the NFT that's being purchased, then we're going to 241
return 242
true. 243
Otherwise, we're going to return false. 244
When true is returned, then that particular NFT id will get added to the new list. 245
And if it returns false, then that particular id will be omitted from the new list. 246
And finally, we're going to add this to the ownership map of the new owner. 247
So we can use our method that we've really got here, which is called addToOwnership map passing in 248
the new owner and the ID. 249
And make sure that you've got that outside of this filter function. 250
Now, finally, once all of that is done, we're going to return success, 251
if it all went successfully. 252
And in the else statement, in case the transfer result was not success, we're simply going to return 253
the transferResult so that we can see what is the problem. 254
And taking a glance up here, I've got an error here that says type of Text does not have expected type 255
async text. 256
So the reason is because this is missing an await because we have to call this asynchronously. 257
And that should get rid of that error. 258
This error is because I've mistyped. It's actually called the newOwnerId rather than the newOwner. 259
And now I think we should be good to go so we can copy the name of this method, 260
go back to our item.jsx and actually perform the transfer. 261
So we're going to say opend. and then paste in the name of our method. 262
And let me just check the order of inputs. 263
It's the id, the ownerID and the newOwnerId. 264
So the id is props.id, the ownerId is this sellerId that we've got here. 265
And the newOwnerId is the current user id. And we're going to call this with an await and we're going 266
to capture the result. 267
Now finally I'm going to console.log this results with purchase... 268
And let's go ahead and try this out. 269
So I'm going to hit save and I'll need to redeploy. 270
So let's go way up to the top of our read me and copy the deploy code here, 271
paste that in there run our deploy. 272
Once everything's been deployed, we're going to need to mint an NFT from the command line in order 273
to buy it. 274
So I'm going to copy all of this code and then paste it in here and hopefully we'll get a success 275
back with the principal ID of the newly minted canister. 276
So I'm going to replace this with that principle, and then we're going to run this command to list 277
the item for sale. 278
Once that comes back as success, 279
I'm going to get hold of my OpenD's canister ID and I'm going to copy that and put it into this fourth 280
step here. 281
And it's going to be the part where it says principal something, something. 282
And I also have to copy this principal of the NFT canister and paste it into here where it says dfx 283
canister call. 284
So after all of that, let's go ahead and hit enter. 285
So now we're ready to test our code. 286
So I'm going to go ahead and click on the buy button. 287
And you can see that we get purchase success. 288
And now if I refresh this Discover page, it should have disappeared from this page. 289
And when I go into My NFTs, I should see that item listed in there. 290
Now, I've made quite a few purchases just testing this, 291
but you should, if you head over to the token frontend, be able to check the before and after token 292
balance of your account. 293
And it should go down by 2 each time when you try to purchase one of these NFTs depending on its cost. 294
Now the final thing we need to address is just some UI issues so that when we purchase our NFT, it disappears 295
from the Discover page. 296
And for the loader to show up when the purchase process is undergoing. 297
So we're going to be reusing some of the things that we had from before. 298
For example, when the handleBuy is triggered, we're going to set the loader hidden property to false 299
to bring up our loader. 300
And then it's going to go through all of this purchasing process. 301
And once it's completed, 302
then we're going to go ahead and set our loader to hidden once more. 303
The next thing I want to do is to make the NFT item disappear from the Discover page once it's been 304
bought. 305
So to do that, we're going to add a new style to our top-level div in our item.jsx. 306
Just before the class name of the first div, I'm going to add my style property, and inside here we're 307
going to use a little bit of JavaScript to determine whether or not this thing actually displays or 308
not. 309
There is a CSS property called display which we can set, and if we set this to none, then you'll 310
see that it will disappear, even though it's actually there 311
if we take a look inside the elements. If you go inside the route and inside our gallery view, you can 312
see that we've got our item and all of the divs that correspond to our item, but it doesn't show up 313
because we've set the display to be none. 314
You can either set this display to none or you can set it to inline, which will show it which is what 315
its original display setting is. 316
But we're going to use a ternary operator here for the display and we're going to use a variable called 317
shouldDisplay. 318
And if it is true, then we're going to use the inline property, and if it's false then we're going 319
to use none, 320
so n-o-n-e. And we're going to keep track of the shouldDisplay constant up here. 321
And setDisplay. 322
And we're going to set it to true to begin with. 323
So it should in fact display the item normally, but we're going to use the setDisplay once the purchase 324
has gone through successfully. 325
So down here in our handleBuy right at the very end, we're going to set the display to false and that's 326
going to make that item disappear once it's been purchased. 327
So we can test all of that out by going through our read me and minting our NFT, copying our principal over to 328
list it. 329
And finally, we're going to put this principal into here to call that canister and transfer the ownership 330
to my OpenD canister, which I hope has not changed. 331
So I'm going to go ahead and do that. 332
And now I will see my new NFT ready to be purchased. 333
And check out what happens this time. 334
So just to show you that the account actually does change, 335
currently I've got 9,988 DANG. 336
I'm going to click Buy. 337
I've got my loader showing up and once the purchase process goes through, that item is removed from 338
the gallery. 339
And if we check our faucet, you can see it went down by 2 340
DANG and I transferred the money to the original owner of the NFT and I should now be able to view my 341
NFT in the My NFTs section. 342
And we have now pretty much completed this entire project. 343
I can already think of many, many things that you could think about doing to improve the project. 344
But this has been a really, really long and very big project, which is why in each of the steps, I've 345
actually included the code that you can run in case you run into any problems. 346
While building this project, I had many, many, many bugs. 347
And I wanted to show you some of those bugs just so that in case you see those error messages, you'll 348
just think to yourself, ah, I saw that in the video and you'll know what to do. 349
But the truth is, working with the Motoko backend and the internet computer and plus React frontend, 350
it is a complex, complex project and I fully expect you to have many, many things to debug and to 351
figure out. 352
But hey, that's all the part of the fun of learning programming, right? 353
But at the end, once you persevere through, it's actually a pretty cool website. We're able to mint 354
NFTs, we're able to list them for sale, we're able to buy them and keep track of the NFTs that we 355
own and it has a lot of the core functionality of a digital marketplace like Opensea that is running on 356
the Internet computer. 357
Now, if you have a lot of money and you want to deploy this project, then feel free to do that. 358
But just a word of warning that every single time you mint an NFT and you test anything on the deployed 359
version, then it's going to cost real money and it actually can eat down your balance quite a lot. 360
Which is why I've tried to keep it in localhost so that we can all learn the things we need to learn 361
before we start putting up a big budget. 362
But remember that Dfinity has a really generous developer grant scheme. 363
So if you think of a project that you think would be really awesome on Dfinity, be sure to go through 364
the portal and submit a grant application where they can give you lots and lots of support and also 365
cycles to be able to develop something that is valuable to either people working with the Internet computer 366
or just as a standalone, decentralized app. 367
So I hope you enjoyed building this and many of the other projects along with me, and I look forward 368
to seeing all the great things that you're going to be creating with your imagination in the future. 369
So until next time. 370
That's all from me.