mirror of
https://github.com/status-im/discover-dapps.git
synced 2025-03-04 03:51:05 +00:00
Merge branch 'develop' of https://github.com/bakasura980/discover-dapps into develop
This commit is contained in:
commit
b566c248bf
@ -1,10 +1,21 @@
|
|||||||
{
|
{
|
||||||
"extends": ["airbnb", "plugin:prettier/recommended"],
|
"extends": [
|
||||||
"plugins": ["prettier"],
|
"airbnb",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": ["error", {
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
"endOfLine": "auto"
|
"endOfLine": "auto"
|
||||||
}]
|
}
|
||||||
|
],
|
||||||
|
"func-names": "off",
|
||||||
|
"eqeqeq": "off",
|
||||||
|
"class-methods-use-this": "off"
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
|
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
|
||||||
|
@ -2,70 +2,69 @@ module.exports = {
|
|||||||
// applies to all environments
|
// applies to all environments
|
||||||
default: {
|
default: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
rpcHost: "localhost", // HTTP-RPC server listening interface (default: "localhost")
|
rpcHost: 'localhost', // HTTP-RPC server listening interface (default: "localhost")
|
||||||
rpcPort: 8545, // HTTP-RPC server listening port (default: 8545)
|
rpcPort: 8545, // HTTP-RPC server listening port (default: 8545)
|
||||||
rpcCorsDomain: { // Domains from which to accept cross origin requests (browser enforced). This can also be a comma separated list
|
rpcCorsDomain: {
|
||||||
|
// Domains from which to accept cross origin requests (browser enforced). This can also be a comma separated list
|
||||||
auto: true, // When "auto" is true, Embark will automatically set the cors to the address of the webserver
|
auto: true, // When "auto" is true, Embark will automatically set the cors to the address of the webserver
|
||||||
additionalCors: [] // Additional CORS domains to add to the list. If "auto" is false, only those will be added
|
additionalCors: [], // Additional CORS domains to add to the list. If "auto" is false, only those will be added
|
||||||
},
|
},
|
||||||
wsRPC: true, // Enable the WS-RPC server
|
wsRPC: true, // Enable the WS-RPC server
|
||||||
wsOrigins: { // Same thing as "rpcCorsDomain", but for WS origins
|
wsOrigins: {
|
||||||
|
// Same thing as "rpcCorsDomain", but for WS origins
|
||||||
auto: true,
|
auto: true,
|
||||||
additionalCors: []
|
additionalCors: [],
|
||||||
},
|
},
|
||||||
wsHost: "localhost", // WS-RPC server listening interface (default: "localhost")
|
wsHost: 'localhost', // WS-RPC server listening interface (default: "localhost")
|
||||||
wsPort: 8546 // WS-RPC server listening port (default: 8546)
|
wsPort: 8546, // WS-RPC server listening port (default: 8546)
|
||||||
|
|
||||||
// Accounts to use as node accounts
|
// Accounts to use as node accounts
|
||||||
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
|
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
|
||||||
/*,accounts: [
|
// accounts: [
|
||||||
{
|
// {
|
||||||
nodeAccounts: true, // Accounts use for the node
|
// nodeAccounts: true, // Accounts use for the node
|
||||||
numAddresses: "1", // Number of addresses/accounts (defaults to 1)
|
// numAddresses: '1', // Number of addresses/accounts (defaults to 1)
|
||||||
password: "config/development/devpassword" // Password file for the accounts
|
// password: 'config/development/password', // Password file for the accounts
|
||||||
},
|
// },
|
||||||
// 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: "your_private_key"
|
// privateKeyFile: 'path/to/file', // Either a keystore or a list of keys, separated by , or ;
|
||||||
},
|
// password: 'passwordForTheKeystore', // Needed to decrypt the keystore file
|
||||||
{
|
// },
|
||||||
privateKeyFile: "path/to/file", // Either a keystore or a list of keys, separated by , or ;
|
// {
|
||||||
password: "passwordForTheKeystore" // Needed to decrypt the keystore file
|
// mnemonic: '12 word mnemonic',
|
||||||
},
|
// addressIndex: '0', // Optional. The index to start getting the address
|
||||||
{
|
// numAddresses: '1', // Optional. The number of addresses to get
|
||||||
mnemonic: "12 word mnemonic",
|
// hdpath: "m/44'/60'/0'/0/", // Optional. HD derivation path
|
||||||
addressIndex: "0", // Optional. The index to start getting the address
|
// },
|
||||||
numAddresses: "1", // Optional. The number of addresses to get
|
// ],
|
||||||
hdpath: "m/44'/60'/0'/0/" // Optional. HD derivation path
|
|
||||||
}
|
|
||||||
]*/
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// default environment, merges with the settings in default
|
// default environment, merges with the settings in default
|
||||||
// assumed to be the intended environment by `embark run` and `embark blockchain`
|
// assumed to be the intended environment by `embark run` and `embark blockchain`
|
||||||
development: {
|
development: {
|
||||||
ethereumClientName: "geth", // Can be geth or parity (default:geth)
|
ethereumClientName: 'geth', // Can be geth or parity (default:geth)
|
||||||
// ethereumClientBin: "geth", // path to the client binary. Useful if it is not in the global PATH
|
// ethereumClientBin: "geth", // path to the client binary. Useful if it is not in the global PATH
|
||||||
networkType: "custom", // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId
|
networkType: 'custom', // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId
|
||||||
networkId: 1337, // Network id used when networkType is custom
|
networkId: 1337, // Network id used when networkType is custom
|
||||||
isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled
|
isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled
|
||||||
datadir: ".embark/development/datadir", // Data directory for the databases and keystore (Geth 1.8.15 and Parity 2.0.4 can use the same base folder, till now they does not conflict with each other)
|
datadir: '.embark/development/datadir', // Data directory for the databases and keystore (Geth 1.8.15 and Parity 2.0.4 can use the same base folder, till now they does not conflict with each other)
|
||||||
mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed
|
mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed
|
||||||
nodiscover: true, // Disables the peer discovery mechanism (manual peer addition)
|
nodiscover: true, // Disables the peer discovery mechanism (manual peer addition)
|
||||||
maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25)
|
maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25)
|
||||||
proxy: true, // Proxy is used to present meaningful information about transactions
|
proxy: true, // Proxy is used to present meaningful information about transactions
|
||||||
targetGasLimit: 9000000, // Target gas limit sets the artificial target gas floor for the blocks to mine
|
targetGasLimit: 9000000, // Target gas limit sets the artificial target gas floor for the blocks to mine
|
||||||
simulatorBlocktime: 0 // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining.
|
simulatorBlocktime: 0, // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining.
|
||||||
},
|
},
|
||||||
|
|
||||||
// merges with the settings in default
|
// merges with the settings in default
|
||||||
// used with "embark run privatenet" and/or "embark blockchain privatenet"
|
// used with "embark run privatenet" and/or "embark blockchain privatenet"
|
||||||
privatenet: {
|
privatenet: {
|
||||||
networkType: "custom",
|
networkType: 'custom',
|
||||||
networkId: 1337,
|
networkId: 1337,
|
||||||
isDev: false,
|
isDev: false,
|
||||||
datadir: ".embark/privatenet/datadir",
|
datadir: '.embark/privatenet/datadir',
|
||||||
// -- mineWhenNeeded --
|
// -- mineWhenNeeded --
|
||||||
// This options is only valid when isDev is false.
|
// This options is only valid when isDev is false.
|
||||||
// Enabling this option uses our custom script to mine only when needed.
|
// Enabling this option uses our custom script to mine only when needed.
|
||||||
@ -80,27 +79,27 @@ module.exports = {
|
|||||||
// When enabled, geth uses POW to mine transactions as it would normally, instead of using POA as it does in --dev mode.
|
// When enabled, geth uses POW to mine transactions as it would normally, instead of using POA as it does in --dev mode.
|
||||||
// On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a
|
// On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a
|
||||||
// genesis block, which can be configured using the `genesisBlock` configuration option below.
|
// genesis block, which can be configured using the `genesisBlock` configuration option below.
|
||||||
genesisBlock: "config/privatenet/genesis.json", // Genesis block to initiate on first creation of a development node
|
genesisBlock: 'config/privatenet/genesis.json', // Genesis block to initiate on first creation of a development node
|
||||||
nodiscover: true,
|
nodiscover: true,
|
||||||
maxpeers: 0,
|
maxpeers: 0,
|
||||||
proxy: true,
|
proxy: true,
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
nodeAccounts: true,
|
nodeAccounts: true,
|
||||||
password: "config/privatenet/password" // Password to unlock the account
|
password: 'config/privatenet/password', // Password to unlock the account
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
targetGasLimit: 8000000,
|
targetGasLimit: 8000000,
|
||||||
simulatorBlocktime: 0
|
simulatorBlocktime: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
privateparitynet: {
|
privateparitynet: {
|
||||||
ethereumClientName: "parity",
|
ethereumClientName: 'parity',
|
||||||
networkType: "custom",
|
networkType: 'custom',
|
||||||
networkId: 1337,
|
networkId: 1337,
|
||||||
isDev: false,
|
isDev: false,
|
||||||
genesisBlock: "config/privatenet/genesis-parity.json", // Genesis block to initiate on first creation of a development node
|
genesisBlock: 'config/privatenet/genesis-parity.json', // Genesis block to initiate on first creation of a development node
|
||||||
datadir: ".embark/privatenet/datadir",
|
datadir: '.embark/privatenet/datadir',
|
||||||
mineWhenNeeded: false,
|
mineWhenNeeded: false,
|
||||||
nodiscover: true,
|
nodiscover: true,
|
||||||
maxpeers: 0,
|
maxpeers: 0,
|
||||||
@ -108,43 +107,43 @@ module.exports = {
|
|||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
nodeAccounts: true,
|
nodeAccounts: true,
|
||||||
password: "config/privatenet/password"
|
password: 'config/privatenet/password',
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
targetGasLimit: 8000000,
|
targetGasLimit: 8000000,
|
||||||
simulatorBlocktime: 0
|
simulatorBlocktime: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
// merges with the settings in default
|
// merges with the settings in default
|
||||||
// used with "embark run testnet" and/or "embark blockchain testnet"
|
// used with "embark run testnet" and/or "embark blockchain testnet"
|
||||||
testnet: {
|
testnet: {
|
||||||
networkType: "testnet",
|
networkType: 'testnet',
|
||||||
syncMode: "light",
|
syncMode: 'light',
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
nodeAccounts: true,
|
nodeAccounts: true,
|
||||||
password: "config/testnet/password"
|
password: 'config/testnet/password',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// merges with the settings in default
|
// merges with the settings in default
|
||||||
// used with "embark run livenet" and/or "embark blockchain livenet"
|
// used with "embark run livenet" and/or "embark blockchain livenet"
|
||||||
livenet: {
|
livenet: {
|
||||||
networkType: "livenet",
|
networkType: 'livenet',
|
||||||
syncMode: "light",
|
syncMode: 'light',
|
||||||
rpcCorsDomain: "http://localhost:8000",
|
rpcCorsDomain: 'http://localhost:8000',
|
||||||
wsOrigins: "http://localhost:8000",
|
wsOrigins: 'http://localhost:8000',
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
nodeAccounts: true,
|
nodeAccounts: true,
|
||||||
password: "config/livenet/password"
|
password: 'config/livenet/password',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
|
|
||||||
// you can name an environment with specific settings and then specify with
|
// you can name an environment with specific settings and then specify with
|
||||||
// "embark run custom_name" or "embark blockchain custom_name"
|
// "embark run custom_name" or "embark blockchain custom_name"
|
||||||
// custom_name: {
|
// custom_name: {
|
||||||
// }
|
// }
|
||||||
};
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
const wallet = require('./development/mnemonic')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// default applies to all environments
|
// default applies to all environments
|
||||||
default: {
|
default: {
|
||||||
// Blockchain node to deploy the contracts
|
// Blockchain node to deploy the contracts
|
||||||
deployment: {
|
deployment: {
|
||||||
host: "localhost", // Host of the blockchain node
|
host: 'localhost', // Host of the blockchain node
|
||||||
port: 8546, // Port of the blockchain node
|
port: 8545, // Port of the blockchain node
|
||||||
type: "ws" // Type of connection (ws or rpc),
|
type: 'rpc', // Type of connection (ws or rpc),
|
||||||
// Accounts to use instead of the default account to populate your wallet
|
// Accounts to use instead of the default account to populate your wallet
|
||||||
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
|
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
|
||||||
/* ,accounts: [
|
/* ,accounts: [
|
||||||
@ -28,12 +30,19 @@ module.exports = {
|
|||||||
"nodeAccounts": true // Uses the Ethereum node's accounts
|
"nodeAccounts": true // Uses the Ethereum node's accounts
|
||||||
}
|
}
|
||||||
] */
|
] */
|
||||||
|
|
||||||
|
accounts: [
|
||||||
|
{
|
||||||
|
mnemonic: wallet.mnemonic,
|
||||||
|
balance: '1534983463450 ether',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// order of connections the dapp should connect to
|
// order of connections the dapp should connect to
|
||||||
dappConnection: [
|
dappConnection: [
|
||||||
"$WEB3", // uses pre existing web3 object if available (e.g in Mist)
|
'$WEB3', // uses pre existing web3 object if available (e.g in Mist)
|
||||||
"ws://localhost:8546",
|
'ws://localhost:8546',
|
||||||
"http://localhost:8545"
|
'http://localhost:8545',
|
||||||
],
|
],
|
||||||
|
|
||||||
// Automatically call `ethereum.enable` if true.
|
// Automatically call `ethereum.enable` if true.
|
||||||
@ -41,7 +50,7 @@ module.exports = {
|
|||||||
// Default value is true.
|
// Default value is true.
|
||||||
// dappAutoEnable: true,
|
// dappAutoEnable: true,
|
||||||
|
|
||||||
gas: "auto",
|
gas: 'auto',
|
||||||
|
|
||||||
// Strategy for the deployment of the contracts:
|
// Strategy for the deployment of the contracts:
|
||||||
// - implicit will try to deploy all the contracts located inside the contracts directory
|
// - implicit will try to deploy all the contracts located inside the contracts directory
|
||||||
@ -51,42 +60,60 @@ module.exports = {
|
|||||||
// contracts section.
|
// contracts section.
|
||||||
// strategy: 'implicit',
|
// strategy: 'implicit',
|
||||||
|
|
||||||
|
// contracts: {
|
||||||
|
// Discover: {
|
||||||
|
// args: { _SNT: '0x744d70fdbe2ba4cf95131626614a1763df805b9e' },
|
||||||
|
// },
|
||||||
|
// MiniMeToken: { deploy: false },
|
||||||
|
// TestBancorFormula: { deploy: false },
|
||||||
|
// },
|
||||||
|
|
||||||
contracts: {
|
contracts: {
|
||||||
Discover: {
|
MiniMeToken: { deploy: false },
|
||||||
args: { _SNT: "0x744d70fdbe2ba4cf95131626614a1763df805b9e" }
|
TestBancorFormula: { deploy: false },
|
||||||
|
MiniMeTokenFactory: {},
|
||||||
|
SNT: {
|
||||||
|
instanceOf: 'MiniMeToken',
|
||||||
|
args: [
|
||||||
|
'$MiniMeTokenFactory',
|
||||||
|
'0x0000000000000000000000000000000000000000',
|
||||||
|
0,
|
||||||
|
'TestMiniMeToken',
|
||||||
|
18,
|
||||||
|
'SNT',
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Discover: {
|
||||||
|
args: ['$SNT'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
MiniMeToken: { "deploy": false },
|
|
||||||
TestBancorFormula: { "deploy": false }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// default environment, merges with the settings in default
|
// default environment, merges with the settings in default
|
||||||
// assumed to be the intended environment by `embark run`
|
// assumed to be the intended environment by `embark run`
|
||||||
development: {
|
development: {
|
||||||
dappConnection: [
|
dappConnection: [
|
||||||
"ws://localhost:8546",
|
'ws://localhost:8546',
|
||||||
"http://localhost:8545",
|
'http://localhost:8545',
|
||||||
"$WEB3" // uses pre existing web3 object if available (e.g in Mist)
|
'$WEB3', // uses pre existing web3 object if available (e.g in Mist)
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// merges with the settings in default
|
// merges with the settings in default
|
||||||
// used with "embark run privatenet"
|
// used with "embark run privatenet"
|
||||||
privatenet: {
|
privatenet: {},
|
||||||
},
|
|
||||||
|
|
||||||
// merges with the settings in default
|
// merges with the settings in default
|
||||||
// used with "embark run testnet"
|
// used with "embark run testnet"
|
||||||
testnet: {
|
testnet: {},
|
||||||
},
|
|
||||||
|
|
||||||
// merges with the settings in default
|
// merges with the settings in default
|
||||||
// used with "embark run livenet"
|
// used with "embark run livenet"
|
||||||
livenet: {
|
livenet: {},
|
||||||
},
|
|
||||||
|
|
||||||
// you can name an environment with specific settings and then specify with
|
// you can name an environment with specific settings and then specify with
|
||||||
// "embark run custom_name" or "embark blockchain custom_name"
|
// "embark run custom_name" or "embark blockchain custom_name"
|
||||||
// custom_name: {
|
// custom_name: {
|
||||||
// }
|
// }
|
||||||
};
|
}
|
||||||
|
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'
|
@ -42,7 +42,7 @@ contract Discover is ApproveAndCallFallBack, BancorFormula {
|
|||||||
|
|
||||||
Data[] public dapps;
|
Data[] public dapps;
|
||||||
mapping(bytes32 => uint) public id2index;
|
mapping(bytes32 => uint) public id2index;
|
||||||
mapping(bytes32 => bool) existingIDs;
|
mapping(bytes32 => bool) public existingIDs;
|
||||||
|
|
||||||
event DAppCreated(bytes32 indexed id, uint newEffectiveBalance);
|
event DAppCreated(bytes32 indexed id, uint newEffectiveBalance);
|
||||||
event Upvote(bytes32 indexed id, uint newEffectiveBalance);
|
event Upvote(bytes32 indexed id, uint newEffectiveBalance);
|
||||||
@ -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.
|
||||||
|
@ -22,5 +22,5 @@
|
|||||||
"optimize-runs": 200
|
"optimize-runs": 200
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"generationDir": "embarkArtifacts"
|
"generationDir": "src/embarkArtifacts"
|
||||||
}
|
}
|
@ -4,12 +4,14 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/runtime-corejs2": "^7.4.3",
|
||||||
"@trailofbits/embark-contract-info": "^1.0.0",
|
"@trailofbits/embark-contract-info": "^1.0.0",
|
||||||
"bignumber.js": "^8.1.1",
|
"bignumber.js": "^8.1.1",
|
||||||
"bs58": "^4.0.1",
|
"bs58": "^4.0.1",
|
||||||
"connected-react-router": "^6.3.2",
|
"connected-react-router": "^6.3.2",
|
||||||
"debounce": "^1.2.0",
|
"debounce": "^1.2.0",
|
||||||
"decimal.js": "^10.0.2",
|
"decimal.js": "^10.0.2",
|
||||||
|
"embark": "^4.0.2",
|
||||||
"embark-solium": "0.0.1",
|
"embark-solium": "0.0.1",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
|
28
src/common/blockchain/index.js
Normal file
28
src/common/blockchain/index.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import utils from './utils'
|
||||||
|
import SNTService from './services/contracts-services/snt-service/snt-service'
|
||||||
|
import DiscoverService from './services/contracts-services/discover-service/discover-service'
|
||||||
|
|
||||||
|
import BlockchainConfig from './services/config'
|
||||||
|
|
||||||
|
const init = function() {
|
||||||
|
try {
|
||||||
|
BlockchainConfig()
|
||||||
|
|
||||||
|
const sharedContext = {
|
||||||
|
account: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedContext.SNTService = new SNTService(sharedContext)
|
||||||
|
sharedContext.DiscoverService = new DiscoverService(sharedContext)
|
||||||
|
|
||||||
|
return {
|
||||||
|
SNTService: sharedContext.SNTService,
|
||||||
|
DiscoverService: sharedContext.DiscoverService,
|
||||||
|
utils,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { init, utils }
|
28
src/common/blockchain/ipfs/helpers.js
Normal file
28
src/common/blockchain/ipfs/helpers.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import bs58 from 'bs58'
|
||||||
|
|
||||||
|
export const base64ToBlob = base64Text => {
|
||||||
|
const byteString = atob(base64Text)
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBytes32FromIpfsHash = ipfsListing => {
|
||||||
|
const decodedHash = bs58
|
||||||
|
.decode(ipfsListing)
|
||||||
|
.slice(2)
|
||||||
|
.toString('hex')
|
||||||
|
return `0x${decodedHash}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getIpfsHashFromBytes32 = bytes32Hex => {
|
||||||
|
const hashHex = `1220${bytes32Hex.slice(2)}`
|
||||||
|
const hashBytes = Buffer.from(hashHex, 'hex')
|
||||||
|
const hashStr = bs58.encode(hashBytes)
|
||||||
|
return hashStr
|
||||||
|
}
|
63
src/common/blockchain/ipfs/index.js
Normal file
63
src/common/blockchain/ipfs/index.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import * as helpers from './helpers'
|
||||||
|
import EmbarkJSService from '../services/embark-service/embark-service'
|
||||||
|
|
||||||
|
const checkIPFSAvailability = async () => {
|
||||||
|
const isAvailable = await EmbarkJSService.Storage.isAvailable()
|
||||||
|
if (!isAvailable) {
|
||||||
|
throw new Error('IPFS Storage is unavailable')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadImage = async base64Image => {
|
||||||
|
const imageFile = [
|
||||||
|
{
|
||||||
|
files: [helpers.base64ToBlob(base64Image)],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return EmbarkJSService.Storage.uploadFile(imageFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadMetadata = async metadata => {
|
||||||
|
const hash = await EmbarkJSService.Storage.saveText(metadata)
|
||||||
|
return helpers.getBytes32FromIpfsHash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadDAppMetadata = async metadata => {
|
||||||
|
try {
|
||||||
|
await checkIPFSAvailability()
|
||||||
|
|
||||||
|
metadata.image = await uploadImage(metadata.image)
|
||||||
|
const uploadedMetadataHash = await uploadMetadata(JSON.stringify(metadata))
|
||||||
|
|
||||||
|
return uploadedMetadataHash
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Uploading DApp metadata to IPFS failed. Details: ${error.message}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const retrieveMetadata = async metadataBytes32 => {
|
||||||
|
const metadataHash = helpers.getIpfsHashFromBytes32(metadataBytes32)
|
||||||
|
return EmbarkJSService.Storage.get(metadataHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
const retrieveImageUrl = async imageHash => {
|
||||||
|
return EmbarkJSService.Storage.getUrl(imageHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const retrieveDAppMetadataByHash = async metadataBytes32 => {
|
||||||
|
try {
|
||||||
|
await checkIPFSAvailability()
|
||||||
|
|
||||||
|
const metadata = JSON.parse(await retrieveMetadata(metadataBytes32))
|
||||||
|
metadata.image = await retrieveImageUrl(metadata.image)
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Fetching metadata from IPFS failed. Details: ${error.message}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
9
src/common/blockchain/services/config.js
Normal file
9
src/common/blockchain/services/config.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* global web3 */
|
||||||
|
import Web3 from '../../../embarkArtifacts/modules/web3'
|
||||||
|
|
||||||
|
// Todo: Should be moved to .env
|
||||||
|
const RPC_URL = 'http://localhost:8545'
|
||||||
|
|
||||||
|
export default function() {
|
||||||
|
web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL))
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/* global web3 */
|
||||||
|
|
||||||
|
import EmbarkJSService from '../embark-service/embark-service'
|
||||||
|
|
||||||
|
class BlockchainService {
|
||||||
|
constructor(sharedContext, contract, Validator) {
|
||||||
|
this.contract = contract.address
|
||||||
|
contract.setProvider(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 EmbarkJSService.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
|
@ -0,0 +1,148 @@
|
|||||||
|
/* global web3 */
|
||||||
|
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'
|
||||||
|
|
||||||
|
class DiscoverService extends BlockchainService {
|
||||||
|
constructor(sharedContext) {
|
||||||
|
super(sharedContext, DiscoverContract, DiscoverValidator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// View methods
|
||||||
|
async upVoteEffect(id, amount) {
|
||||||
|
await this.validator.validateUpVoteEffect(id, amount)
|
||||||
|
|
||||||
|
return DiscoverContract.methods.upvoteEffect(id, amount).call()
|
||||||
|
}
|
||||||
|
|
||||||
|
async downVoteCost(id) {
|
||||||
|
const dapp = await this.getDAppById(id)
|
||||||
|
return DiscoverContract.methods.downvoteCost(dapp.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) {
|
||||||
|
let dapp
|
||||||
|
try {
|
||||||
|
const dappId = await DiscoverContract.methods.id2index(id).call()
|
||||||
|
dapp = await DiscoverContract.methods.dapps(dappId).call()
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Searching DApp does not exists')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dapp.id != id) {
|
||||||
|
throw new Error('Error fetching correct data from contract')
|
||||||
|
}
|
||||||
|
|
||||||
|
return dapp
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDAppDataById(id) {
|
||||||
|
const dapp = await this.getDAppById(id)
|
||||||
|
|
||||||
|
try {
|
||||||
|
dapp.metadata = await ipfsSDK.retrieveDAppMetadataByHash(dapp.metadata)
|
||||||
|
return dapp
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Error fetching correct data from IPFS')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async safeMax() {
|
||||||
|
return DiscoverContract.methods.safeMax().call()
|
||||||
|
}
|
||||||
|
|
||||||
|
async isDAppExists(id) {
|
||||||
|
return DiscoverContract.methods.existingIDs(id).call()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction methods
|
||||||
|
async createDApp(amount, metadata) {
|
||||||
|
const dappMetadata = JSON.parse(JSON.stringify(metadata))
|
||||||
|
const dappId = web3.utils.keccak256(JSON.stringify(dappMetadata))
|
||||||
|
|
||||||
|
await this.validator.validateDAppCreation(dappId, amount)
|
||||||
|
|
||||||
|
const uploadedMetadata = await ipfsSDK.uploadDAppMetadata(dappMetadata)
|
||||||
|
|
||||||
|
const callData = DiscoverContract.methods
|
||||||
|
.createDApp(dappId, amount, uploadedMetadata)
|
||||||
|
.encodeABI()
|
||||||
|
|
||||||
|
const createdTx = await this.sharedContext.SNTService.approveAndCall(
|
||||||
|
this.contract,
|
||||||
|
amount,
|
||||||
|
callData,
|
||||||
|
)
|
||||||
|
|
||||||
|
return { tx: createdTx, id: dappId }
|
||||||
|
}
|
||||||
|
|
||||||
|
async upVote(id, amount) {
|
||||||
|
await this.validator.validateUpVoting(id, amount)
|
||||||
|
|
||||||
|
const callData = DiscoverContract.methods.upvote(id, amount).encodeABI()
|
||||||
|
return this.sharedContext.SNTService.approveAndCall(
|
||||||
|
this.contract,
|
||||||
|
amount,
|
||||||
|
callData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async downVote(id, amount) {
|
||||||
|
await this.validator.validateDownVoting(id, amount)
|
||||||
|
|
||||||
|
const callData = DiscoverContract.methods.downvote(id, amount).encodeABI()
|
||||||
|
return this.sharedContext.SNTService.approveAndCall(
|
||||||
|
this.contract,
|
||||||
|
amount,
|
||||||
|
callData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async withdraw(id, amount) {
|
||||||
|
await super.__unlockServiceAccount()
|
||||||
|
await this.validator.validateWithdrawing(id, amount)
|
||||||
|
|
||||||
|
try {
|
||||||
|
return broadcastContractFn(
|
||||||
|
DiscoverContract.methods.withdraw(id, amount).send,
|
||||||
|
this.sharedContext.account,
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Transfer on withdraw failed. Details: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setMetadata(id, metadata) {
|
||||||
|
await super.__unlockServiceAccount()
|
||||||
|
await this.validator.validateMetadataSet(id)
|
||||||
|
|
||||||
|
const dappMetadata = JSON.parse(JSON.stringify(metadata))
|
||||||
|
const uploadedMetadata = await ipfsSDK.uploadDAppMetadata(dappMetadata)
|
||||||
|
|
||||||
|
try {
|
||||||
|
return broadcastContractFn(
|
||||||
|
DiscoverContract.methods.setMetadata(id, uploadedMetadata).send,
|
||||||
|
this.sharedContext.account,
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Uploading metadata failed. Details: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiscoverService
|
@ -0,0 +1,76 @@
|
|||||||
|
class DiscoverValidator {
|
||||||
|
constructor(service) {
|
||||||
|
this.service = service
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateUpVoteEffect(id, amount) {
|
||||||
|
const dapp = await this.service.getDAppById(id)
|
||||||
|
|
||||||
|
const safeMax = await this.service.safeMax()
|
||||||
|
if (Number(dapp.balance) + amount > safeMax) {
|
||||||
|
throw new Error(
|
||||||
|
`You cannot upvote by this much, try with a lower amount. Maximum upvote amount:
|
||||||
|
${Number(safeMax) - Number(dapp.balance)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateDAppCreation(id, amount) {
|
||||||
|
const dappExists = await this.service.isDAppExists(id)
|
||||||
|
if (dappExists) {
|
||||||
|
throw new Error('You must submit a unique ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new Error(
|
||||||
|
'You must spend some SNT to submit a ranking in order to avoid spam',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeMax = await this.service.safeMax()
|
||||||
|
if (amount > safeMax) {
|
||||||
|
throw new Error('You cannot stake more SNT than the ceiling dictates')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateUpVoting(id, amount) {
|
||||||
|
await this.validateUpVoteEffect(id, amount)
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new Error('You must send some SNT in order to upvote')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateDownVoting(id, amount) {
|
||||||
|
const dapp = await this.service.getDAppById(id)
|
||||||
|
|
||||||
|
const downVoteCost = await this.service.downVoteCost(dapp.id)
|
||||||
|
if (downVoteCost.c != amount) {
|
||||||
|
throw new Error('Incorrect amount: valid if effect on ranking is 1%')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateWithdrawing(id, amount) {
|
||||||
|
const dapp = await this.service.getDAppById(id)
|
||||||
|
|
||||||
|
if (dapp.developer.toLowerCase() != this.service.sharedContext.account) {
|
||||||
|
throw new Error('Only the developer can withdraw SNT staked on this data')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > dapp.available) {
|
||||||
|
throw new Error(
|
||||||
|
'You can only withdraw a percentage of the SNT staked, less what you have already received',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateMetadataSet(id) {
|
||||||
|
const dapp = await this.service.getDAppById(id)
|
||||||
|
|
||||||
|
if (dapp.developer.toLowerCase() != this.service.sharedContext.account) {
|
||||||
|
throw new Error('Only the developer can update the metadata')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiscoverValidator
|
@ -0,0 +1,7 @@
|
|||||||
|
export const broadcastContractFn = (contractMethod, account) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
contractMethod({ from: account }).on('transactionHash', hash => {
|
||||||
|
resolve(hash)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { broadcastContractFn } from '../helpers'
|
||||||
|
|
||||||
|
import BlockchainService from '../blockchain-service'
|
||||||
|
|
||||||
|
import SNTValidator from './snt-validator'
|
||||||
|
import SNTToken from '../../../../../embarkArtifacts/contracts/SNT'
|
||||||
|
|
||||||
|
class SNTService extends BlockchainService {
|
||||||
|
constructor(sharedContext) {
|
||||||
|
super(sharedContext, SNTToken, SNTValidator)
|
||||||
|
}
|
||||||
|
|
||||||
|
async allowance(from, to) {
|
||||||
|
return SNTToken.methods.allowance(from, to).call()
|
||||||
|
}
|
||||||
|
|
||||||
|
async balanceOf(account) {
|
||||||
|
return SNTToken.methods.balanceOf(account).call()
|
||||||
|
}
|
||||||
|
|
||||||
|
async controller() {
|
||||||
|
return SNTToken.methods.controller().call()
|
||||||
|
}
|
||||||
|
|
||||||
|
async transferable() {
|
||||||
|
return SNTToken.methods.transfersEnabled().call()
|
||||||
|
}
|
||||||
|
|
||||||
|
async approveAndCall(spender, amount, callData) {
|
||||||
|
await super.__unlockServiceAccount()
|
||||||
|
await this.validator.validateApproveAndCall(spender, amount)
|
||||||
|
|
||||||
|
return broadcastContractFn(
|
||||||
|
SNTToken.methods.approveAndCall(spender, amount, callData).send,
|
||||||
|
this.sharedContext.account,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for testing purpose only
|
||||||
|
async generateTokens() {
|
||||||
|
await super.__unlockServiceAccount()
|
||||||
|
|
||||||
|
await SNTToken.methods
|
||||||
|
.generateTokens(this.sharedContext.account, 10000)
|
||||||
|
.send({ from: this.sharedContext.account })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SNTService
|
@ -0,0 +1,35 @@
|
|||||||
|
class SNTValidator {
|
||||||
|
constructor(service) {
|
||||||
|
this.service = service
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateSNTTransferFrom(amount) {
|
||||||
|
const toBalance = await this.service.balanceOf(
|
||||||
|
this.service.sharedContext.account,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (toBalance < amount) {
|
||||||
|
throw new Error('Not enough SNT balance')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateApproveAndCall(spender, amount) {
|
||||||
|
const isTransferableToken = await this.service.transferable()
|
||||||
|
if (!isTransferableToken) {
|
||||||
|
throw new Error('Token is not transferable')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.validateSNTTransferFrom(amount)
|
||||||
|
|
||||||
|
const allowance = await this.service.allowance(
|
||||||
|
this.service.sharedContext.account,
|
||||||
|
spender,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (amount != 0 && allowance != 0) {
|
||||||
|
throw new Error('You have allowance already')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SNTValidator
|
@ -0,0 +1,14 @@
|
|||||||
|
import EmbarkJS from '../../../../embarkArtifacts/embarkjs'
|
||||||
|
|
||||||
|
class EmbarkService {
|
||||||
|
constructor() {
|
||||||
|
if (!EmbarkService.instance) {
|
||||||
|
EmbarkJS.Storage.setProvider('ipfs')
|
||||||
|
EmbarkService.instance = EmbarkJS
|
||||||
|
}
|
||||||
|
|
||||||
|
return EmbarkService.instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new EmbarkService()
|
20
src/common/blockchain/utils.js
Normal file
20
src/common/blockchain/utils.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/* global web3 */
|
||||||
|
|
||||||
|
const TRANSACTION_STATUSES = {
|
||||||
|
Failed: 0,
|
||||||
|
Successful: 1,
|
||||||
|
Pending: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getTxStatus: async txHash => {
|
||||||
|
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
||||||
|
if (txReceipt) {
|
||||||
|
return txReceipt.status
|
||||||
|
? TRANSACTION_STATUSES.Successful
|
||||||
|
: TRANSACTION_STATUSES.Failed
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRANSACTION_STATUSES.Pending
|
||||||
|
},
|
||||||
|
}
|
29
src/common/utils/number-formatter.js
Normal file
29
src/common/utils/number-formatter.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const ONE = '1000000000000000000'
|
||||||
|
|
||||||
|
const formatBigNumberToNumber = function(bigNumber) {
|
||||||
|
let stringifyedNumber = bigNumber.toString(10)
|
||||||
|
|
||||||
|
if (stringifyedNumber == '0') {
|
||||||
|
return stringifyedNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
let numberWholePartLength = 0
|
||||||
|
|
||||||
|
if (bigNumber.lt(ONE)) {
|
||||||
|
stringifyedNumber = stringifyedNumber.padStart(19, 0)
|
||||||
|
numberWholePartLength = 1
|
||||||
|
} else {
|
||||||
|
numberWholePartLength = bigNumber.div('1000000000000000000').toString(10)
|
||||||
|
.length
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${stringifyedNumber.substr(
|
||||||
|
0,
|
||||||
|
numberWholePartLength,
|
||||||
|
)}.${stringifyedNumber.substr(
|
||||||
|
numberWholePartLength,
|
||||||
|
stringifyedNumber.length,
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export default formatBigNumberToNumber
|
@ -10,6 +10,8 @@ import Submit from '../Submit'
|
|||||||
import Terms from '../Terms/Terms'
|
import Terms from '../Terms/Terms'
|
||||||
import TransactionStatus from '../TransactionStatus'
|
import TransactionStatus from '../TransactionStatus'
|
||||||
|
|
||||||
|
import Example from '../BlockchainExample'
|
||||||
|
|
||||||
class Router extends React.Component {
|
class Router extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { fetchHighestRanked, fetchRecentlyAdded } = this.props
|
const { fetchHighestRanked, fetchRecentlyAdded } = this.props
|
||||||
@ -25,6 +27,7 @@ class Router extends React.Component {
|
|||||||
<Route path="/all" component={Dapps} />
|
<Route path="/all" component={Dapps} />
|
||||||
<Route path="/recently-added" component={RecentlyAdded} />
|
<Route path="/recently-added" component={RecentlyAdded} />
|
||||||
<Route path="/terms" component={Terms} />
|
<Route path="/terms" component={Terms} />
|
||||||
|
<Route path="/example" component={Example} />
|
||||||
</Switch>,
|
</Switch>,
|
||||||
<Vote key={2} />,
|
<Vote key={2} />,
|
||||||
<Submit key={3} />,
|
<Submit key={3} />,
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import BlockchainExample from './BlockchainExample'
|
||||||
|
|
||||||
|
export default connect()(BlockchainExample)
|
107
src/modules/BlockchainExample/BlockchainExample.jsx
Normal file
107
src/modules/BlockchainExample/BlockchainExample.jsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import exampleImage from './dapp.image'
|
||||||
|
|
||||||
|
import BlockchainSDK from '../../common/blockchain'
|
||||||
|
|
||||||
|
const SERVICES = BlockchainSDK.init()
|
||||||
|
|
||||||
|
const DAPP_DATA = {
|
||||||
|
name: 'Test1',
|
||||||
|
url: 'https://www.test1.com/',
|
||||||
|
description: 'Decentralized Test DApp',
|
||||||
|
category: 'test',
|
||||||
|
dateCreated: Date.now(),
|
||||||
|
image: exampleImage.image,
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTimeout is used in order to wait a transaction to be mined
|
||||||
|
const getResult = async function(method, params) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
const result = await SERVICES.DiscoverService[method](...params)
|
||||||
|
resolve(result)
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Each transaction-function return tx hash
|
||||||
|
createDApp returns tx hash + dapp id
|
||||||
|
*/
|
||||||
|
class Example extends React.Component {
|
||||||
|
async getFullDApp(id) {
|
||||||
|
return getResult('getDAppDataById', [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDApp() {
|
||||||
|
await SERVICES.SNTService.generateTokens()
|
||||||
|
return SERVICES.DiscoverService.createDApp(10000, DAPP_DATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
async upvote(id) {
|
||||||
|
return getResult('upVote', [id, 1000])
|
||||||
|
}
|
||||||
|
|
||||||
|
async downvote(id, amount) {
|
||||||
|
return getResult('downVote', [id, amount])
|
||||||
|
}
|
||||||
|
|
||||||
|
async withdraw(id) {
|
||||||
|
return getResult('withdraw', [id, 500])
|
||||||
|
}
|
||||||
|
|
||||||
|
async upVoteEffect(id) {
|
||||||
|
return getResult('upVoteEffect', [id, 10000])
|
||||||
|
}
|
||||||
|
|
||||||
|
async downVoteCost(id) {
|
||||||
|
return getResult('downVoteCost', [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
async setMetadata(id) {
|
||||||
|
DAPP_DATA.category = 'updated'
|
||||||
|
return getResult('setMetadata', [id, DAPP_DATA])
|
||||||
|
}
|
||||||
|
|
||||||
|
async logDiscoverMethods() {
|
||||||
|
const createdDApp = await this.createDApp()
|
||||||
|
|
||||||
|
const dappData = await this.getFullDApp(createdDApp.id)
|
||||||
|
console.log(`Created DApp : ${JSON.stringify(dappData)}`)
|
||||||
|
|
||||||
|
document.getElementById('testImage').src = dappData.metadata.image
|
||||||
|
|
||||||
|
const downVote = await this.downVoteCost(createdDApp.id)
|
||||||
|
console.log(
|
||||||
|
`Downvote TX Hash : ${await this.downvote(createdDApp.id, downVote.c)}`,
|
||||||
|
)
|
||||||
|
console.log(`Upvote TX Hash : ${await this.upvote(createdDApp.id)}`)
|
||||||
|
console.log(`Withdraw TX Hash : ${await this.withdraw(createdDApp.id)}`)
|
||||||
|
console.log(
|
||||||
|
`UpvoteEffect Result : ${await this.upVoteEffect(createdDApp.id)}`,
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
`DownVoteCost Result : ${await this.downVoteCost(createdDApp.id)}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Set metadata TX Hash : ${await this.setMetadata(createdDApp.id)}`,
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
`Updated DApp : ${JSON.stringify(
|
||||||
|
await this.getFullDApp(createdDApp.id),
|
||||||
|
)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 onLoad={this.logDiscoverMethods()} />
|
||||||
|
<img id="testImage" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Example
|
3
src/modules/BlockchainExample/dapp.image.json
Normal file
3
src/modules/BlockchainExample/dapp.image.json
Normal file
File diff suppressed because one or more lines are too long
3
src/modules/BlockchainExample/index.js
Normal file
3
src/modules/BlockchainExample/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import BlockchainExample from './BlockchainExample.container'
|
||||||
|
|
||||||
|
export default BlockchainExample
|
@ -1,5 +1,5 @@
|
|||||||
/*global assert, web3 */
|
/*global assert, web3 */
|
||||||
const bs58 = require('bs58');
|
const bs58 = require('bs58')
|
||||||
|
|
||||||
// This has been tested with the real Ethereum network and Testrpc.
|
// This has been tested with the real Ethereum network and Testrpc.
|
||||||
// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
|
// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
|
||||||
@ -7,150 +7,170 @@ const bs58 = require('bs58');
|
|||||||
exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
|
exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
resolve(contractMethodCall());
|
resolve(contractMethodCall())
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
reject(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(tx => {
|
.then(tx => {
|
||||||
assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed");
|
assert.equal(
|
||||||
|
tx.receipt.gasUsed,
|
||||||
|
maxGasAvailable,
|
||||||
|
'tx successful, the max gas available was not consumed',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if ((String(error)).indexOf("invalid opcode") < 0 && (String(error)).indexOf("out of gas") < 0) {
|
if (
|
||||||
|
String(error).indexOf('invalid opcode') < 0 &&
|
||||||
|
String(error).indexOf('out of gas') < 0
|
||||||
|
) {
|
||||||
// Checks if the error is from TestRpc. If it is then ignore it.
|
// Checks if the error is from TestRpc. If it is then ignore it.
|
||||||
// Otherwise relay/throw the error produced by the above assertion.
|
// Otherwise relay/throw the error produced by the above assertion.
|
||||||
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
||||||
throw error;
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.listenForEvent = event => new Promise((resolve, reject) => {
|
exports.listenForEvent = event =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
event({}, (error, response) => {
|
event({}, (error, response) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
resolve(response.args);
|
resolve(response.args)
|
||||||
} else {
|
} else {
|
||||||
reject(error);
|
reject(error)
|
||||||
}
|
}
|
||||||
event.stopWatching();
|
event.stopWatching()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
exports.eventValues = (receipt, eventName) => {
|
exports.eventValues = (receipt, eventName) => {
|
||||||
if (receipt.events[eventName]) return receipt.events[eventName].returnValues;
|
if (receipt.events[eventName]) return receipt.events[eventName].returnValues
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.addressToBytes32 = (address) => {
|
exports.addressToBytes32 = address => {
|
||||||
const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2);
|
const stringed =
|
||||||
return "0x" + stringed.substring(stringed.length - 64, stringed.length);
|
'0000000000000000000000000000000000000000000000000000000000000000' +
|
||||||
};
|
address.slice(2)
|
||||||
|
return `0x${ stringed.substring(stringed.length - 64, stringed.length)}`;
|
||||||
|
}
|
||||||
|
|
||||||
// OpenZeppelin's expectThrow helper -
|
// OpenZeppelin's expectThrow helper -
|
||||||
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
|
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
|
||||||
exports.expectThrow = async promise => {
|
exports.expectThrow = async promise => {
|
||||||
try {
|
try {
|
||||||
await promise;
|
await promise
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// TODO: Check jump destination to destinguish between a throw
|
// TODO: Check jump destination to destinguish between a throw
|
||||||
// and an actual invalid jump.
|
// and an actual invalid jump.
|
||||||
const invalidOpcode = error.message.search('invalid opcode') >= 0;
|
const invalidOpcode = error.message.search('invalid opcode') >= 0
|
||||||
// TODO: When we contract A calls contract B, and B throws, instead
|
// TODO: When we contract A calls contract B, and B throws, instead
|
||||||
// of an 'invalid jump', we get an 'out of gas' error. How do
|
// of an 'invalid jump', we get an 'out of gas' error. How do
|
||||||
// we distinguish this from an actual out of gas event? (The
|
// we distinguish this from an actual out of gas event? (The
|
||||||
// testrpc log actually show an 'invalid jump' event.)
|
// testrpc log actually show an 'invalid jump' event.)
|
||||||
const outOfGas = error.message.search('out of gas') >= 0;
|
const outOfGas = error.message.search('out of gas') >= 0
|
||||||
const revert = error.message.search('revert') >= 0;
|
const revert = error.message.search('revert') >= 0
|
||||||
assert(
|
assert(
|
||||||
invalidOpcode || outOfGas || revert,
|
invalidOpcode || outOfGas || revert,
|
||||||
'Expected throw, got \'' + error + '\' instead',
|
`Expected throw, got '${ error }' instead`,
|
||||||
);
|
)
|
||||||
return;
|
return
|
||||||
|
}
|
||||||
|
assert.fail('Expected throw not received')
|
||||||
}
|
}
|
||||||
assert.fail('Expected throw not received');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
exports.assertJump = (error) => {
|
|
||||||
assert(error.message.search('VM Exception while processing transaction: revert') > -1, 'Revert should happen');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
exports.assertJump = error => {
|
||||||
|
assert(
|
||||||
|
error.message.search('VM Exception while processing transaction: revert') >
|
||||||
|
-1,
|
||||||
|
'Revert should happen',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function callbackToResolve(resolve, reject) {
|
function callbackToResolve(resolve, reject) {
|
||||||
return function(error, value) {
|
return function(error, value) {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error)
|
||||||
} else {
|
} else {
|
||||||
resolve(value);
|
resolve(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.promisify = (func) =>
|
exports.promisify = func => (...args) => {
|
||||||
(...args) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const callback = (err, data) => err ? reject(err) : resolve(data);
|
const callback = (err, data) => (err ? reject(err) : resolve(data))
|
||||||
func.apply(this, [...args, callback]);
|
func.apply(this, [...args, callback])
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.zeroAddress = '0x0000000000000000000000000000000000000000';
|
exports.zeroAddress = '0x0000000000000000000000000000000000000000'
|
||||||
exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
exports.zeroBytes32 =
|
||||||
|
'0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||||
exports.timeUnits = {
|
exports.timeUnits = {
|
||||||
seconds: 1,
|
seconds: 1,
|
||||||
minutes: 60,
|
minutes: 60,
|
||||||
hours: 60 * 60,
|
hours: 60 * 60,
|
||||||
days: 24 * 60 * 60,
|
days: 24 * 60 * 60,
|
||||||
weeks: 7 * 24 * 60 * 60,
|
weeks: 7 * 24 * 60 * 60,
|
||||||
years: 365 * 24 * 60 * 60
|
years: 365 * 24 * 60 * 60,
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.ensureException = function(error) {
|
exports.ensureException = function(error) {
|
||||||
assert(isException(error), error.toString());
|
assert(isException(error), error.toString())
|
||||||
};
|
}
|
||||||
|
|
||||||
function isException(error) {
|
function isException(error) {
|
||||||
let strError = error.toString();
|
const strError = error.toString()
|
||||||
return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
|
return (
|
||||||
|
strError.includes('invalid opcode') ||
|
||||||
|
strError.includes('invalid JUMP') ||
|
||||||
|
strError.includes('revert')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const evmMethod = (method, params = []) => {
|
const evmMethod = (method, params = []) => {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
const sendMethod = (web3.currentProvider.sendAsync) ? web3.currentProvider.sendAsync.bind(web3.currentProvider) : web3.currentProvider.send.bind(web3.currentProvider);
|
const sendMethod = web3.currentProvider.sendAsync
|
||||||
|
? web3.currentProvider.sendAsync.bind(web3.currentProvider)
|
||||||
|
: web3.currentProvider.send.bind(web3.currentProvider)
|
||||||
sendMethod(
|
sendMethod(
|
||||||
{
|
{
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
id: new Date().getSeconds()
|
id: new Date().getSeconds(),
|
||||||
},
|
},
|
||||||
(error, res) => {
|
(error, res) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error)
|
||||||
}
|
}
|
||||||
resolve(res.result);
|
resolve(res.result)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.evmSnapshot = async () => {
|
exports.evmSnapshot = async () => {
|
||||||
const result = await evmMethod("evm_snapshot");
|
const result = await evmMethod('evm_snapshot')
|
||||||
return web3.utils.hexToNumber(result);
|
return web3.utils.hexToNumber(result)
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.evmRevert = (id) => {
|
exports.evmRevert = id => {
|
||||||
const params = [id];
|
const params = [id]
|
||||||
return evmMethod("evm_revert", params);
|
return evmMethod('evm_revert', params)
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.increaseTime = async (amount) => {
|
|
||||||
await evmMethod("evm_increaseTime", [Number(amount)]);
|
|
||||||
await evmMethod("evm_mine");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
exports.increaseTime = async amount => {
|
||||||
|
await evmMethod('evm_increaseTime', [Number(amount)])
|
||||||
|
await evmMethod('evm_mine')
|
||||||
|
}
|
||||||
|
|
||||||
exports.getBytes32FromIpfsHash = ipfsListing => {
|
exports.getBytes32FromIpfsHash = ipfsListing => {
|
||||||
const decodedHash = bs58.decode(ipfsListing).slice(2).toString('hex')
|
const decodedHash = bs58
|
||||||
|
.decode(ipfsListing)
|
||||||
|
.slice(2)
|
||||||
|
.toString('hex')
|
||||||
return `0x${decodedHash}`
|
return `0x${decodedHash}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user