package nft
import (
"std"
"strconv"
"gno.land/p/demo/avl"
"gno.land/p/demo/grc/grc721"
)
type token struct {
grc721.IGRC721 // implements the GRC721 interface
tokenCounter int
tokens avl.Tree // grc721.TokenID -> *NFToken{}
operators avl.Tree // owner std.Address -> operator std.Address
}
type NFToken struct {
owner std.Address
approved std.Address
tokenID grc721.TokenID
data string
}
var gToken = &token{}
func GetToken() *token { return gToken }
func (grc *token) nextTokenID() grc721.TokenID {
grc.tokenCounter++
s := strconv.Itoa(grc.tokenCounter)
return grc721.TokenID(s)
}
func (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) {
token, ok := grc.tokens.Get(string(tid))
if !ok {
return nil, false
}
return token.(*NFToken), true
}
func (grc *token) Mint(to std.Address, data string) grc721.TokenID {
tid := grc.nextTokenID()
grc.tokens.Set(string(tid), &NFToken{
owner: to,
tokenID: tid,
data: data,
})
return tid
}
func (grc *token) BalanceOf(owner std.Address) (count int64) {
panic("not yet implemented")
}
func (grc *token) OwnerOf(tid grc721.TokenID) std.Address {
token, ok := grc.getToken(tid)
if !ok {
panic("token does not exist")
}
return token.owner
}
// XXX not fully implemented yet.
func (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) {
grc.TransferFrom(from, to, tid)
// When transfer is complete, this function checks if `_to` is a smart
// contract (code size > 0). If so, it calls `onERC721Received` on
// `_to` and throws if the return value is not
// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
// XXX ensure "to" is a realm with onERC721Received() signature.
}
func (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) {
caller := std.GetCallerAt(2)
token, ok := grc.getToken(tid)
// Throws if `_tokenId` is not a valid NFT.
if !ok {
panic("token does not exist")
}
// Throws unless `msg.sender` is the current owner, an authorized
// operator, or the approved address for this NFT.
if caller != token.owner && caller != token.approved {
operator, ok := grc.operators.Get(token.owner.String())
if !ok || caller != operator.(std.Address) {
panic("unauthorized")
}
}
// Throws if `_from` is not the current owner.
if from != token.owner {
panic("from is not the current owner")
}
// Throws if `_to` is the zero address.
if to == "" {
panic("to cannot be empty")
}
// Good.
token.owner = to
}
func (grc *token) Approve(approved std.Address, tid grc721.TokenID) {
caller := std.GetCallerAt(2)
token, ok := grc.getToken(tid)
// Throws if `_tokenId` is not a valid NFT.
if !ok {
panic("token does not exist")
}
// Throws unless `msg.sender` is the current owner,
// or an authorized operator.
if caller != token.owner {
operator, ok := grc.operators.Get(token.owner.String())
if !ok || caller != operator.(std.Address) {
panic("unauthorized")
}
}
// Good.
token.approved = approved
}
// XXX make it work for set of operators.
func (grc *token) SetApprovalForAll(operator std.Address, approved bool) {
caller := std.GetCallerAt(2)
grc.operators.Set(caller.String(), operator)
}
func (grc *token) GetApproved(tid grc721.TokenID) std.Address {
token, ok := grc.getToken(tid)
// Throws if `_tokenId` is not a valid NFT.
if !ok {
panic("token does not exist")
}
return token.approved
}
// XXX make it work for set of operators
func (grc *token) IsApprovedForAll(owner, operator std.Address) bool {
operator2, ok := grc.operators.Get(owner.String())
if !ok {
return false
}
return operator == operator2.(std.Address)
}