Making an ERC721 contract
In this tutorial we are going to implement a subset of the functionality of
an ERC721 contract in Etch. We will be using
UInt256 for identifiers and
SHA256 to generate the identifiers of the initial tokens.
We will need two
Address record stating which tokens an address holds and a token
record keeping track of the owner of the token.
For the first of these tasks, we will
State object in Etch, and for the second we will use
Assuming that we have defined an
owner and a
total_supply, the initialise function
will do three things:
- It generates a list of token ids.
- It creates a record of each token owner.
- It creates a record of the tokens that an owner has.
The dual relationship is there to make lookups efficient, but comes at the price of twice the book keeping.
As the first step we create the list of token ids:
// Genereating tokens var token_id = UInt256("hello world"); for(i in 0:tokens.count()-1) var hasher = SHA256(); hasher.update(token_id); token_id = hasher.final(); tokens[i] = token_id; endfor
Next we assign an owner:
// Assigning owner var owner_state = ShardedState< Address >("tokens.owner"); for(i in 0:tokens.count()-1) var tid = tokens[i]; owner_state.set(toString(tid), owner); endfor
var objects_state = State< Array< UInt256 > >(owner); // Storing the tokens on the owners address objects_state.set(tokens);
In this section we will focus on the two functions which are needed to implement a wallet overview and token details view, namely
ownerOf. Both of these functions are short and easy to implement. We first make it possible to query the balance:
@query function balanceOf(owner: Address) : UInt256 var objects_state = State< Array< UInt256 > >(owner); var tokens = objects_state.get( Array< UInt256 >(0) ); var ret = UInt256( toUInt64(tokens.count()) ); return ret; endfunction
@query function ownerOf(token_id: UInt256) : Address var owner_state = ShardedState< Address >("tokens.owner"); return owner_state.get(toString(token_id)); endfunction
The standard ERC-721 contract has a number of different functions to transfer funds from one party to another. We will only implement one of these as they are all essentially variations of the same mechanism with more or less error checking built into them.
We implement a single transfer function here:
function transferFrom(from: Address, to: Address, token_id: UInt256) if(!from.signedTx()) panic("Invalid signature from owner."); endif var owner_state = ShardedState< Address >("tokens.owner"); var owner = owner_state.get(toString(token_id)); if(owner != from) panic("Owner does not actually own the token"); endif var from_state = State< Array< UInt256 > >(from); var from_objects = from_state.get( Array< UInt256 >(0) ); var found = false; var position : Int32; for(i in 0:from_objects.count()-1) var tid = from_objects[i]; if(tid == token_id) if(found) panic("Contract broken - token is only supposed be represented once."); endif found = true; position = i; break; endif endfor if(!found) panic("Contract is fundamentally broken - owner has not been updated correctly"); endif from_objects[position] = from_objects[from_objects.count() - 1]; from_objects.popBack(); var to_state = State< Array< UInt256 > >(to); var to_objects = to_state.get( Array< UInt256 >(0) ); to_objects.append(token_id); // updating sender from_state.set(from_objects); // Updating receiver to_state.set(to_objects); // Updating owner owner = to; owner_state.set(toString(token_id), owner); endfunction
The full contract can be found here.