Skip to content
by Unity Nodes
NEAR Protocol

NEAR Live Contract Review | Episode 17: NFT with approvals + Marketplace

about 44:27 long

Nothing is loaded from YouTube until you press play, and then without tracking cookies.

About this video

We've reviewed a simple version of a NFT contract implementation with ability to add approved accounts per token. The storage is paid by the NFT contract owner at the moment a new NFT is minted.

The account approvals storage is paid by the owner of the tokens. When the owner adds a new approved account ID to their token, the owner has to cover the storage with the attached deposit and the rest is refunded. But when the approved account(s) are removed from the token, the token owner gets a refund for the released storage.

We've also looked at how to implement a simple marketplace that uses NFT approvals system to be able to list an NFT tokens without taking ownership first.

Source code of the NFT contract: https://github.com/near/core-contract...

Source code of the reviewed marketplace: https://github.com/near-apps/nft-laun...

Episode #16:    • NEAR Live Contract Review | Episode 16: Me...

Follow the latest from NEAR Protocol on,

Website: near.org Discord: near.chat Blog: near.org/blog Twitter: near.ai/twitter GitHub: near.ai/github

#Blockchain​ #FutureIsNEAR #NEAR #NEARProtocol

Transcript

5,075 words

Captions as YouTube publishes them, usually machine-generated, so product and people names can be misheard.

  1. 0:01

    hey everyone uh this is eugene and today we're gonna record another video of live contract reviews and we're gonna review two contracts one is our attempt at a new nfg standard and another one is a contract by matt which is a market that would use approval system from the new nft to try to actually sell tokens so let me start my c line cool so first i recorded all changes that matt did to to the simple nft so it's a one that is currently

  2. 0:50

    up on on the core contracts in the pr it uses a prerelease version of nearest the case so it has some advanced features such as panic on default but we still haven't published the contract so the difference from the previous version which had only one um owner and it was following a fungible token pattern where you can only transfer token you cannot give approval so this one adds

  3. 1:29

    ability to approve a set of accounts per token so the main structure is contract it has two collections one is tokens per owner so it has a map of from account id to the set of tokens that this account owns and you can iterate on this except you don't have view calls yet but that's the goal second is you have um tokens by id so from token id to uh to the token structure it's not very optimized towards the

  4. 2:09

    performance but it's somewhat working now a token is a structure that has three fields it has the current owner id as an account metadata is what this token is about and a hash set not a persistent hash set of the account ids that are approved so every time a token is deserialized and pulled from the storage you can see a list of approved accounts per this token and this set is limited by the gas you cannot really

  5. 2:50

    like add too many accounts but we probably should limit it to let's say 10 accounts at most or something like this so 20. and finally to two other three other fields so the token contract or the the this nft contract has the owner so owner can meet new enfts and it has total supply the total supply is the number of tokens in the collection we don't really need this because we can just take this map out and get the links

  6. 3:33

    so you can probably remove it and amount of storage that you need to mint nft and let's this is needed for some basic storage management which we need to talk about so when you initialize the contract you provide some defaults and it takes the owner id next thing you do you measure the cost the extra cost that you would need per token when you meet a new token as you can see there's no

  7. 4:09

    real storage management per per account or per other users this is because the storage is fully paid per token is fully paid by the owner of the contract so when the owner meets a new nfg the owner pays for the storage of this new nfg and everything except for the approvals and we'll talk about storage management of approvals in when we talk about uh implementation of this now in order to um signs the owner account id

  8. 5:01

    can be shorter than 64 characters when the token is transferred the new account id may be longer than the owner's account id that will increase the amount of storage required for this token to be stored um you may see that the token has a fixed size except for the account id and ignore approvals for now metadata doesn't change at least in the current implementation metadata is fixed

  9. 5:32

    the account id can be variable so what we need is we need to calculate the worst case scenario in amount of storage that is required to store a 64 character account id um to do this we measure the initial storage then we create a new unordered said with the unique prefix unique prefix right now is a fixed size of 33 characters and tokens per owner is um inserts this new longest account id

  10. 6:16

    and this and new order said we don't insert the new token id which will be added later what we do we say okay so what is the tokens per owner entry in bytes so this is how much it's going to cost to insert one on order it said we measure this as well we measure how much you need to pay when you change ownership from a default owner or like from a current owner to this 64 characters so this is the difference

  11. 6:48

    between lengths of the accounts and it means the extra storage in bytes is something like this um and you will see how it's used when we meant a token which is the next thing we do then we clean up storage so we remove this uh an order it said from the existence we talk a little bit more about how you said metadata is set per like uh the same for every token that's the expectation uh it's fixed per

  12. 7:23

    token which means you as a owner once the token is created or is the new owner you cannot change the metadata so it cannot change okay storage size and i missed it being measured that's not part of what's being measured in measurement token storage cost yes it's going to be measured separately so okay it's measured in mint the mint function is when you want to mint a new token with the token id and then metadata you cannot give it directly to someone

  13. 7:54

    so you will first as owner receive it by yourself um so we assert that you're the owner which just checks that you the predecessor matches uh it's a payable method we start by remembering the current storage so we create a new token where you are the owner a metadata is set and the list of approved accounts is empty now we insert this new token into the tokens by id so we insert a new

  14. 8:33

    token into the set if the token with this token id already exists we say well no you cannot recreate a new token with the same token id now we get we add it to the a list of tokens owned by the given account so owner now owns this new token id the way it works we get the set with a unique prefix and we insert a token id into the set and we save tokens per owner now we increase the total supply by one which is not necessary

  15. 9:22

    because you can derive it and we measure how much bytes we spent on this operation so we say we actually spent the increased storage by this given amount by doing two operations right now adding a new token into storage and adding the token id into the ownership of the of the oh like into this on tokens by the contract owner plus we use this extra storage and bytes that we measured originally in the in select in in the constructor

  16. 10:08

    to compensate for the difference in account links and potentially a new set have to be created by the new owner of the token and this is the amount of storage and bytes required for this token to be minted and there is a helper function that's saying basically take the attached deposit and refund the rest so keep the tokens that needed to to increase the storage and refund the rest and that's pretty much it so this is how

  17. 10:47

    you mint it so token id is measured twice uh in the insert in the storage then metadata is measured as well and the only difference is like this account links difference plus they're like a new another set difference in storage that we measured originally which means um now if the stocking is transferred you basically don't need to cover storage because it's already paid for by the

  18. 11:21

    contract owner by the minter does it make sense the logic behind the storage mostly i feel like i would want to squint at it a little bit longer to be fully satisfied but we don't need to do that here cool yeah i think it well i also haven't wrote a test for this which is probably would be a good idea to just make sure we're not missing any bytes now now we have ability to meet new tokens so basically let's say we have a token

  19. 12:02

    and we have a methods in a core token core nft token core um trade interface that defines how much uh well what methods needs to be implemented for the core standard so it is similar to fungible token in terms of what it is except we have a new extra field on a transfer which say enforce owner id this is what we talk about security of the approvals and same method is on transfer call the reason why you want to enforce it is because the predecessor might be different from the token owner

  20. 12:50

    because predecessor can be an approved account that has ability to send this token but this predecessor is not the owner so we need to know who is intended owner to avoid some race condition security now we have three methods that allows you to approve an account id for a given token revoke the account id from a given token and revoke all accounts for the given token and these are to one of them is number of tokens and second

  21. 13:38

    is give the token and this one also returns the list of approved accounts because it's part of the token now we also have token receiver so this is not really implemented by this method and it uses bull to say if the token should be returned so to to match the idea of fungible token so far so it's essentially saying you should return one token back to the sender so just a note that uh the current ver the current discussion around the nft

  22. 14:31

    standard this this might end up requiring a panic rather than returning that panic is the worst idea than um the nurturing info and i commented on the reason why main reason is panic is much harder to return if you need to save your state so if you're a receiver and you want to do some actions let's say you want to do another call on the receiving side then you have a callback and in your callback you may want to

  23. 15:13

    save your state if you have to return a panic then you cannot really save your state because at panic the state is not saved that's why um returning true as a secondary option is a better idea true or false doesn't matter like we can change it um it has an extra field that is not on functional token it has a previous owner id and the previous owner id is the account that is was actually only the token because the token can be sent by approved account instead of actual

  24. 16:06

    owner and and we have a resolver so overall the logic is fairly simple for transfers it is somewhat similar to fungible token except we need to handle storage refunds for the set of approved accounts and here's how we do this for when we call internal transfer if it succeeds not panicking then it would return the previous owner of the token and the previous list of the approved account ids for

  25. 16:56

    this token and for nft transfer so for the simple transfer we just refund this list the storage for this list back to the previous owner so there is no storage management standard here which may be a problem but for now we didn't implement it but we can actually implement it so just to double check internal transfer transfers the token to the new owner and clears the list of approved account ids since the new owner presumably doesn't want them

  26. 17:39

    and since it has cleared the storage it can refund the deposit for that storage to the previous owner correct now nft transfer call now this call is a little more complicated and the reason for this is we actually need to handle a refund of approved account ids only after the transfer was confirmed because if the transfer was cancelled by a receiver or receiver panicked it means the approved account id set has to be returned back

  27. 18:23

    to the original one that the owner has set instead of being the one that was reset during the transfer so during this transform we immediately transferred the token to the new owner and clear the list of the approved account id so then now the contract to which we transferring these tokens is the owner of this account id or of the stockinette this uh clear set of the approved account ids list so there's no other accounts that can interact on this

  28. 19:06

    if we refund the storage immediately for the approved account i just list and later the transfer is reverted it means we out of storage so we cannot set it back so the list has to be temporary captain memory in case we need to revert the transfer and we cannot refund tokens for this list because um the transfers have not been finalized yet so the receiver have not finalized the transfer

  29. 19:44

    um now so it calls a promise on the receiver and then it calls the callback so before we go into how the approvals works let's actually look what we do at the resolver so resolver is somewhat similar to the fungible token one so we check the promise is successful we say whether we should return the token back to the sender or like back to the original owner if we don't need to return we say the token transfer was successful and we refund the list of the approved

  30. 20:27

    uh account id so this is kind of the same we're doing that we did at the regular transfer and we say okay the transfer has completed now if if we have to return it for example promise failed to be it returned uh full uh it returned true so we need to return the token then we get the the current token if the owner has changed during the moment after we initiated transfer call it means maybe the receiver the contract already transferred to

  31. 21:13

    somewhere we cannot do anything it means the the refund cannot be completed so we refund the approved account storage again so it just calls this method again and return true so the token owner is not owned by a receiver we can't do it otherwise let's say the token was burned well there's no ability to burn but let's assume it's possible we also give that money back and say okay done it was taking otherwise we send a message saying okay we're gonna return this talking id back

  32. 21:46

    to the from receiver back to owner to original owner we remove tokens from the new owner is added back to the original owner we said the token ownership we refund the list of the approved accounts for the new owner so let's say a new owner actually added an approved account and paid tokens for this so we give the money back to the current new owner who should refund it then um the ad we changed the list and we don't refund it and we insert it back

  33. 22:28

    so that's the logic it's complication around storage management that we need to keep track of it's a complication around approved account ids list that we need to keep track of and stuff like this okay so let's quickly go through approved approvals so here's how it works so we get the one yokta for the approval we say that token is getting the token we verify the ownership so only the owner can add new approved so a marketplace cannot add another

  34. 23:09

    approval you get the account id and the storage needed for a new account that you added then we say okay go insert this and if the approval was already there then it just returned that it was there you don't need to do this again if it's not there then we refund the deposit oh we actually take your deposit i guess the problem is it didn't do you need to return money and it cannot be one yorktown sucks

  35. 23:56

    because it has to be more than one reactor or at least one reactor this one can be one reactor so we take money for um for the storage use if you already have a token then we just refund everything we can also panic here it will automatically refund the money and revoking works the following so we get again the same checks around the ownership now we remove this account id

  36. 25:05

    from um from the list of approved tokens if we successfully removed one then we need to release storage so we get the storage for the remote account in bytes and we refund this amount to the predecessor who is the owner actually and we save this token and return true if not we if such account didn't exist we just return false and now revocal does the following it uses this magic method of

  37. 25:47

    refunding all accounts i did and we clean it up and we saved the token where should we dive okay so so bytes for approved account id is is the length of the account plus four for the lengths of the board serialization a refund list of account id calculates the sum by calling this method on every account id from the set and then refunds this amount based on the

  38. 26:54

    storage released and finally the internal transfer how it works with approvals so we get the token we break the token into owner id metadata and a set of approved account ids we verify that you have ownership or access by checking if the sender is not the owner and the approved account id list does not contain the sender then we panic now if the if we need to enforce the owner id then we do the check that the owner is exactly the same as uh one that is enforced then we say

  39. 27:46

    you cannot transfer to the same receiver then we'll log that we do in the transfer we remove owner from a current one of token from the current list of tokens from for the owner and give it to receiver then we create a new token with the owner for new receiver a list is empty and the same metadata and very inserted back to the set and if there is a memo we drop the memo and return the original owner so that's pretty much it for the nft standard

  40. 28:32

    there is no storage management that is following the standard so because the list of approved account ids is currently tracked and refunded on demand to the original owner and only the original only the account can actually initiate this transfers so we have 10 minutes left and let's actually look at the simple market that matt implemented the contract is has the owner id and a list of the tokens on sale contract and token id is a string for

  41. 29:30

    some reason yeah it's a um it's a lookup basically to say that the the market simple can basically be used by any contract uh and token for the contract so so it's just a conca it's a concatenation with a colon sure um so you initiate the contract and then there's a method to add a listing which is he has the contract id the token id and the price and you're saying on behalf of which account you you want

  42. 30:18

    to do the sale which probably should be valid account id here so you get the deposit it needs to be at least point one near to list i guess to cover storage for the listing and like do some civil resistance contract id is the is the token contract id and owner id is who is calling the sale well why okay okay so if you call in on behalf of then you can like pretend to be an owner and you kind of paying for the storage

  43. 31:23

    but okay beneficiary is the the predecessor got it surprise and the deposit is how much tokens you paid for this listing price is what you want to get for the token now you have removed sale that or removes the given sale it checks that the owner's predecessor so only the predecessor can remove sale and it refunds it for some reason to the owner instead of beneficiary so it's a bug should the owner id should the on behalf of ch like does

  44. 32:15

    this mean anyone can list an nft as being for sale yes yeah so so far there doesn't check the ownership so i i feel like there should be like at least some is there a way to to verify the ownership so you can at least someone's talking for sale but it should be owner or approvers or like approved whatever we call them approvals yeah approved accounts but um that that's fine which would require a cross-contract call

  45. 32:52

    yep which means it could change in between yes yeah that's a problem with approvals like approvals cannot be verified so this is as good as rely on a cross-contract call the only uh yeah the only way to to solve the problem as i see it is is if the call originates inside the nft contract and the token ownership is enforced and then the nft contract in a very standard way um calls the uh the market contract and this could be this could be done in a standard way and it could actually be

  46. 33:34

    paired up with the approval so it could be like some some method for example like like approve and add sale to market contract and then what would happen is you check token ownership you add to the approved you add the contract id to the approved ids and then you make the cross-contract call which adds the sale so it does it all in one one shot yeah you can do unapproved so you can at least prevent some some listings it's still at any moment

  47. 34:07

    listen can become invalid because the approval can be revoked but we also can do well we cannot really do revoke callback because it's many of them while approval is one to one so we can only add the pros but then what's the point if you can do revokes plus there still can be like cross contract call for this anyway so let's look at the purchase so you have a contract id a token id you pull the sale

  48. 34:48

    you get the how much it's paying so the price should be exact so you're paying exactly create you actually don't need to to use a valid account id here so that can be just accounts for the interface so owner id um oh yeah just to clarify so when you're doing when i'm specifying uh this external transfer nft transfer trait down at the bottom i can just use account ids and just ignore the whole account id thing yeah so valid account

  49. 35:34

    ids uh essentially they should only be used for external the the very first external call coming in right yeah but also you have the option here here i think it's better to to to follow this pattern even so that this realization potentially will work so you don't appear so um sorry this was a this was a leftover a little bit of kind of leftover code i had oh no no sorry never mind nevermind

  50. 36:06

    i understand what you're doing i originally i i originally had a really terrible flow for the market contract um where i had the nft transferred to the market contract and then transferred to the to the buyer it was it was not not optimal yeah that's the best um okay so you have a memo which is again should be an optional string you don't really need to pass it but so you you better do this and

  51. 36:49

    you send in a contract id and paying for the transfer call and herself and on purchase you remember the contract that you're talking i did the predecessor but you already removed the listing what you really need is a deposit for listing unless you keep the fee maybe you just keep the fee um and on resolve uh what you do is you actually forget to enforce that it's a private contract so you probably want to do

  52. 37:37

    so some other contract doesn't call you and then you got the token id so you check the result of the promise so if promise is successful but it always successful it could fail it could fail at the transfer itself so are you calling the basic transfer yeah that's fine yeah got it so you paint the but there's no sale already you already removed the sale oh no you did not i see it means you can like try to purchase twice

  53. 38:33

    so okay so there's a there's a problem here you can purchase twice in between promises right like yeah yeah okay so you have to remove the sale and add it back ideally yeah if it wasn't successful yeah you can basically do this um just like you did with the approved account ids you can log the sale instead instead of removing it you can like say oh the purchase in progress but in theory if the sale failed you probably want to remove

  54. 39:06

    the sale anyway because it's invalid now but what if somebody what if the sale failed due to you know not like dude this is not enough gas yeah you probably want to attach gas fixed here so the sale fails before you initiate so instead of paying prepaid gas you just say well the transfer requires some near let's say 10 just because it needs to do a refund anyway let's see what you do so let's

  55. 39:43

    say the transfer succeeded uh that's um then you refund to well that's actually not correct because why beneficiary grabs both the sale and the deposit shouldn't beneficiary only grab the deposit and like you should pay the owner like what's the point of the owner i guess i maybe don't understand the market uh yeah the deposit the deposit was just to pay for storage of of storing the sale right um yeah so you're gonna pay that

  56. 40:30

    yeah this part this part's not perfect um something like this so you pay the owner the price and they deposit back to the beneficiary or something if you the one who listed well actually i'm not sure who oh yeah can i can i explain why i did i did what i did by by explicitly listing a beneficiary of the sale proceeds so the beneficiary um so in my in my nft example i have i have

  57. 41:01

    like essentially um like guest accounts and meta transactions like basically sponsored transactions so i have i have the nft contract acting on behalf of of a sub account so basically the nft contract is the one that that made the that made the uh the original sale deposit and the nft contract will will take the sale proceeds and then the and then the the nft contract that i have uh essentially it

  58. 41:36

    marks uh it marks the sale price as um as basically uh funds that should be escrowed for the uh for the guest so the guest when the guest wants to like withdraw their funds they can they can sort of take out that amount from the contract i realize it's it's totally totally like not the best no no it's a problem because the beneficiary can set the price to whatever they want on behalf of the owner so if owner

  59. 42:06

    approves this marketplace then the beneficiary can list a token and immediately buy it and pay owner zero like very low price like one right um but the like again the idea is that the nft market is inviting these people in as guests so it's like i mean it's you're describing kind of like a rug poll right like i mean if they started doing that to their guests then i don't think they'd have a

  60. 42:38

    very successful app but a okay so the idea is somewhat correct except uh you cannot really trust the ownership that the listings still exist and stuff like this soon yeah but the idea is the market would look something like this um so it will attempt to do the transfer on behalf of the real owner the one who listed the the token as an owner and um paid the owner instead of benny

  61. 43:18

    fisher so beneficiary here shouldn't exist at all and only maybe like doing the deposit but the owner has to confirm the price instead of beneficiary either way uh the out of time that we wanted to allocate for this so thanks for watching hope it's gonna it was helpful to explain the nft standard that we currently have and um we still keep it rating on this but this is what we have

  62. 43:49

    so far and maybe it makes sense to to put the storage management into the standard but since all operations still requires one yoktan year for like every approval and transfer then the storage management doesn't really serve the purpose here such as it's required for likable resistance for this one the main reason was to to get the approvals it but we did it through just a regular refunds okay

  63. 44:27

    thanks bye thanks eugene