Is it possible for the same contract to handle multiple ERC721
tokens? And if so, how?
Let’s say you’re making a game and you need to track two types of ERC721
tokens: unique Weapons and unique Vehicles.
You could have Structs for them, as follows:
struct Weapon {
uint256 IDNumber;
string type; // Sword, Bow&Arrow, Gun, Rifle, etc.
string specialFeature;
}
struct Vehicle {
uint256 IDNumber;
string type; // Car, Airplane, Boat, Spaceship, etc.
uint256 damageFactor;
}
To track their ownership, you could double the arrays managing them - for example instead of having the standard:
// Enumerable mapping from token ids to their owners
EnumerableMap.UintToAddressMap private _tokenOwners;
You would do:
// Enumerable mapping from token ids to their owners
EnumerableMap.UintToAddressMap private _weaponTokenOwners;
EnumerableMap.UintToAddressMap private _vehicleTtokenOwners;
(This may not be the most elegant way, but it's a start.)
The real question though is: how would you handle mandatory functions that are part of the ERC721
standard, such as balanceOf()
and ownerOf()
?
To be specific, are you allowed to say add an additional argument to these methods’ signatures to help indicate which particular Token you’re querying about?
For example, instead of this:
function balanceOf(address owner) public view override returns (uint256) {
}
You’d add a tokenName
argument to the function’s signature, as follows:
function balanceOf(address owner, string tokenName) public view override returns (uint256) {
if(tokenName == “weapon”) {
return ownerAndHisWeaponTokensDictionary[owner].length;
}
else if(tokenName == “vehicle”) {
return ownerAndHisVehicleTokensDictionary[owner].length;
}
}
And you’d do something similar for ownerOf()
?
Is this allowable? And is this even the right approach tackling this - or is there a different way to reason about all of this and approach it differently?
My approach would be to define 3 separate contracts on 3 separate addresses:
0x123123
as the Weapons ERC-721 token contract0x456456
as the Vehicles ERC-721 token contract0x789789
as the actual game contractIn the game contract, you can then call the NFTs contracts to get or validate values:
function attack(uint attackerWeaponId) {
require(weaponsContract.isOwnerOf(msg.sender, attackerWeaponId));
// ...
}
The isOwnerOf()
function takes 2 arguments, address owner
and uint256 weaponId
. Also, a user can probably own more weapons so that's why I'm showing the validation.
And the weapons contract balanceOf(address)
would reflect the total amount of the Weapon NFTs that the user has.
mapping (address => Weapon[]) userOwnedWeapons;
function balanceOf(address owner) external view returns (uint256) {
return userOwnedWeapons[msg.sender].length;
}