Translations and more documentation
This commit is contained in:
parent
68b22a8621
commit
96fb57eae4
|
@ -23,17 +23,16 @@ class Ship extends Component {
|
|||
}
|
||||
|
||||
componentDidMount(){
|
||||
// TODO: cuando se carga el componente se deben buscar los atributos del token
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
this._loadAttributes();
|
||||
// ============== END: Function implementation here ================ //
|
||||
}
|
||||
|
||||
_loadAttributes(){
|
||||
// TODO: implementar carga de atributos aca
|
||||
// El unico atributo interesante es la imagen
|
||||
// Guardar el url en this.state.image
|
||||
|
||||
// Ejemplo
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
// TODO: the only attribute we are interested to load is the image
|
||||
this.setState({image: "./images/sampleShip.png"});
|
||||
// ============== END: Function implementation here ================ //
|
||||
}
|
||||
|
||||
showSellForm = (show) => {
|
||||
|
@ -52,13 +51,13 @@ class Ship extends Component {
|
|||
}
|
||||
|
||||
buyFromStore = () => {
|
||||
// TODO: comprar token recien creado
|
||||
// En props esta el atributo 'id' y 'price', que podemos usar para determinar el id del token
|
||||
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
// In props you can find the attributes 'id' and 'price' required for buying the token
|
||||
this.setState({isSubmitting: true});
|
||||
// Llamar la siguiente funcion para refrescar las listas
|
||||
this.props.onAction();
|
||||
this.props.onAction(); // This function needs to be called to reload the lists of spaceships
|
||||
this.setState({isSubmitting: false});
|
||||
// ============== END: Function implementation here ================ //
|
||||
|
||||
}
|
||||
|
||||
buyFromMarket = () => {
|
||||
|
@ -86,14 +85,14 @@ class Ship extends Component {
|
|||
<img src={image} />
|
||||
<br />
|
||||
<ul>
|
||||
<li title="Energia"><i className="fa fa-dashboard" aria-hidden="true"></i> {energy}</li>
|
||||
<li title="Energy"><i className="fa fa-dashboard" aria-hidden="true"></i> {energy}</li>
|
||||
<li title="Lasers"><i className="fa fa-crosshairs" aria-hidden="true"></i> {lasers}</li>
|
||||
<li title="Escudo"><i className="fa fa-shield" aria-hidden="true"></i> {shield}</li>
|
||||
<li title="Shield"><i className="fa fa-shield" aria-hidden="true"></i> {shield}</li>
|
||||
</ul>
|
||||
{ !wallet || marketplace
|
||||
? <Button disabled={isSubmitting} bsStyle="success" onClick={marketplace ? this.buyFromMarket : this.buyFromStore}>Comprar</Button>
|
||||
? <Button disabled={isSubmitting} bsStyle="success" onClick={marketplace ? this.buyFromMarket : this.buyFromStore}>Buy</Button>
|
||||
: (!showSellForm && salesEnabled
|
||||
? <Button bsStyle="success" className="hiddenOnLeave" onClick={e => { this.showSellForm(true) }}>Vender</Button>
|
||||
? <Button bsStyle="success" className="hiddenOnLeave" onClick={e => { this.showSellForm(true) }}>Sell</Button>
|
||||
: '')
|
||||
}
|
||||
|
||||
|
@ -106,8 +105,8 @@ class Ship extends Component {
|
|||
onChange={(e) => this.handleChange('sellPrice', e.target.value)} />
|
||||
<InputGroup.Addon>Ξ</InputGroup.Addon>
|
||||
</InputGroup>
|
||||
<Button disabled={isSubmitting} bsStyle="success" onClick={this.sellShip}>Vender</Button>
|
||||
<Button disabled={isSubmitting} onClick={e => { this.showSellForm(false) }}>Cancelar</Button>
|
||||
<Button disabled={isSubmitting} bsStyle="success" onClick={this.sellShip}>Sell</Button>
|
||||
<Button disabled={isSubmitting} onClick={e => { this.showSellForm(false) }}>Cancel</Button>
|
||||
</Fragment> : ''
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class ShipList extends Component {
|
|||
{ wallet ? <EnableSales isSubmitting={this.state.isSubmitting} handleChange={this.enableMarketplace} salesEnabled={this.state.salesEnabled} /> : ''}
|
||||
{ list.map((ship, i) => <Ship onAction={onAction} wallet={wallet} salesEnabled={salesEnabled} key={i} marketplace={marketplace} {...ship} />) }
|
||||
{ list.length == 0
|
||||
? <p>No hay naves disponibles</p>
|
||||
? <p>No ships available</p>
|
||||
: ''
|
||||
}
|
||||
</div>;
|
||||
|
|
|
@ -16,35 +16,38 @@ class WithdrawBalance extends Component {
|
|||
}
|
||||
|
||||
componentDidMount(){
|
||||
// TODO: Al cargar el componente, debemos obtener el balance
|
||||
// podemos hacerlo llamando a this._getBalance();
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
this._getBalance();
|
||||
// ============== END: Function implementation here ================ //
|
||||
}
|
||||
|
||||
_getBalance(){
|
||||
// TODO: implementar, el estado a actualizar es 'balance'
|
||||
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
// TODO: Update this.state.balance
|
||||
// ============== END: Function implementation here ================ //
|
||||
|
||||
}
|
||||
|
||||
handleClick(e){
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
e.preventDefault();
|
||||
|
||||
// TODO: este metodo se debe llamar al hacer click en retirar fondos
|
||||
// Debe extraer el balance total del contrato del token, y actualizar el UI
|
||||
// para mostrar que no hay balance disponible
|
||||
// TODO: this method needs to extract the balance from the contract
|
||||
// and update the UI to show the new balance
|
||||
|
||||
this.setState({isSubmitting: true});
|
||||
this._getBalance();
|
||||
this.setState({isSubmitting: false});
|
||||
// ============== END: Function implementation here ================ //
|
||||
}
|
||||
|
||||
render(){
|
||||
const { balance } = this.state;
|
||||
return <Grid>
|
||||
<h4>Fondos</h4>
|
||||
<Row>
|
||||
<Col sm={3} md={3}>
|
||||
Balance Disponible: <b>{ balance } Ξ</b>
|
||||
<Button onClick={(e) => this.handleClick(e)} disabled={balance == "0"}>Retirar</Button>
|
||||
Balance: <b>{ balance } Ξ</b>
|
||||
<Button onClick={(e) => this.handleClick(e)} disabled={balance == "0"}>Withdraw</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>;
|
||||
|
|
|
@ -44,18 +44,19 @@ class App extends Component {
|
|||
}
|
||||
|
||||
_loadMyShips = async () => {
|
||||
// TODO: aqui nos interesa cargar la lista de naves que posee el usuario
|
||||
// se espera un array de objetos en el estado myShips
|
||||
// cada objeto debe tener los siguientes atributos:
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
|
||||
// TODO: We want to load the lists of ships a user has
|
||||
// this.state.myShips expects an array of objects with these attributes:
|
||||
// {
|
||||
// id: "id del token",
|
||||
// energy: "Atributo del token",
|
||||
// lasers: "Atributo del token",
|
||||
// shield: "Atributo del token",
|
||||
// metadataHash: "Atributo del token",
|
||||
// id: "token Id",
|
||||
// energy: "token attribute",
|
||||
// lasers: "token attribute",
|
||||
// shield: "token attribute",
|
||||
// metadataHash: "token attribute",
|
||||
// }
|
||||
|
||||
// Ejemplo:
|
||||
// Example:
|
||||
const myShip = {
|
||||
id: 1,
|
||||
energy: 10,
|
||||
|
@ -64,24 +65,27 @@ class App extends Component {
|
|||
metadataHash: "METADATA"
|
||||
};
|
||||
const list = [ myShip ];
|
||||
this.setState({myShips: list.reverse()});
|
||||
this.setState({myShips: list});
|
||||
|
||||
// ============== END: Function implementation here ================ //
|
||||
}
|
||||
|
||||
_loadShipsForSale = async () => {
|
||||
// TODO: aqui nos interesa cargar la lista de naves a la venta cuando generamos el token
|
||||
// se espera un array de objetos en el estado shipsForSale
|
||||
// cada objeto debe tener los siguientes atributos:
|
||||
// ============== BEGIN: Function implementation here ================ //
|
||||
|
||||
// TODO: load the list of ships for sale
|
||||
// this.state.shipsForSale expects an array of objects with these attributes:
|
||||
// {
|
||||
// price: "precio de venta",
|
||||
// id: "id del token",
|
||||
// energy: "Atributo del token",
|
||||
// lasers: "Atributo del token",
|
||||
// shield: "Atributo del token",
|
||||
// metadataHash: "Atributo del token",
|
||||
// id: "token Id",
|
||||
// energy: "token attribute",
|
||||
// lasers: "token attribute",
|
||||
// shield: "token attribute",
|
||||
// metadataHash: "token attribute",
|
||||
// price: "token price"
|
||||
// }
|
||||
|
||||
let list = [];
|
||||
this.setState({shipsForSale: list.reverse()});
|
||||
this.setState({shipsForSale: list});
|
||||
}
|
||||
|
||||
_loadMarketPlace = async () => {
|
||||
|
|
|
@ -4,11 +4,10 @@ import "zeppelin-solidity/contracts/token/ERC721/ERC721Token.sol";
|
|||
import "zeppelin-solidity/contracts/ownership/Ownable.sol";
|
||||
|
||||
|
||||
/// @title Contrato base para Mexico Workshop
|
||||
/// @dev En Status tambien hablamos espanol ;)
|
||||
/// @title SpaceshipToken
|
||||
contract SpaceshipToken is ERC721Token("CryptoSpaceships", "CST"), Ownable {
|
||||
|
||||
// Estructura que representa nuestra nave spacial
|
||||
// Struct with spaceship attributes
|
||||
struct Spaceship {
|
||||
bytes metadataHash; // IPFS Hash
|
||||
uint8 energy;
|
||||
|
@ -16,19 +15,19 @@ contract SpaceshipToken is ERC721Token("CryptoSpaceships", "CST"), Ownable {
|
|||
uint8 shield;
|
||||
}
|
||||
|
||||
// Todas las naves que se han creado.
|
||||
// All the spaceships minted
|
||||
Spaceship[] public spaceships;
|
||||
|
||||
// Precio de las naves
|
||||
|
||||
mapping(uint => uint) public spaceshipPrices;
|
||||
uint[] public shipsForSale;
|
||||
mapping(uint => uint) public indexes; // shipId => shipForSale
|
||||
|
||||
/// @notice Crear un tocken
|
||||
/// @param _metadataHash IPFS hash que contiene la metadata del token
|
||||
/// @param _energy Atributo: Energia
|
||||
/// @param _lasers Atributo: Lasers
|
||||
/// @param _price Precio de venta del token
|
||||
/// @notice Mint a token
|
||||
/// @param _metadataHash IPFS hash with the token metadata
|
||||
/// @param _energy Attribute: Energy
|
||||
/// @param _lasers Attribute: Lasers
|
||||
/// @param _price Sale price in Wei
|
||||
function mint(bytes _metadataHash,
|
||||
uint8 _energy,
|
||||
uint8 _lasers,
|
||||
|
@ -51,64 +50,59 @@ contract SpaceshipToken is ERC721Token("CryptoSpaceships", "CST"), Ownable {
|
|||
shipsForSale.push(spaceshipId);
|
||||
indexes[spaceshipId] = shipsForSale.length - 1;
|
||||
|
||||
// _mint es una funcion del contrato ERC721Token que genera el NFT
|
||||
// El contrato sera dueno de las naves que se generen
|
||||
// _mint is a function part of ERC721Token that generates the NFT
|
||||
// The contract will own the newly minted tokens
|
||||
_mint(address(this), spaceshipId);
|
||||
}
|
||||
|
||||
/// @notice Obtener cantidad de naves a la venta
|
||||
/// @return Cantidad
|
||||
/// @notice Get number of spaceships for sale
|
||||
function shipsForSaleN() public view returns(uint) {
|
||||
return shipsForSale.length;
|
||||
}
|
||||
|
||||
/// @notice Comprar nave
|
||||
/// @param _spaceshipId Id del token a comprar
|
||||
/// @notice Buy spaceship
|
||||
/// @param _spaceshipId TokenId
|
||||
function buySpaceship(uint _spaceshipId) public payable {
|
||||
// Solo se pueden comprar las naves cuyo dueno sea el contrato
|
||||
// You can only buy the spaceships owned by this contract
|
||||
require(ownerOf(_spaceshipId) == address(this));
|
||||
|
||||
// Se debe enviar al menos el precio de la nave
|
||||
require(msg.value != 0);
|
||||
// Value sent should be at least the spaceship price
|
||||
require(msg.value >= spaceshipPrices[_spaceshipId]);
|
||||
|
||||
// Approvamos directamente para evitar tener que crear una transaccion extra
|
||||
// y luego enviamos la nave a quien origino la transaccion
|
||||
// We approve the transfer directly to avoid creating two trx
|
||||
// then we send the token to the sender
|
||||
tokenApprovals[_spaceshipId] = msg.sender;
|
||||
safeTransferFrom(address(this), msg.sender, _spaceshipId);
|
||||
|
||||
// La eliminamos de la lista para venta
|
||||
// Esto se ve un poco mas complicado de lo necesario,
|
||||
// Pero es para borrar elementos del arreglo de forma eficiente
|
||||
// Delete the token from the list of tokens for sale
|
||||
uint256 replacer = shipsForSale[shipsForSale.length - 1];
|
||||
uint256 pos = indexes[_spaceshipId];
|
||||
shipsForSale[pos] = replacer;
|
||||
indexes[replacer] = pos;
|
||||
shipsForSale.length--;
|
||||
|
||||
// Reembolsamos el sobrante
|
||||
uint refund = msg.value - spaceshipPrices[_spaceshipId];
|
||||
if (refund > 0)
|
||||
msg.sender.transfer(refund);
|
||||
}
|
||||
|
||||
/// @notice Retirar balance por compras hechas
|
||||
/// @notice Withdraw sales balance
|
||||
function withdrawBalance() public onlyOwner {
|
||||
owner.transfer(address(this).balance);
|
||||
}
|
||||
|
||||
/// @notice Obtener metadata
|
||||
/// @param _spaceshipId Id del token
|
||||
/// @return Direccion desde donde obtener la metadata
|
||||
/// @notice Get Metadata URI
|
||||
/// @param _spaceshipId TokenID
|
||||
/// @return IPFS URL of the metadata
|
||||
function tokenURI(uint256 _spaceshipId) public view returns (string) {
|
||||
Spaceship storage s = spaceships[_spaceshipId];
|
||||
return strConcat("https://ipfs.io/ipfs/", string(s.metadataHash));
|
||||
}
|
||||
|
||||
/// @notice Concatenar strings
|
||||
/// @dev La concatenacion por strings por ahora debe hacerse manual o usando librerias
|
||||
/// @param _a Primer string
|
||||
/// @param _b Segundo string
|
||||
/// @return String concatenado
|
||||
/// @notice Concatenate strings
|
||||
/// @param _a First string
|
||||
/// @param _b Second string
|
||||
/// @return _a+_b
|
||||
function strConcat(string _a, string _b) private returns (string) {
|
||||
bytes memory _ba = bytes(_a);
|
||||
bytes memory _bb = bytes(_b);
|
||||
|
|
|
@ -91,6 +91,8 @@ _loadShipsForSale = async () => {
|
|||
}
|
||||
```
|
||||
|
||||
[IMAGE_HERE]
|
||||
|
||||
You may have noticed that the ships that you create are displayed in the UI, however, they lack their image, and the prices are not formatted correctly. We need to work on the file `app/js/components/ship.js` for this.
|
||||
|
||||
As usual, start by importing `EmbarkJS` and `web3`, as well as our `SpaceshipToken`
|
||||
|
@ -101,33 +103,41 @@ import web3 from "Embark/web3";
|
|||
import SpaceshipToken from 'Embark/contracts/SpaceshipToken';
|
||||
```
|
||||
|
||||
The best place to load the image is when the component mounts. We will use `EmbarkJS.onReady` to invoke the `_loadAttributes` method
|
||||
|
||||
```
|
||||
componentDidMount(){
|
||||
EmbarkJS.onReady((err) => {
|
||||
this._loadAttributes();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The image URL is part of the token attributes stored in IPFS. We need to use the method `EmbarkJS.Storage.get` to obtain the content of the JSON object. We can access the IPFS hash via `this.props.metadataHash`. Remember we store this information in the contract as a `bytes` data type so we need to convert it back to string:
|
||||
|
||||
```
|
||||
_loadAttributes(){
|
||||
const metadataHash = web3.utils.toAscii(this.props.metadataHash);
|
||||
EmbarkJS.Storage.get(metadataHash)
|
||||
.then(content => {
|
||||
console.log(content);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The content returned is a string, so, before we can access the image attribute, we need to convert this string back to a JSON object using `JSON.parse(string)`. Once you do this, you are able to set the image state, and after reloading the DApp in the browser, the image should load.
|
||||
|
||||
|
||||
TODO:
|
||||
|
||||
|
||||
componentDidMount(){
|
||||
EmbarkJS.onReady((err) => {
|
||||
this._loadAttributes();
|
||||
});
|
||||
}
|
||||
|
||||
_loadAttributes(){
|
||||
// Cargar los atributos involucra leer la metadata
|
||||
EmbarkJS.Storage.get(web3.utils.toAscii(this.props.metadataHash))
|
||||
.then(content => {
|
||||
const jsonMetadata = JSON.parse(content);
|
||||
|
||||
// Podemos usar este metodo
|
||||
const _url = EmbarkJS.Storage.getUrl(jsonMetadata.imageHash);
|
||||
|
||||
// o leer el url que grabamos en la metadata
|
||||
// const _url = jsonMetadata.image
|
||||
|
||||
this.setState({image: _url})
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
_loadAttributes(){
|
||||
const metadataHash = web3.utils.toAscii(this.props.metadataHash);
|
||||
EmbarkJS.Storage.get(metadataHash)
|
||||
.then(content => {
|
||||
const jsonMetadata = JSON.parse(content);
|
||||
this.setState({image: jsonMetadata.image});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Finally, to format the price, on the `render` method, we will update the constant `formattedPrice` to use `web3.utils.fromWei` and convert the value from wei to ether as expected:
|
||||
|
||||
|
@ -348,7 +358,6 @@ componentDidMount(){
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
_isOwner(){
|
||||
SpaceshipToken.methods.owner()
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
|
||||
## Buying tokens from the marketplace
|
||||
|
||||
## Other functionality you could implement
|
||||
- Cancel sales
|
||||
- Transfer tokens
|
||||
- Trade tokens
|
||||
- Whisper chat
|
||||
## Other features
|
||||
There's still some missing functionality on this DApp that would be great to build, and probably make sense to include, such as: Allow the user to cancel sales, to tranfer tokens to other addresses, trading tokens offchain (maybe through Whisper?), etc. This tutorial covers most of the patterns required to implement them, so if you're looking for an extra challenge, you can have fun building these!
|
||||
|
||||
## Conclusion
|
||||
|
||||
As you can see, interacting with Ethereum and IPFS is easy using Embark, and lets you build truly decentralized applications fast! Are you interested in learning more or contributing to this framework? Visit http://embark.status.im or chat with us on [Gitter](https://embark.status.im/chat/)
|
Loading…
Reference in New Issue