From c3920055bb6afbedb7517d41a9261e767f8dac85 Mon Sep 17 00:00:00 2001 From: Lyubomir Kiprov Date: Fri, 3 May 2019 18:50:54 +0300 Subject: [PATCH] Add IPFS data uploading --- .gitignore | 1 + config/blockchain.js | 5 -- config/contracts.js | 9 ++- config/development/mnemonic.js | 2 + contracts/Discover.sol | 9 +++ src/common/blockchain/blockchain-service.js | 24 ------- src/common/blockchain/index.js | 14 ++-- src/common/blockchain/ipfs/helpers.js | 11 +++ src/common/blockchain/ipfs/index.js | 50 ++++++++++++++ .../blockchain/sdk/blockchain-service.js | 31 +++++++++ src/common/blockchain/sdk/config.js | 8 +++ .../discover-services/discover-service.js | 67 +++++++++++++------ .../discover-services/discover-validator.js | 0 src/common/blockchain/sdk/helpers.js | 9 +++ .../{ => sdk}/snt-services/snt-service.js | 19 +++--- .../{ => sdk}/snt-services/snt-validator.js | 0 src/common/blockchain/utils.js | 18 +++++ .../BlockchainExample/BlockchainExample.jsx | 12 ++-- 18 files changed, 217 insertions(+), 72 deletions(-) create mode 100644 config/development/mnemonic.js delete mode 100644 src/common/blockchain/blockchain-service.js create mode 100644 src/common/blockchain/ipfs/helpers.js create mode 100644 src/common/blockchain/ipfs/index.js create mode 100644 src/common/blockchain/sdk/blockchain-service.js create mode 100644 src/common/blockchain/sdk/config.js rename src/common/blockchain/{ => sdk}/discover-services/discover-service.js (53%) rename src/common/blockchain/{ => sdk}/discover-services/discover-validator.js (100%) create mode 100644 src/common/blockchain/sdk/helpers.js rename src/common/blockchain/{ => sdk}/snt-services/snt-service.js (69%) rename src/common/blockchain/{ => sdk}/snt-services/snt-validator.js (100%) create mode 100644 src/common/blockchain/utils.js diff --git a/.gitignore b/.gitignore index 98f13c7..0e97b3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .embark chains.json +config/development/mnemonic config/livenet/password config/production/password coverage diff --git a/config/blockchain.js b/config/blockchain.js index d1bce0d..a6944ec 100644 --- a/config/blockchain.js +++ b/config/blockchain.js @@ -29,11 +29,6 @@ module.exports = { // Below are additional accounts that will count as `nodeAccounts` in the `deployment` section of your contract config // Those will not be unlocked in the node itself // { - // privateKey: - // '0xEFA9DB87A755C9D2B96F77BBCB9EF06CBDDFC01DB1A5129CE2649F73E9C2739C', - // balance: '100 ether', - // }, - // { // privateKeyFile: 'path/to/file', // Either a keystore or a list of keys, separated by , or ; // password: 'passwordForTheKeystore', // Needed to decrypt the keystore file // }, diff --git a/config/contracts.js b/config/contracts.js index c2d1c59..8a06264 100644 --- a/config/contracts.js +++ b/config/contracts.js @@ -1,3 +1,5 @@ +const wallet = require('./development/mnemonic') + module.exports = { // default applies to all environments default: { @@ -31,9 +33,8 @@ module.exports = { accounts: [ { - privateKey: - '0xEFA9DB87A755C9D2B96F77BBCB9EF06CBDDFC01DB1A5129CE2649F73E9C2739C', - balance: '100 ether', + mnemonic: wallet.mnemonic, + balance: '1534983463450 ether', }, ], }, @@ -72,7 +73,6 @@ module.exports = { TestBancorFormula: { deploy: false }, MiniMeTokenFactory: {}, SNT: { - from: '0x68C864373C6631984B646453138557A81224ACf6', instanceOf: 'MiniMeToken', args: [ '$MiniMeTokenFactory', @@ -85,7 +85,6 @@ module.exports = { ], }, Discover: { - from: '0x68C864373C6631984B646453138557A81224ACf6', args: ['$SNT'], }, }, diff --git a/config/development/mnemonic.js b/config/development/mnemonic.js new file mode 100644 index 0000000..45c9c58 --- /dev/null +++ b/config/development/mnemonic.js @@ -0,0 +1,2 @@ +module.exports.mnemonic = + 'artefact rebuild liquid honey sport clean candy motor cereal job gap series' diff --git a/contracts/Discover.sol b/contracts/Discover.sol index 1f57aeb..9207402 100644 --- a/contracts/Discover.sol +++ b/contracts/Discover.sol @@ -237,6 +237,15 @@ contract Discover is ApproveAndCallFallBack, BancorFormula { return (mEBalance.sub(d.effectiveBalance)); } + + /** + * @dev Used in UI in order to fetch all dapps + * @return dapps count + */ + function getDAppsCount() external view returns(uint) { + return dapps.length; + } + /** * @dev Downvotes always remove 1% of the current ranking. * @param _id bytes32 unique identifier. diff --git a/src/common/blockchain/blockchain-service.js b/src/common/blockchain/blockchain-service.js deleted file mode 100644 index 8e05ac7..0000000 --- a/src/common/blockchain/blockchain-service.js +++ /dev/null @@ -1,24 +0,0 @@ -import EmbarkJS from '../../embarkArtifacts/embarkjs' - -class BlockchainService { - constructor(sharedContext, contractAddress, Validator) { - this.contract = contractAddress - this.sharedContext = sharedContext - this.validator = new Validator(this) - } - - async __unlockServiceAccount() { - try { - const accounts = await EmbarkJS.Blockchain.Providers.web3.getAccounts() - if (accounts.length > 0) { - this.sharedContext.account = accounts[0] - } - - this.sharedContext.account = (await EmbarkJS.enableEthereum())[0] - } catch (error) { - throw new Error('Could not unlock an account or web3 is missing') - } - } -} - -export default BlockchainService diff --git a/src/common/blockchain/index.js b/src/common/blockchain/index.js index 800d559..ebaa71e 100644 --- a/src/common/blockchain/index.js +++ b/src/common/blockchain/index.js @@ -1,8 +1,13 @@ -import SNTService from './snt-services/snt-service' -import DiscoverService from './discover-services/discover-service' +import utils from './utils' +import SNTService from './sdk/snt-services/snt-service' +import DiscoverService from './sdk/discover-services/discover-service' -const init = async function() { +import BlockchainConfig from './sdk/config' + +const init = function() { try { + BlockchainConfig() + const sharedContext = { account: '', } @@ -13,10 +18,11 @@ const init = async function() { return { SNTService: sharedContext.SNTService, DiscoverService: sharedContext.DiscoverService, + utils, } } catch (error) { throw new Error(error.message) } } -export default { init } +export default { init, utils } diff --git a/src/common/blockchain/ipfs/helpers.js b/src/common/blockchain/ipfs/helpers.js new file mode 100644 index 0000000..6550afe --- /dev/null +++ b/src/common/blockchain/ipfs/helpers.js @@ -0,0 +1,11 @@ +export const base64ToBlob = base64Text => { + const byteString = atob(base64Text.split(',')[1]) + + const arrayBuffer = new ArrayBuffer(byteString.length) + const uintArray = new Uint8Array(arrayBuffer) + for (let i = 0; i < byteString.length; i++) { + uintArray[i] = byteString.charCodeAt(i) + } + + return new Blob([arrayBuffer]) +} diff --git a/src/common/blockchain/ipfs/index.js b/src/common/blockchain/ipfs/index.js new file mode 100644 index 0000000..350380d --- /dev/null +++ b/src/common/blockchain/ipfs/index.js @@ -0,0 +1,50 @@ +import { base64ToBlob } from './helpers' + +// Todo: EmbarkJS -> setup it in init +// Todo: Should check for isAvailable +import EmbarkJS from '../../../embarkArtifacts/embarkjs' + +EmbarkJS.Storage.setProvider('ipfs') + +export const uploadMetadata = async metadata => { + try { + const hash = await EmbarkJS.Storage.saveText(metadata) + return hash + } catch (error) { + throw new Error( + `Uploading DApp metadata to IPFS failed. Details: ${error.message}`, + ) + } +} + +// Todo: should convert base64 image into binary data in order to upload it on IPFS +export const uploadImage = async base64Image => { + try { + const imageFile = [ + { + files: [base64ToBlob(base64Image)], + }, + ] + const hash = await EmbarkJS.Storage.uploadFile(imageFile) + return hash + } catch (error) { + throw new Error( + `Uploading DApp image to IPFS failed. Details: ${error.message}`, + ) + } +} + +export const retrieveMetadata = async metadataHash => { + try { + const metadata = await EmbarkJS.Storage.get(metadataHash) + return metadata + } catch (error) { + throw new Error( + `Fetching metadata from IPFS failed. Details: ${error.message}`, + ) + } +} + +export const retrieveImageUrl = async imageHash => { + return EmbarkJS.Storage.getUrl(imageHash) +} diff --git a/src/common/blockchain/sdk/blockchain-service.js b/src/common/blockchain/sdk/blockchain-service.js new file mode 100644 index 0000000..5f4687f --- /dev/null +++ b/src/common/blockchain/sdk/blockchain-service.js @@ -0,0 +1,31 @@ +import EmbarkJS from '../../../embarkArtifacts/embarkjs' + +class BlockchainService { + constructor(sharedContext, contract, Validator) { + this.contract = contract.address + contract.setProvider(global.web3.currentProvider) + + this.sharedContext = sharedContext + this.validator = new Validator(this) + } + + async __unlockServiceAccount() { + const accounts = await EmbarkJS.Blockchain.Providers.web3.getAccounts() + // if (accounts.length > 0) { + this.sharedContext.account = accounts[0] + // } else { + // const provider = global.web3.currentProvider + // Check for undefined + // console.log(await global.web3.eth.getAccounts()) + // const accounts = await EmbarkJS.enableEthereum() + // if (accounts) { + // this.sharedContext.account = accounts[0] + // } + // global.web3.setProvider(provider) + // } + + // throw new Error('Could not unlock an account or web3 is missing') + } +} + +export default BlockchainService diff --git a/src/common/blockchain/sdk/config.js b/src/common/blockchain/sdk/config.js new file mode 100644 index 0000000..c244753 --- /dev/null +++ b/src/common/blockchain/sdk/config.js @@ -0,0 +1,8 @@ +import Web3 from '../../../embarkArtifacts/modules/web3' + +// Should be moved to .env +const RPC_URL = 'http://localhost:8545' + +export default function() { + global.web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL)) +} diff --git a/src/common/blockchain/discover-services/discover-service.js b/src/common/blockchain/sdk/discover-services/discover-service.js similarity index 53% rename from src/common/blockchain/discover-services/discover-service.js rename to src/common/blockchain/sdk/discover-services/discover-service.js index 3953e25..25c314f 100644 --- a/src/common/blockchain/discover-services/discover-service.js +++ b/src/common/blockchain/sdk/discover-services/discover-service.js @@ -1,19 +1,17 @@ +import broadcastContractFn from '../helpers' + +import * as ipfsSDK from '../../ipfs' import BlockchainService from '../blockchain-service' import DiscoverValidator from './discover-validator' -import DiscoverContract from '../../../embarkArtifacts/contracts/Discover' - -// TODO: Validators ? - YUP -// TODO: check for unlocked account: If it is not -> request unlocking - YUP -// TODO: Make transfer failed an Error object ? +import DiscoverContract from '../../../../embarkArtifacts/contracts/Discover' class DiscoverService extends BlockchainService { constructor(sharedContext) { - super(sharedContext, DiscoverContract.address, DiscoverValidator) + super(sharedContext, DiscoverContract, DiscoverValidator) } // TODO: Amount -> string/bigInt/number ? - // TODO: Maybe we can get id from a DApp name ? // TODO: formatBigNumberToNumber // View methods @@ -31,6 +29,16 @@ class DiscoverService extends BlockchainService { return DiscoverContract.methods.upvoteEffect(id).call() } + // Todo: Should be implemented + // async getDApps() { + // const dapps = [] + // const dappsCount = await DiscoverContract.methods.getDAppsCount().call() + + // for (let i = 0; i < dappsCount; i++) { + // const dapp = await DiscoverContract.methods.dapps(i).call() + // } + // } + async getDAppById(id) { try { const dappId = await DiscoverContract.methods.id2index(id).call() @@ -51,14 +59,22 @@ class DiscoverService extends BlockchainService { } // Transaction methods - async createDApp(id, amount, metadata) { - await this.validator.validateDAppCreation(id, amount) + async createDApp(amount, metadata) { + const dappMetadata = JSON.parse(JSON.stringify(metadata)) + const dappId = global.web3.keccak256(JSON.stringify(dappMetadata)) + + await this.validator.validateDAppCreation(dappId, amount) + + dappMetadata.image = await ipfsSDK.uploadImage(dappMetadata.image) + const metadataHash = await ipfsSDK.uploadMetadata( + JSON.stringify(dappMetadata), + ) const callData = DiscoverContract.methods - .createDApp(id, amount, metadata) + .createDApp(dappId, amount, metadataHash) .encodeABI() - await this.sharedContext.SNTService.approveAndCall( + return this.sharedContext.SNTService.approveAndCall( this.contract, amount, callData, @@ -69,7 +85,7 @@ class DiscoverService extends BlockchainService { await this.validator.validateUpVoting(id, amount) const callData = DiscoverContract.methods.upvote(id, amount).encodeABI() - await this.sharedContext.SNTService.approveAndCall( + return this.sharedContext.SNTService.approveAndCall( this.contract, amount, callData, @@ -80,7 +96,7 @@ class DiscoverService extends BlockchainService { await this.validator.validateDownVoting(id, amount) const callData = DiscoverContract.methods.downvote(id, amount).encodeABI() - await this.sharedContext.SNTService.approveAndCall( + return this.sharedContext.SNTService.approveAndCall( this.contract, amount, callData, @@ -88,25 +104,32 @@ class DiscoverService extends BlockchainService { } async withdraw(id, amount) { - await super.__unlockServiceAccount(this.service) + await super.__unlockServiceAccount() await this.validator.validateWithdrawing(id, amount) try { - await DiscoverContract.methods - .withdraw(id, amount) - .send({ from: this.sharedContext.account }) + return broadcastContractFn( + DiscoverContract.methods.withdraw(id, amount).send, + this.sharedContext.account, + ) } catch (error) { - throw new Error('Transfer on withdraw failed') + throw new Error(`Transfer on withdraw failed. Details: ${error.message}`) } } + // Todo: Should we upload the metadata to IPFS async setMetadata(id, metadata) { - await super.__unlockServiceAccount(this.service) + await super.__unlockServiceAccount() await this.validator.validateMetadataSet(id) - await DiscoverContract.methods - .setMetadata(id, metadata) - .send({ from: this.sharedContext.account }) + try { + return broadcastContractFn( + DiscoverContract.methods.setMetadata(id, metadata).send, + this.sharedContext.account, + ) + } catch (error) { + throw new Error(`Uploading metadata failed. Details: ${error.message}`) + } } } diff --git a/src/common/blockchain/discover-services/discover-validator.js b/src/common/blockchain/sdk/discover-services/discover-validator.js similarity index 100% rename from src/common/blockchain/discover-services/discover-validator.js rename to src/common/blockchain/sdk/discover-services/discover-validator.js diff --git a/src/common/blockchain/sdk/helpers.js b/src/common/blockchain/sdk/helpers.js new file mode 100644 index 0000000..e6ed32b --- /dev/null +++ b/src/common/blockchain/sdk/helpers.js @@ -0,0 +1,9 @@ +export default { + broadcastContractFn: (contractMethod, account) => { + return new Promise(resolve => { + contractMethod({ from: account }).on('transactionHash', hash => { + resolve(hash) + }) + }) + }, +} diff --git a/src/common/blockchain/snt-services/snt-service.js b/src/common/blockchain/sdk/snt-services/snt-service.js similarity index 69% rename from src/common/blockchain/snt-services/snt-service.js rename to src/common/blockchain/sdk/snt-services/snt-service.js index f1d5012..8c626b6 100644 --- a/src/common/blockchain/snt-services/snt-service.js +++ b/src/common/blockchain/sdk/snt-services/snt-service.js @@ -1,11 +1,13 @@ +import broadcastContractFn from '../helpers' + import BlockchainService from '../blockchain-service' import SNTValidator from './snt-validator' -import SNTToken from '../../../embarkArtifacts/contracts/SNT' +import SNTToken from '../../../../embarkArtifacts/contracts/SNT' class SNTService extends BlockchainService { constructor(sharedContext) { - super(sharedContext, SNTToken.address, SNTValidator) + super(sharedContext, SNTToken, SNTValidator) } async allowance(from, to) { @@ -25,21 +27,22 @@ class SNTService extends BlockchainService { } async approveAndCall(spender, amount, callData) { - await super.__unlockServiceAccount(this.service) + await super.__unlockServiceAccount() await this.validator.validateApproveAndCall(spender, amount) - await SNTToken.methods - .approveAndCall(spender, amount, callData) - .send({ from: this.sharedContext.account }) + return broadcastContractFn( + SNTToken.methods.approveAndCall(spender, amount, callData).send, + this.sharedContext.account, + ) } // This is for testing purpose only async generateTokens() { - await super.__unlockServiceAccount(this.service) + await super.__unlockServiceAccount() await SNTToken.methods .generateTokens(this.sharedContext.account, 10000) - .send() + .send({ from: this.sharedContext.account }) } } diff --git a/src/common/blockchain/snt-services/snt-validator.js b/src/common/blockchain/sdk/snt-services/snt-validator.js similarity index 100% rename from src/common/blockchain/snt-services/snt-validator.js rename to src/common/blockchain/sdk/snt-services/snt-validator.js diff --git a/src/common/blockchain/utils.js b/src/common/blockchain/utils.js new file mode 100644 index 0000000..c9851ca --- /dev/null +++ b/src/common/blockchain/utils.js @@ -0,0 +1,18 @@ +const TRANSACTION_STATUSES = { + Failed: 0, + Successful: 1, + Pending: 2, +} + +export default { + getTxStatus: async txHash => { + const txReceipt = await global.web3.eth.getTransactionReceipt(txHash) + if (txReceipt) { + return txReceipt.status + ? TRANSACTION_STATUSES.Successful + : TRANSACTION_STATUSES.Failed + } + + return TRANSACTION_STATUSES.Pending + }, +} diff --git a/src/modules/BlockchainExample/BlockchainExample.jsx b/src/modules/BlockchainExample/BlockchainExample.jsx index b4fdbc9..848aa07 100644 --- a/src/modules/BlockchainExample/BlockchainExample.jsx +++ b/src/modules/BlockchainExample/BlockchainExample.jsx @@ -3,17 +3,21 @@ import BlockchainSDK from '../../common/blockchain' class Example extends React.Component { async logDiscoverMethod() { - const services = await BlockchainSDK.init() - - console.log(await services.SNTService.controller()) + // const services = await BlockchainSDK.init() + // console.log(await services.SNTService.controller()) // await services.SNTService.generateTokens() // await services.DiscoverService.createDApp('0x2', 10000, '0x2') // console.log(await services.DiscoverService.getDAppById('0x2')) } render() { - return

+ return ( +
+

+

+ ) } } export default Example +// QmZGzoAEEZoFP9jYXoVfhkDqXHxVrFCSMxSU8eGQpcDNHw