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) ;
}
}
Related
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.
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
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
I'm building a game on ethereum as my first project and I'm facing with the storage and gas limits. I would like to store a storage smart contract on the blockchain to be queried after the deployment. I really need to initialize a fixed length array with constant values I insert manually. My situation is the following:
contract A {
...some states variables/modifiers and events......
uint[] public vector = new uint[](162);
vector = [.......1, 2, 3,......];
function A () {
....some code....
ContractB contract = new ContractB(vector);
}
....functions....
}
This code doesn't deploy. Apparently I exceed gas limits on remix. I tried the following:
I split the vector in 10 different vectors and then pass just one of them to the constructor. With this the deploy works.
I really need to have just one single vector because it represents the edges set of a graph where ContractB is the data structure to build a graph. Vectors elements are ordered like this:
vector = [edge1From, edge1To, edge2From, edge2To,.......]
and I got 81 edges (162 entries in the vector).
I tought I can create a setData function that push the values in the vector one by one calling this function after the deployment but this is not my case because I need to have the vector filled before the call
ContractB contract = new ContractB(vector);
Now I can see I have two doubts:
1) Am I wrong trying to pass a vector as parameter in a function call inside the A constructor ?
2) I can see that I can create a double mapping for the edges. Something like
mapping (bool => mapping(uint => uint))
but then I will need multi-key valued mappings (more edges starting from the same point) and I will have the problem to initialize all the mappings at once like I do with the vector?
Why does the contract need to be initialized at construction time?
This should work
pragma solidity ^0.4.2;
contract Graph {
address owner;
struct GraphEdge {
uint128 from;
uint128 to;
}
GraphEdge[] public graph;
bool public initialized = false;
constructor() public {
owner = msg.sender;
}
function addEdge(uint128 edgeFrom, uint128 edgeTo) public {
require(!initialized);
graph.push(GraphEdge({
from: edgeFrom,
to: edgeTo
}));
}
function finalize() public {
require(msg.sender == owner);
initialized = true;
}
}
contract ContractB {
Graph graph;
constructor(address graphAddress) public {
Graph _graph = Graph(graphAddress);
require(_graph.initialized());
graph = _graph;
}
}
If the range of values for you array are small enough, you can save on gas consumption by using a more appropriate size for your uints. Ethereum stores values into 32-bytes slots and you pay 20,000 gas for every slot used. If you are able to use a smaller sized uint (remember, uint is the same as uint256), you'll be able to save on gas usage.
For example, consider the following contract:
pragma solidity ^0.4.19;
contract Test {
uint256[100] big;
uint128[100] small;
function addBig(uint8 index, uint256 num) public {
big[index] = num;
}
function addSmall(uint8 index, uint128 num1, uint128 num2) public {
small[index] = num1;
small[index + 1] = num2;
}
}
Calling addBig() each time with a previously unused index will have an execution cost of a little over 20,000 gas and results in one value being added to an array. Calling addSmall() each time will cost about 26,000, but you're adding 2 elements to the array. Both only use 1 slot of storage. You can get even better results if you can go smaller than uint128.
Another option (depending on if you need to manipulate the array data) is to store your vector off chain. You can use an oracle to retrieve data or store your data in IPFS.
If neither of those options work for your use case, then you'll have to change your data structure and/or use multiple transactions to initialize your array.
I have the following code in my Cocos2d-X application
void SampleRequest::setResponseCallback(CCCallFuncND* cb){
if(cb){
cb->retain();
stored_cb=cb;
}
}
void SampleRequest::executeStoredCallback(){
if(stored_cb)
stored_cb->execute();
}
void SampleRequest::releaseCallback(){
if(stored_cb){
stored_cb->release();
stored_cb=NULL;
}
}
and a simple class
void RequestHandler::handleSampleRequest(int data){
CCLog("--------------------------------------------> Its here for me to do %d",data);
}
and another peace of code
int i=10;
SampleRequest *t=new SampleRequest();
t->setResponseCallback(
CCCallFuncND::create(
this,
callfuncND_selector(RequestHandler::handleSampleRequest),
(void*)&i));
but the value of i recieved is 0. How can i send the value of I back to the call back function, and how can i send multiple parameters to this function.
Kind Regards,
int i=10;
Are you declaring i as a temporary variable on the stack, rather than on the heap, or as request object instance data?
If so, your i variable will be destroyed when the block within which it is created exits (variable scope ends).
That could explain why the callback receives a value pointing to undefined memory, that has been destroyed at the time of the call.
Try using the new operator, or storing your i value inside your request object up until the cb call is made.
how can i send multiple parameters to this function
You would not ; Simply pass a pointer to a structure or object. If all your stored data is in your "request" instance, you can pass the instance itself, as well.
For an example, assuming, again, that the data passed to the callback is going to remain in memory at the time of the call to the callback function (ie, the "RequestData" instance below):
struct RequestData
{
int value1 ;
int value2 ;
// ....
} ;
class RequestHandler: public cocos2d::CCObject
{
// ...
public:
void requestCallback( CCNode* sender, void* pData ) ;
}
In your implementation:
RequestHandler::requestCallback( CCNode* sender, void* pData )
{
RequestData* pRequestData = static_cast<RequestData*>( pData ) ;
if ( pRequestData )
{
// do something ...
}
}
To construct your call, build an instance of RequestData containing all the data you need to pass to the callback, make sure it is allocated on the heap with "new" or part of another object (in a queue, for instance) so that its data will still be valid in memory at the time the callback is called. I insist a bit on this point because you need some kind of data storage mechanism as part of your design, otherwise your callbacks may find themselves working off invalid addresses in memory (dangling pointers).
Essentially, from your previous code:
RequestData* pRequestData = new RequestData();
// fill in the structure data here...
SampleRequest *t=new SampleRequest();
t->setResponseCallback(
CCCallFuncND::create(
this,
callfuncND_selector(RequestHandler::requestCallback),
(void*)pRequestData));
// Use like this
void* data = (int*) 10;
int value = *((int*) &data);