mirror of
https://github.com/status-im/discover-dapps.git
synced 2025-03-03 19:40:50 +00:00
Add IPFS data uploading
This commit is contained in:
parent
a26091f59e
commit
c3920055bb
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
.embark
|
.embark
|
||||||
chains.json
|
chains.json
|
||||||
|
config/development/mnemonic
|
||||||
config/livenet/password
|
config/livenet/password
|
||||||
config/production/password
|
config/production/password
|
||||||
coverage
|
coverage
|
||||||
|
@ -29,11 +29,6 @@ module.exports = {
|
|||||||
// Below are additional accounts that will count as `nodeAccounts` in the `deployment` section of your contract config
|
// 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
|
// 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 ;
|
// privateKeyFile: 'path/to/file', // Either a keystore or a list of keys, separated by , or ;
|
||||||
// password: 'passwordForTheKeystore', // Needed to decrypt the keystore file
|
// password: 'passwordForTheKeystore', // Needed to decrypt the keystore file
|
||||||
// },
|
// },
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
const wallet = require('./development/mnemonic')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// default applies to all environments
|
// default applies to all environments
|
||||||
default: {
|
default: {
|
||||||
@ -31,9 +33,8 @@ module.exports = {
|
|||||||
|
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
privateKey:
|
mnemonic: wallet.mnemonic,
|
||||||
'0xEFA9DB87A755C9D2B96F77BBCB9EF06CBDDFC01DB1A5129CE2649F73E9C2739C',
|
balance: '1534983463450 ether',
|
||||||
balance: '100 ether',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -72,7 +73,6 @@ module.exports = {
|
|||||||
TestBancorFormula: { deploy: false },
|
TestBancorFormula: { deploy: false },
|
||||||
MiniMeTokenFactory: {},
|
MiniMeTokenFactory: {},
|
||||||
SNT: {
|
SNT: {
|
||||||
from: '0x68C864373C6631984B646453138557A81224ACf6',
|
|
||||||
instanceOf: 'MiniMeToken',
|
instanceOf: 'MiniMeToken',
|
||||||
args: [
|
args: [
|
||||||
'$MiniMeTokenFactory',
|
'$MiniMeTokenFactory',
|
||||||
@ -85,7 +85,6 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Discover: {
|
Discover: {
|
||||||
from: '0x68C864373C6631984B646453138557A81224ACf6',
|
|
||||||
args: ['$SNT'],
|
args: ['$SNT'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
2
config/development/mnemonic.js
Normal file
2
config/development/mnemonic.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
module.exports.mnemonic =
|
||||||
|
'artefact rebuild liquid honey sport clean candy motor cereal job gap series'
|
@ -237,6 +237,15 @@ contract Discover is ApproveAndCallFallBack, BancorFormula {
|
|||||||
return (mEBalance.sub(d.effectiveBalance));
|
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.
|
* @dev Downvotes always remove 1% of the current ranking.
|
||||||
* @param _id bytes32 unique identifier.
|
* @param _id bytes32 unique identifier.
|
||||||
|
@ -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
|
|
@ -1,8 +1,13 @@
|
|||||||
import SNTService from './snt-services/snt-service'
|
import utils from './utils'
|
||||||
import DiscoverService from './discover-services/discover-service'
|
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 {
|
try {
|
||||||
|
BlockchainConfig()
|
||||||
|
|
||||||
const sharedContext = {
|
const sharedContext = {
|
||||||
account: '',
|
account: '',
|
||||||
}
|
}
|
||||||
@ -13,10 +18,11 @@ const init = async function() {
|
|||||||
return {
|
return {
|
||||||
SNTService: sharedContext.SNTService,
|
SNTService: sharedContext.SNTService,
|
||||||
DiscoverService: sharedContext.DiscoverService,
|
DiscoverService: sharedContext.DiscoverService,
|
||||||
|
utils,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error.message)
|
throw new Error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { init }
|
export default { init, utils }
|
||||||
|
11
src/common/blockchain/ipfs/helpers.js
Normal file
11
src/common/blockchain/ipfs/helpers.js
Normal file
@ -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])
|
||||||
|
}
|
50
src/common/blockchain/ipfs/index.js
Normal file
50
src/common/blockchain/ipfs/index.js
Normal file
@ -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)
|
||||||
|
}
|
31
src/common/blockchain/sdk/blockchain-service.js
Normal file
31
src/common/blockchain/sdk/blockchain-service.js
Normal file
@ -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
|
8
src/common/blockchain/sdk/config.js
Normal file
8
src/common/blockchain/sdk/config.js
Normal file
@ -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))
|
||||||
|
}
|
@ -1,19 +1,17 @@
|
|||||||
|
import broadcastContractFn from '../helpers'
|
||||||
|
|
||||||
|
import * as ipfsSDK from '../../ipfs'
|
||||||
import BlockchainService from '../blockchain-service'
|
import BlockchainService from '../blockchain-service'
|
||||||
|
|
||||||
import DiscoverValidator from './discover-validator'
|
import DiscoverValidator from './discover-validator'
|
||||||
import DiscoverContract from '../../../embarkArtifacts/contracts/Discover'
|
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 ?
|
|
||||||
|
|
||||||
class DiscoverService extends BlockchainService {
|
class DiscoverService extends BlockchainService {
|
||||||
constructor(sharedContext) {
|
constructor(sharedContext) {
|
||||||
super(sharedContext, DiscoverContract.address, DiscoverValidator)
|
super(sharedContext, DiscoverContract, DiscoverValidator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Amount -> string/bigInt/number ?
|
// TODO: Amount -> string/bigInt/number ?
|
||||||
// TODO: Maybe we can get id from a DApp name ?
|
|
||||||
// TODO: formatBigNumberToNumber
|
// TODO: formatBigNumberToNumber
|
||||||
|
|
||||||
// View methods
|
// View methods
|
||||||
@ -31,6 +29,16 @@ class DiscoverService extends BlockchainService {
|
|||||||
return DiscoverContract.methods.upvoteEffect(id).call()
|
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) {
|
async getDAppById(id) {
|
||||||
try {
|
try {
|
||||||
const dappId = await DiscoverContract.methods.id2index(id).call()
|
const dappId = await DiscoverContract.methods.id2index(id).call()
|
||||||
@ -51,14 +59,22 @@ class DiscoverService extends BlockchainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transaction methods
|
// Transaction methods
|
||||||
async createDApp(id, amount, metadata) {
|
async createDApp(amount, metadata) {
|
||||||
await this.validator.validateDAppCreation(id, amount)
|
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
|
const callData = DiscoverContract.methods
|
||||||
.createDApp(id, amount, metadata)
|
.createDApp(dappId, amount, metadataHash)
|
||||||
.encodeABI()
|
.encodeABI()
|
||||||
|
|
||||||
await this.sharedContext.SNTService.approveAndCall(
|
return this.sharedContext.SNTService.approveAndCall(
|
||||||
this.contract,
|
this.contract,
|
||||||
amount,
|
amount,
|
||||||
callData,
|
callData,
|
||||||
@ -69,7 +85,7 @@ class DiscoverService extends BlockchainService {
|
|||||||
await this.validator.validateUpVoting(id, amount)
|
await this.validator.validateUpVoting(id, amount)
|
||||||
|
|
||||||
const callData = DiscoverContract.methods.upvote(id, amount).encodeABI()
|
const callData = DiscoverContract.methods.upvote(id, amount).encodeABI()
|
||||||
await this.sharedContext.SNTService.approveAndCall(
|
return this.sharedContext.SNTService.approveAndCall(
|
||||||
this.contract,
|
this.contract,
|
||||||
amount,
|
amount,
|
||||||
callData,
|
callData,
|
||||||
@ -80,7 +96,7 @@ class DiscoverService extends BlockchainService {
|
|||||||
await this.validator.validateDownVoting(id, amount)
|
await this.validator.validateDownVoting(id, amount)
|
||||||
|
|
||||||
const callData = DiscoverContract.methods.downvote(id, amount).encodeABI()
|
const callData = DiscoverContract.methods.downvote(id, amount).encodeABI()
|
||||||
await this.sharedContext.SNTService.approveAndCall(
|
return this.sharedContext.SNTService.approveAndCall(
|
||||||
this.contract,
|
this.contract,
|
||||||
amount,
|
amount,
|
||||||
callData,
|
callData,
|
||||||
@ -88,25 +104,32 @@ class DiscoverService extends BlockchainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async withdraw(id, amount) {
|
async withdraw(id, amount) {
|
||||||
await super.__unlockServiceAccount(this.service)
|
await super.__unlockServiceAccount()
|
||||||
await this.validator.validateWithdrawing(id, amount)
|
await this.validator.validateWithdrawing(id, amount)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DiscoverContract.methods
|
return broadcastContractFn(
|
||||||
.withdraw(id, amount)
|
DiscoverContract.methods.withdraw(id, amount).send,
|
||||||
.send({ from: this.sharedContext.account })
|
this.sharedContext.account,
|
||||||
|
)
|
||||||
} catch (error) {
|
} 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) {
|
async setMetadata(id, metadata) {
|
||||||
await super.__unlockServiceAccount(this.service)
|
await super.__unlockServiceAccount()
|
||||||
await this.validator.validateMetadataSet(id)
|
await this.validator.validateMetadataSet(id)
|
||||||
|
|
||||||
await DiscoverContract.methods
|
try {
|
||||||
.setMetadata(id, metadata)
|
return broadcastContractFn(
|
||||||
.send({ from: this.sharedContext.account })
|
DiscoverContract.methods.setMetadata(id, metadata).send,
|
||||||
|
this.sharedContext.account,
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Uploading metadata failed. Details: ${error.message}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
src/common/blockchain/sdk/helpers.js
Normal file
9
src/common/blockchain/sdk/helpers.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default {
|
||||||
|
broadcastContractFn: (contractMethod, account) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
contractMethod({ from: account }).on('transactionHash', hash => {
|
||||||
|
resolve(hash)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
|
import broadcastContractFn from '../helpers'
|
||||||
|
|
||||||
import BlockchainService from '../blockchain-service'
|
import BlockchainService from '../blockchain-service'
|
||||||
|
|
||||||
import SNTValidator from './snt-validator'
|
import SNTValidator from './snt-validator'
|
||||||
import SNTToken from '../../../embarkArtifacts/contracts/SNT'
|
import SNTToken from '../../../../embarkArtifacts/contracts/SNT'
|
||||||
|
|
||||||
class SNTService extends BlockchainService {
|
class SNTService extends BlockchainService {
|
||||||
constructor(sharedContext) {
|
constructor(sharedContext) {
|
||||||
super(sharedContext, SNTToken.address, SNTValidator)
|
super(sharedContext, SNTToken, SNTValidator)
|
||||||
}
|
}
|
||||||
|
|
||||||
async allowance(from, to) {
|
async allowance(from, to) {
|
||||||
@ -25,21 +27,22 @@ class SNTService extends BlockchainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async approveAndCall(spender, amount, callData) {
|
async approveAndCall(spender, amount, callData) {
|
||||||
await super.__unlockServiceAccount(this.service)
|
await super.__unlockServiceAccount()
|
||||||
await this.validator.validateApproveAndCall(spender, amount)
|
await this.validator.validateApproveAndCall(spender, amount)
|
||||||
|
|
||||||
await SNTToken.methods
|
return broadcastContractFn(
|
||||||
.approveAndCall(spender, amount, callData)
|
SNTToken.methods.approveAndCall(spender, amount, callData).send,
|
||||||
.send({ from: this.sharedContext.account })
|
this.sharedContext.account,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is for testing purpose only
|
// This is for testing purpose only
|
||||||
async generateTokens() {
|
async generateTokens() {
|
||||||
await super.__unlockServiceAccount(this.service)
|
await super.__unlockServiceAccount()
|
||||||
|
|
||||||
await SNTToken.methods
|
await SNTToken.methods
|
||||||
.generateTokens(this.sharedContext.account, 10000)
|
.generateTokens(this.sharedContext.account, 10000)
|
||||||
.send()
|
.send({ from: this.sharedContext.account })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/common/blockchain/utils.js
Normal file
18
src/common/blockchain/utils.js
Normal file
@ -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
|
||||||
|
},
|
||||||
|
}
|
@ -3,17 +3,21 @@ import BlockchainSDK from '../../common/blockchain'
|
|||||||
|
|
||||||
class Example extends React.Component {
|
class Example extends React.Component {
|
||||||
async logDiscoverMethod() {
|
async logDiscoverMethod() {
|
||||||
const services = await BlockchainSDK.init()
|
// const services = await BlockchainSDK.init()
|
||||||
|
// console.log(await services.SNTService.controller())
|
||||||
console.log(await services.SNTService.controller())
|
|
||||||
// await services.SNTService.generateTokens()
|
// await services.SNTService.generateTokens()
|
||||||
// await services.DiscoverService.createDApp('0x2', 10000, '0x2')
|
// await services.DiscoverService.createDApp('0x2', 10000, '0x2')
|
||||||
// console.log(await services.DiscoverService.getDAppById('0x2'))
|
// console.log(await services.DiscoverService.getDAppById('0x2'))
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <h1 onLoad={this.logDiscoverMethod()} />
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 onLoad={this.logDiscoverMethod()} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Example
|
export default Example
|
||||||
|
// QmZGzoAEEZoFP9jYXoVfhkDqXHxVrFCSMxSU8eGQpcDNHw
|
||||||
|
Loading…
x
Reference in New Issue
Block a user