Minting tokens tutorial part 1

This commit is contained in:
Richard Ramos 2018-07-10 12:17:15 -04:00
parent 0194956152
commit cea0e4c63b
2 changed files with 153 additions and 15 deletions

View File

@ -29,21 +29,28 @@ class AddToken extends Component {
this.setState({isSubmitting: true});
// TODO: Implementar llamada al contrato para crear el token
// Un token espera los siguientes atributos: energy, lasers, shield and price
// al igual que una imagen
// La siguiente funcion se puede llamar para actualizar la lista de tokens
// ============== BEGIN: Function implementation here ================ //
// TODO: Implement call to the contract to create a token
// A token expects the followin attributes: energy, lasers, shield and price
// and a metadataHash that contains the attribute list
// TODO: the next function needs to be invoked to update the list of spaceships
this.props.loadShipsForSale();
// TODO: remember to update isSubmitting to false to enable the create button
this.setState({isSubmitting: false});
// ============== END: Function implementation here ================ //
}
render(){
return <Grid>
<h4>Crear nave</h4>
<h4>Create Spaceship</h4>
<FormGroup>
<Row>
<Col sm={2} md={2}>
<ControlLabel><i className="fa fa-dashboard" aria-hidden="true"></i> Energ&iacute;a</ControlLabel>
<ControlLabel><i className="fa fa-dashboard" aria-hidden="true"></i> Energy</ControlLabel>
<FormControl
type="text"
value={this.state.energy}
@ -57,14 +64,14 @@ class AddToken extends Component {
onChange={(e) => this.handleChange('lasers', e.target.value)} />
</Col>
<Col sm={2} md={2}>
<ControlLabel><i className="fa fa-shield" aria-hidden="true"></i> Escudo</ControlLabel>
<ControlLabel><i className="fa fa-shield" aria-hidden="true"></i> Shields</ControlLabel>
<FormControl
type="text"
value={this.state.shield}
onChange={(e) => this.handleChange('shield', e.target.value)} />
</Col>
<Col sm={2} md={2}>
<ControlLabel>Precio</ControlLabel>
<ControlLabel>Price</ControlLabel>
<InputGroup>
<FormControl
type="text"
@ -74,7 +81,7 @@ class AddToken extends Component {
</InputGroup>
</Col>
<Col sm={4} md={4}>
<ControlLabel>Imagen</ControlLabel>
<ControlLabel>Image</ControlLabel>
<FormControl
type="file"
onChange={(e) => this.handleChange('fileToUpload', [e.target])} />
@ -85,7 +92,7 @@ class AddToken extends Component {
{
this.state.isSubmitting
? <Spinner name="wave" color="coral"/>
: <Button disabled={this.state.isSubmitting} onClick={(e) => this.handleClick(e)}>Crear</Button>
: <Button disabled={this.state.isSubmitting} onClick={(e) => this.handleClick(e)}>Create</Button>
}
</Col>
</Row>

View File

@ -6,11 +6,142 @@ The DApp will be running in http://localhost:8000 . This URL will present a simp
## Minting tokens
Each spaceship in this dapp is an ERC721 token, and as such, they have their own characteristics. Since they represent an spaceship (which could be used in a game), the attributes they will have are: lasers, shields, and energy, as well as an image.
The contract we will use to represent the spaceships is `SpaceshipToken` and it is located in the file: `contracts\SpaceshipToken.sol`.
> Embark will attempt to deploy all the contracts it finds in the `contracts` folder and their dependencies. For this project, we already pre-configured the contracts we will and will not deploy. You can read more about this in the [documentation](https://embark.status.im/docs/contracts.html#Specify-contract-file).
The contract we will use to represent the spaceships is `SpaceshipToken` and it is located in the file: `contracts\SpaceshipToken.sol`.
For minting the tokens we will use a combination of IPFS to store the image and attributes, and the function `mint(bytes _metadataHash, uint8 _energy, uint8 _lasers, uint8 _shield, uint _price) public onlyOwner`.
> Embark will attempt to deploy all the contracts it finds in the `contracts` folder and their dependencies. For this project, we already pre-configured the contracts we will and will not deploy. You can read more about this in the [documentation](https://embark.status.im/docs/contracts.html#Specify-contract-file).
Let's edit the file `app/js/components/addToken.js` which contains the form to add tokens:
For minting the tokens we will use a combination of IPFS to store the image and attributes, and the function `mint(bytes _metadataHash, uint8 _energy, uint8 _lasers, uint8 _shield, uint _price) public onlyOwner`.
Let's edit the file `app/js/components/addToken.js` which contains the form to add tokens:
#### Importing Embark and contracts
Before being able to interact with the EVM, and with IPFS, you need to import both EmbarkJS and the contract file:
```
import EmbarkJS from 'Embark/EmbarkJS';
import SpaceshipToken from 'Embark/contracts/SpaceshipToken';
```
### Adding functionality to the Create button
[IMAGE_HERE]
When you click on the 'Create' button, it will invoke the `handleClick(e)` method which doesn't have a useful implementation at the moment. Let's start by loading the image into IPFS.
#### Creating attributes
Each ERC721 may have an optional list of attributes or metadata that follows the "ERC721 Metadata JSON Schema". We'll start by creating a json object that contains this metadata using the values from the input fields:
```
let attributes = {
"name": "Spaceship",
"image": "",
"attributes": {
"energy": this.state.energy,
"lasers": this.state.lasers,
"shield": this.state.shield
}
}
```
Notice that the image attribute is empty because we still don't know the url of the image, we need to upload it to IPFS first.
#### Uploading image to IPFS
To upload the image file, we'll use `uploadFile(inputFile[])` function of Embark.Storage which returns a promise, and after the promise resolves, we'll add the image hash to our attributes object. We also will add a `catch` block to display any error we receive on the browser console, as well as a `finally` block for enabling the `Create` button after the file upload.
```
EmbarkJS.Storage.uploadFile(this.state.fileToUpload)
.then(fileHash => {
attributes.image = 'https://ipfs.io/ipfs/' + fileHash;
})
.catch((err) => {
console.error(err);
})
.finally(() => {
// Verify our object has a image attribute
console.log(attributes);
this.setState({isSubmitting: false});
});
```
#### Uploading attributes to IPFS
An ERC721 token that supports the metadata standard needs to return an URI with all the attribute info of the token when the `tokenURI(uint256 _tokenId)` function of the contract is invoked. Since IPFS already provides an URL for all the resources it has, we will proceed to store the attributes object in IPFS an return its URL when we invoke that function. We will start by modifying our then() implementation by calling the `saveText(text)` method of Embark.Storage. This also returns a promise, and we will deal with it in the next step.
```
EmbarkJS.Storage.uploadFile(this.state.fileToUpload)
.then(fileHash => {
attributes.image = 'https://ipfs.io/ipfs/' + fileHash;
// Saving our attributes object in IPFS
return EmbarkJS.Storage.saveText(JSON.stringify(attributes))
})
.then(attrHash => {
console.log(attrHash);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
this.setState({isSubmitting: false});
});
```
#### Using our contract to mint the new token
### Full implementation of the handleClick(e) function:
```
handleClick(e){
e.preventDefault();
const { mint } = SpaceshipToken.methods;
this.setState({isSubmitting: true});
let attributes = {
"name": "Spaceship",
"image": "",
"attributes": {
"energy": this.state.energy,
"lasers": this.state.lasers,
"shield": this.state.shield
}
}
let toSend;
EmbarkJS.Storage.uploadFile(this.state.fileToUpload)
.then(fileHash => {
attributes.image = 'https://ipfs.io/ipfs/' + fileHash;
return EmbarkJS.Storage.saveText(JSON.stringify(attributes))
})
.then(attrHash => {
toSend = mint(web3.utils.toHex(attrHash),
this.state.energy,
this.state.lasers,
this.state.shield,
web3.utils.toWei(this.state.price, "ether"));
return toSend.estimateGas();
})
.then(estimatedGas => {
return toSend.send({from: web3.eth.defaultAccount,
gas: estimatedGas + 1000});
})
.then(receipt => {
console.log(receipt);
this.setState({
fileToUpload: [],
energy: '',
lasers: '',
shield: '',
price: ''
});
this.props.loadShipsForSale();
return true;
})
.catch((err) => {
console.error(err);
})
.finally(() => {
this.setState({isSubmitting: false});
});
}
```