Can I import NFT's metadata into a smart contract? - json

When we want to mint an NFT with its own metadata, the asset file and JSON file should be uploaded to IPFS before the minting.
And we mint a new NFT by sending transactions to the smart contract and within the transaction, the token URI(hashed URI from IPFS) would be set as JSON metadata URI of the token.
Now I am wondering whether there is a way to import the attributes from JSON on IPFS into a smart contract and use the data like const variables in the contract.

You can do it but it is going to cost you alot. You need to create a mapping in smart contract.
mapping(string ->Metadata) private tokenURIToMetadata
Since it is mapped from string, it cannot be public. so create a function
function getMetadata(string tokenUri) public returns(Metadata){
return tokenURIToMetadata[tokenUri]
}
also create Metadata struct:
struct Metadata{
string name;
string description;
string image;
}
And then you have to add new medata to the mapping:
function addMetada(string memory tokenUri,?????????){}
Since we cannot pass json object as an argument to the addMetada, now you have to create a separate mapping for each property of the metadata object. So the cost will be crazy amount.
Instead, you could write the metadata to the filesystem or database but this would not be a good option.

Related

Can ETH miners check all the data which is stored in Ethereum?

My doubt is from the below code:
contract RandomNumber{
uint number;
function get_random() public{
bytes32 ramdonNumber = keccak256(abi.encodePacked(block.timestamp,blockhash(block.number-1)));
number = uint(ramdonNumber);
}
}
We assign a random number to the variable number but if I don't set number public or create another public function to retrieve the value then nobody would know the exactly value through Etherscan. But what about the miners? Can they retrieve these unrevealed data in some ways?
I have tried:
Google, Ethereum whitepaper, Solidity documentation
Assuming that the contract is deployed on a public network (e.g. Ethereum), then the value is always readable.
Not directly through the autogenerated getter function (that's available only for public properties), which means it's not available onchain.
But anyone (including miners) can create an offchain app (for example in JavaScript) that queries the specific storage slot where the value is stored - in this case it's in slot number 0. And it returns the "secret" value.
JS code using ethers library, a wrapper to RPC API of Ethereum nodes:
const number = await provider.getStorageAt(CONTRACT_ADDRESS, SLOT_NUMBER);
Docs: https://docs.ethers.org/v5/api/providers/provider/#Provider-getStorageAt
And the actual RPC API method: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getstorageat

Which information hold an NFT?

I am developing an NFT market using solidity, specifically I am creating my own smart contract on top of OpenZeppelin's ERC-721 smart contract. My NFT at the moment have 5 attributes (id, image, description, collection and image) for the image, I save the hash that ipfs develve when uploading it.
My question is where to save all these attributes, since I have the Image struct that has the aforementioned attributes, I add it to an array and I mint the NFT using the id of the Image object in the array and the address of the creator. I mean, I'm saving all the information outside of the ERC-721 contract, so I don't quite understand what an NFT is, since the attributes are not from the NFT but the NFT is an attribute of my struct.
Am I implementing it correctly and the ERC-721 standard is only the necessary functions of an NFT or am I saving the information where it does not touch?
My code is currently the following:
pragma solidity ^0.5.0;
import "./ERC721Full.sol";
contract NftShop is ERC721Full {
string public name;
Image[] public nft;
uint public imageId = 0;
mapping(uint => bool) public _nftExists;
mapping(uint => Image) public images;
struct Image {
uint id; //id of the nft
string hash; //hash of the ipfs
string description; //nft description
string collection; //what collection the nft bellongs
address payable author; //creator of the nft
}
//Event used when new Token is created
event TokenCreated(
uint id,
string hash,
string description,
string collection,
address payable author
);
constructor() public payable ERC721Full("NftShop", "NFTSHOP") {
name = "NftShop";
}
//uploadImage to the blockchain and mint the nft.
function uploadImage(string memory _imgHash, string memory _description, string memory _collection) public {
// Make sure the image hash exists
require(bytes(_imgHash).length > 0);
// Make sure image description exists
require(bytes(_description).length > 0);
// Make sure collectionage exists
require(bytes(_collection).length > 0);
// Make sure uploader address exists
require(msg.sender!=address(0));
// Increment image id
imageId ++;
// Add Image to the contract
images[imageId] = Image(imageId, _imgHash, _description, _collection, msg.sender);
//Mint the token
require(!_nftExists[imageId]);
uint _id = nft.push(images[imageId]);
_mint(msg.sender, _id);
_nftExists[imageId] = true;
// Trigger an event
emit TokenCreated(imageId, _imgHash, _description, _collection, msg.sender);
}
}
Any suggestions on how to improve the code if there is something weird is welcome.
I hope it is not an absurd question, I am starting in the world of Ethereum.
Thanks a lot.
Yes the information such as image address and so on is stored in a contract that inherits from ERC721. The ERC721 mainly tracks ownership, minting and transfer of a token.
One thing you might want to think about is the uniqueness of a token. In your example many tokens can exist that have the same image and description.
You might want to prevent that by storing a hash of the _imgHash, _description, _collection and require it to be unique, so that no user can create a "copy" of an existing token.
something like:
keccak256(abi.encodePacked(_imgHash, _description, _collection))
another frequently used standart is the Opensea Metadata Standard, where a tokenURI is stored on the chain and the metadata lives outside the blockchain. https://docs.opensea.io/docs/metadata-standards

How can I update data on IPFS

I am storing user information on IPFS in JSON object format and then storing that file hash on blockchain. I want to update that JSON object array every time I add a new user object. How can I achieve this?
I'm using Etherium Blockchain and ReactJS
IPFS hashes are based on the content so the IPFS hash will change when the JSON data changes in this case. This means the content hash on-chain will have to be updated.
Read current JSON data from IPFS
Update JSON data with new entry
Add new JSON data to IPFS
Update content hash on smart contract
Below is an example of a smart contract that can be used to maintain a list of links to user objects.
Each user is assigned a unique identifier (for example, a GUID without delimiters just fits in bytes32).
Method PutUser is used to add / update a link to the user's object.
The GetUser method is used to get a link to the user's object.
Method GetUsersList is used to get a list of users.
When you change the some user's object, you put it again in the IPFS and add a new link using the PutUser
pragma solidity >=0.5.8 <0.6.0;
contract UsersList
{
address Owner ;
struct IpfsLink
{
bytes32 used ;
string link ;
}
mapping (bytes32 => IpfsLink) UsersIpfsLinks ;
bytes32[] Users ;
//
constructor() public
{
Owner = tx.origin ;
}
//
function PutUser(bytes32 user_, string memory ipfs_link_) public
{
if(msg.sender!=Owner) return ;
if(UsersIpfsLinks[user_].used!="Y")
{
UsersIpfsLinks[user_]=IpfsLink({ used: "Y", link: ipfs_link_ }) ;
Users.push(user_) ;
}
else
{
UsersIpfsLinks[user_].link=ipfs_link_ ;
}
}
//
function GetUser(bytes32 user_) public view returns (string memory retVal)
{
return(UsersIpfsLinks[user_].link) ;
}
//
function GetUsersList() public view returns (bytes32[] memory retVal)
{
return(Users) ;
}
}

Solidity : submit string array, key value pair or an object as parameter to a function

In order to change the state of the smart contract from front end inputs, wanted to submit string array to a smart contract , key value pair or objects.
Is it possible to use string array as parameter?
No solidity doesn't support arrays of strings as parameter. You would have to serialize and deserialize it in a string yourself to have the desired result but that would be expensive to do in solidity. You can test that on remix if you want. However, on remix the error message says that this function is supported in the experimental ABI encoder but I have never tested that, or how well it works with other libraries, and it is experimental after all.
As seen in below example from solidity document we can send bytes array to constructor
constructor(bytes32[] memory proposalNames) public {
chairperson = msg.sender;
voters[chairperson].weight = 1;
// For each of the provided proposal names,
// create a new proposal object and add it
// to the end of the array.
for (uint i = 0; i < proposalNames.length; i++) {
// `Proposal({...})` creates a temporary
// Proposal object and `proposals.push(...)`
// appends it to the end of `proposals`.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
If you are trying to send string/Objects data specifically then it's better to separate out the methods and call each methods separately or within each other as currently solidity does not support that (using ABIencodere v2 is exceptional as it is only recommended for development purpose- as per on the date of this answer written)
struct A{
uint date,
B[] b
}
You can separate this out to
struct A{
uint date
}
struct B{
string goods,
uint quantity
}
so now for 1 A you can call N B from your service. Use mapping for binding both(if dependent).
In current situation it's better to design a contract which does not take bulk inputs or give out bulk outputs. However contracts are not for storage of huge data it's for storage of related data which fulfills agreement between parties

Reusing the same Domain Object in For Get and POST

Assuming I have a student Object
public class Student{
private long id;
private string name;
private List<string> courses
}
In a typical Get request, to get a student I send the Student object to the client.
In the case of a PUT request to modify the Student object by adding or removing a course or a POST request to create a Student record, I only need to receive from the client the student.id and the list of courses.
My question is, Can I send back the same Student object from the client in the PUT or POST request without including the name or maybe have name=null?
Or should I create a separate domain object that will be sent by the client for example:
public class StudentReponse
{
private long id;
private List<string> courses;
}
I guess my generic question is, should we separate the Request and response objects in Rest API? or for code re usability try to use the same domain objects for both directions?
should we separate the Request and response objects in Rest API?
Yes - it allows to evolve both the request and response independently.
If you follow REST practices when a create is issued, you should return 201 - created and the ID of the newly created object.
If the client requires details about it, the client can use the ID + GET to get the full resource representation.
Also consider not exposing the domain objects directly through REST.
For example having a domain entity object - it will probably have some fields related to persistence layer like database Id, createdOn, createdBy - etc. Those fields should not be sent to the client. Use a simple StudentDto (StudentResponse, StudentResponseDto whatever you want to call it) representation, which holds only those fields, which are of interest to the client.
Keeping domain and response objects separate also gives you the ability to evolve them separately or change the data representation.
Imagine you have a JPA Entity and you use both JPA and Jackson annotations in the same class - it's very messy and hard to read and maintain.
Update:
If you're using the same object to be modified by the client and sent back to the server, I guess you could reuse it and model it like this:
get
#GetMapping("/students/{id}")
public StudentDto getStudent(#PathVariable long id) {
return studentService.get(id);
}
update (replace)
#PutMapping("/students/{id}/")
public ResponseEntity updateStudent(#PathVariable long id, #RequestBody StudentDto student) {
return new ResponseEntity(studentService.replaceStudent(id, student), HttpStatus.OK);
}
OR
update (partial update)
#PostMapping("/students/{id}/")
public ResponseEntity updateStudent(#PathVariable long id, #RequestBody StudentDto student) {
return new ResponseEntity(studentService.updateStudent(id, student), HttpStatus.OK);
}