diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index d921f2084..1fdafc237 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -822,9 +822,11 @@
"not-connected-message": "The application is not connected to a server.",
"dao-confirm-tally": "You are voting for {{voteValue}} on:
{{proposalName}}
Confirm vote in your blockchain wallet.
",
"dao-default-tally": "You are voting on
{{proposalName}}
Confirm vote in your blockchain wallet.
",
- "moloch-guild": "Guild.",
+ "moloch-guild": "Budget.",
"guild-total-shares": "Total Shares",
"guild-total-assets": "Total Assets",
"guild-share-value": "Value per Share",
- "guild-total-value": "Total Value"
+ "guild-total-value": "Total Value",
+ "guild-voting-address": "Voter",
+ "guild-voting-addresses": "Voters"
}
diff --git a/imports/api/collectives/Collectives.js b/imports/api/collectives/Collectives.js
index c9a128a92..28c752b6e 100644
--- a/imports/api/collectives/Collectives.js
+++ b/imports/api/collectives/Collectives.js
@@ -97,6 +97,51 @@ Schema.Menu = new SimpleSchema({
},
});
+Schema.Dataset = new SimpleSchema({
+ data: {
+ type: Array,
+ optional: true,
+ defaultValue: [],
+ },
+ 'data.$': {
+ type: Object,
+ optional: true,
+ },
+ 'data.$.t': {
+ type: Date,
+ optional: true,
+ },
+ 'data.$.x': {
+ type: Number,
+ optional: true,
+ decimal: true,
+ },
+ 'data.$.y': {
+ type: Number,
+ optional: true,
+ decimal: true,
+ },
+});
+
+Schema.Chart = new SimpleSchema({
+ guildLabel: {
+ type: String,
+ optional: true,
+ },
+ type: {
+ type: String,
+ optional: true,
+ },
+ dataset: {
+ type: [Schema.Dataset],
+ optional: true,
+ },
+ lastSyncedBlock: {
+ type: Number,
+ optional: true,
+ },
+});
+
Schema.CollectiveProfile = new SimpleSchema({
website: {
type: String,
@@ -111,6 +156,10 @@ Schema.CollectiveProfile = new SimpleSchema({
type: [Parameter],
optional: true,
},
+ chart: {
+ type: [Schema.Chart],
+ optional: true,
+ },
blockchain: {
type: Blockchain,
optional: true,
@@ -161,6 +210,10 @@ Schema.CollectiveProfile = new SimpleSchema({
type: Date,
optional: true,
},
+ lastSyncedBlock: {
+ type: Number,
+ optional: true,
+ },
});
diff --git a/imports/api/server/methods.js b/imports/api/server/methods.js
index 6f668c735..caa08e8b7 100644
--- a/imports/api/server/methods.js
+++ b/imports/api/server/methods.js
@@ -11,7 +11,7 @@ import { getTime } from '/imports/api/time';
import { logUser, log, defaults, gui } from '/lib/const';
import { stripHTML, urlDoctor, fixDBUrl } from '/lib/utils';
import { notifierHTML } from '/imports/api/notifier/notifierTemplate.js';
-import { computeDAOStats } from '/lib/dao';
+import { refreshDAOs } from '/lib/dao';
import { getLastTimestamp, getBlockHeight } from '/lib/web3';
import { Collectives } from '/imports/api/collectives/Collectives';
@@ -388,7 +388,7 @@ Meteor.methods({
Contracts.update({ _id: feed[i]._id }, { $set: { period: newPeriod } });
}
}
- computeDAOStats();
+ refreshDAOs();
},
async getBlock(collectives) {
diff --git a/imports/startup/both/modules/metamask.js b/imports/startup/both/modules/metamask.js
index 0e8ab8c7a..0f5d98702 100644
--- a/imports/startup/both/modules/metamask.js
+++ b/imports/startup/both/modules/metamask.js
@@ -382,47 +382,11 @@ const _hasRightToVote = async (memberAddress, proposalIndex, collectiveId) => {
*/
const _submitVote = async (proposalIndex, uintVote, contract, choice) => {
const res = await _callDAOMethod('submitVote', [proposalIndex, uintVote], choice.collectiveId, 'send', { from: Meteor.user().username });
+ console.log(res);
if (res) {
_pendingTransaction(Meteor.user().username, res, contract, choice);
displayModal(false, modal);
}
-
- /**
- const collective = Collectives.findOne({ _id: choice.collectiveId });
- const smartContracts = collective.profile.blockchain.smartContracts;
-
- const map = _getMethodMap(smartContracts, 'submitVote');
- const contractABI = JSON.parse(map.abi);
-
- const dao = await new web3.eth.Contract(contractABI, map.publicAddress);
-
- await dao.methods[`${'submitVote'}`](proposalIndex, uintVote).send({ from: Meteor.user().username }, (err, res) => {
- if (err) {
- let message;
- switch (err.code) {
- case -32603:
- message = TAPi18n.__('metamask-invalid-address');
- break;
- case 4001:
- default:
- message = TAPi18n.__('metamask-denied-signature');
- }
- displayModal(
- true,
- {
- icon: Meteor.settings.public.app.logo,
- title: TAPi18n.__('wallet'),
- message,
- cancel: TAPi18n.__('close'),
- alertMode: true,
- },
- );
- return err;
- }
- _pendingTransaction(Meteor.user().username, res, contract, choice);
- displayModal(false, modal);
- return res;
- });*/
};
/**
diff --git a/imports/ui/templates/components/collective/guild/guild.html b/imports/ui/templates/components/collective/guild/guild.html
index 968e6dbf4..32b2b7a3b 100644
--- a/imports/ui/templates/components/collective/guild/guild.html
+++ b/imports/ui/templates/components/collective/guild/guild.html
@@ -10,11 +10,20 @@
{{#unless ragequit}}
-
{{#if requiresClosing}}
{{#with closingData}}
{{> countdown}}
diff --git a/lib/const.js b/lib/const.js
index baed9cfaf..473181614 100644
--- a/lib/const.js
+++ b/lib/const.js
@@ -111,6 +111,7 @@ const _defaults = {
ROOT: 'ETH',
YES: 'YES',
NO: 'NO',
+ START_BLOCK: 5000000,
};
/**
diff --git a/lib/dao.js b/lib/dao.js
index 6ced969a3..0b9e4d02d 100644
--- a/lib/dao.js
+++ b/lib/dao.js
@@ -1,11 +1,12 @@
import { Meteor } from 'meteor/meteor';
import { Collectives } from '/imports/api/collectives/Collectives';
-import { getEvents, syncDAOGuilds } from '/lib/web3';
-import { log } from '/lib/const';
+import { getEvents, syncDAOGuilds, getBlockHeight } from '/lib/web3';
+import { log, defaults } from '/lib/const';
import { Contracts } from '/imports/api/contracts/Contracts';
import { Tokens } from '/imports/api/tokens/tokens';
const daoCollectives = [];
+let lastServerSyncedBlock = 0;
const _daoToCollective = (dao) => {
Collectives.insert(dao, (error, result) => {
@@ -99,45 +100,6 @@ const _setupTokens = async () => {
return Tokens.find().fetch();
};
-/**
-* @summary from collection of collectives, get all the events related to them on chain
-*/
-const _insertDAOEvents = async () => {
- let collectives;
- let noCollectives = true;
- while (noCollectives) {
- collectives = Collectives.find().fetch();
- if (collectives.length === 0) {
- noCollectives = true;
- } else {
- noCollectives = false;
- }
- }
- log(`[dao] Found a total of ${collectives.length} collectives to parse.`);
- const daoLogs = [];
- for (let i = 0; i < collectives.length; i += 1) {
- if (collectives[i].profile.blockchain) {
- log(`[dao] Processing ${collectives[i].name}...`);
- if (collectives[i].profile.blockchain.publicAddress && collectives[i].profile.blockchain.coin.code) {
- log(`[dao] Updating wallet of ${collectives[i].name}...`);
- // await updateWallet(collectives[i].profile.blockchain.publicAddress, collectives[i].profile.blockchain.coin.code);
- }
- if (collectives[i].profile.blockchain.smartContracts && collectives[i].profile.blockchain.smartContracts.length > 0) {
- log(`[dao] Reading smart contracts of ${collectives[i].name}...`);
- for (let k = 0; k < collectives[i].profile.blockchain.smartContracts.length; k += 1) {
- if (collectives[i].profile.blockchain.smartContracts[k].abi && !collectives[i].profile.blockchain.smartContracts[k].EIP) {
- await getEvents(collectives[i].profile.blockchain.smartContracts[k], collectives[i]._id).then((res) => {
- daoLogs.push(res);
- });
- }
- }
- }
- }
- }
-
- return daoLogs;
-};
-
/**
* @summary persists the latest stats of a DAO
*/
@@ -167,14 +129,46 @@ const _computeDAOStats = () => {
}
};
+/**
+* @summary dynamic refresh while app runs on latest activity per smart contract
+*/
+const _refreshDAOs = async () => {
+ let collectives;
+ let noCollectives = true;
+ while (noCollectives) {
+ collectives = Collectives.find().fetch();
+ if (collectives.length === 0) {
+ noCollectives = true;
+ } else {
+ noCollectives = false;
+ }
+ }
+ const currentBlock = await getBlockHeight();
+ if (lastServerSyncedBlock < currentBlock) {
+ log(`[dao] Refreshing DAO activity on block height ${currentBlock}...`);
+
+ let syncFrom;
+ for (let i = 0; i < collectives.length; i += 1) {
+ if (collectives[i].profile.blockchain && collectives[i].profile.blockchain.smartContracts && collectives[i].profile.blockchain.smartContracts.length > 0) {
+ for (let k = 0; k < collectives[i].profile.blockchain.smartContracts.length; k += 1) {
+ syncFrom = (collectives[i].profile.lastSyncedBlock) ? (collectives[i].profile.lastSyncedBlock + 1) : defaults.START_BLOCK;
+ if (syncFrom < currentBlock) {
+ await getEvents(collectives[i].profile.blockchain.smartContracts[k], collectives[i]._id, syncFrom, 'latest');
+ }
+ }
+ }
+ }
+ lastServerSyncedBlock = currentBlock;
+ }
+};
+
/**
* @summary setup DAOs on this server instance
*/
const _setupDAOs = async () => {
await _insertDAOs().then(async (res) => {
if (res) {
- await _insertDAOEvents();
- _computeDAOStats();
+ await _refreshDAOs();
await syncDAOGuilds();
}
});
@@ -190,3 +184,4 @@ if (Meteor.isServer) {
}
export const computeDAOStats = _computeDAOStats;
+export const refreshDAOs = _refreshDAOs;
diff --git a/lib/web3.js b/lib/web3.js
index 760026ece..ec94d0786 100644
--- a/lib/web3.js
+++ b/lib/web3.js
@@ -9,13 +9,14 @@ import { BigNumber } from 'bignumber.js';
import { migrateAddress, getContractObject, getTransactionObject, parseContent, getFinality } from '/lib/interpreter';
import { log, defaults } from '/lib/const';
+import { computeDAOStats } from '/lib/dao';
import erc20 from 'human-standard-token-abi';
import { Math } from 'core-js';
+
const Web3 = require('web3');
-const START_BLOCK = 5000000;
const precedentCache = [];
let web3;
@@ -163,7 +164,7 @@ const _getUserAddress = (res, collectiveId, role) => {
* @param {string} collectiveId this is being subscribed to
* @param {object} block with data from chain of this event
*/
-const _mirrorContractEvent = (event, map, state, collectiveId, block) => {
+const _mirrorContractEvent = (event, state, collectiveId, block) => {
log(`[web3] Mirroring blockchain event as contract action with collectiveId: ${collectiveId}...`);
// create users required for this contract
@@ -280,7 +281,7 @@ const _quickTally = (voter) => {
* @param {string} collectiveId this is being subscribed to
* @param {object} block with data from chain of this event
*/
-const _mirrorTransaction = (event, map, collectiveId, block) => {
+const _mirrorTransaction = (event, collectiveId, block) => {
// create users required for this transaction
const authorUsername = _getUserAddress(event, collectiveId, 'MEMBER');
const user = Meteor.users.findOne({ username: authorUsername });
@@ -400,7 +401,7 @@ const _checkPrecedent = (username, list) => {
* @param {string} collectiveId this is being subscribed to
*/
-const _mirrorTransactionState = (event, map, state, collectiveId, block) => {
+const _mirrorTransactionState = (event, collectiveId, block) => {
log(`[web3] Transaction state with collectiveId: ${collectiveId}...`);
if (event.returnValues.didPass) {
@@ -565,7 +566,7 @@ const _mirrorContractState = (state, index, collectiveId) => {
* @param {string} collectiveId this is being subscribed to
* @param {object} block with data from chain of this event
*/
-const _mirrorCollectiveEvent = (event, map, collectiveId, block) => {
+const _mirrorCollectiveEvent = (collectiveId, block) => {
log(`[web3] Mirroring collective data for ${collectiveId}`);
const collective = Collectives.findOne({ _id: collectiveId });
@@ -589,7 +590,7 @@ const _mirrorCollectiveEvent = (event, map, collectiveId, block) => {
* @param {string} collectiveId this is being subscribed to
* @param {object} block with data from chain of this event
*/
-const _mirrorUserEvent = (event, map, collectiveId, block) => {
+const _mirrorUserEvent = (event, collectiveId, block) => {
const authorUsername = event.returnValues.memberAddress.toLowerCase();
const member = Meteor.users.findOne({ username: authorUsername });
@@ -725,6 +726,10 @@ const _mirrorUserEvent = (event, map, collectiveId, block) => {
log(`[web3] Created a ragequit transaction with txId ${txId} ...`);
};
+/**
+* @summary gets the information of a given block.
+* @param {number} blockNumber to fetch info from.
+*/
const _getEventBlock = async (blockNumber) => {
let block;
log(`[web3] Getting data for block: ${blockNumber}`);
@@ -807,31 +812,25 @@ const _writeEvents = async (event, smartContract, state, collectiveId) => {
log(`[web3] Event timestamp found: ${eventTimestamp}`);
for (let k = 0; k < map.length; k += 1) {
- if (map[k].eventName === event[i].event) {
- if (event[i].event === 'SubmitProposal') {
- if (eventTimestamp >= lastEventBlockTimestamp) {
- _mirrorContractEvent(event[i], map[k], state, collectiveId, block);
- }
- }
- if (event[i].event === 'ProcessProposal') {
- if (eventTimestamp >= lastEventBlockTimestamp) {
- _mirrorTransactionState(event[i], map[k], state, collectiveId, block);
- }
- }
- if (event[i].event === 'SubmitVote') {
- if (eventTimestamp >= lastEventBlockTimestamp) {
- _mirrorTransaction(event[i], map[k], collectiveId, block);
- }
- }
- if (event[i].event === 'SummonComplete') {
- if (eventTimestamp >= lastEventBlockTimestamp) {
- _mirrorCollectiveEvent(event[i], map[k], collectiveId, block);
- }
- }
- if (event[i].event === 'Ragequit') {
- if (eventTimestamp >= lastEventBlockTimestamp) {
- _mirrorUserEvent(event[i], map[k], collectiveId, block);
- }
+ if ((map[k].eventName === event[i].event) && (eventTimestamp >= lastEventBlockTimestamp)) {
+ switch (event[i].event) {
+ case 'SubmitProposal':
+ _mirrorContractEvent(event[i], state, collectiveId, block);
+ break;
+ case 'ProcessProposal':
+ _mirrorTransactionState(event[i], collectiveId, block);
+ break;
+ case 'SubmitVote':
+ _mirrorTransaction(event[i], collectiveId, block);
+ break;
+ case 'SummonComplete':
+ _mirrorCollectiveEvent(collectiveId, block);
+ break;
+ case 'Ragequit':
+ _mirrorUserEvent(event[i], collectiveId, block);
+ break;
+ default:
+ log(`[web3] No interpreter function was found for event '${event[i].event}'`);
}
}
}
@@ -842,11 +841,11 @@ const _writeEvents = async (event, smartContract, state, collectiveId) => {
// save the index of last event from reading contract state
if (lastEventBlockTimestamp < currentEventBlockTimestamp) {
- Collectives.update({ _id: collectiveId }, { $set: { 'profile.lastEventBlockTimestamp': currentEventBlockTimestamp } }, (err, res) => {
+ Collectives.update({ _id: collectiveId }, { $set: { 'profile.lastEventBlockTimestamp': currentEventBlockTimestamp, 'profile.lastSyncedBlock': event[parseInt(event.length - 1, 10)].blockNumber } }, (err, res) => {
if (err) {
console.log(err);
}
- log(`[web3] Updated collective with lastEventBlockTimestamp ${lastEventBlockTimestamp}`);
+ log(`[web3] Updated collective with lastEventBlockTimestamp ${lastEventBlockTimestamp} and lastSyncedBlock: ${event[parseInt(event.length - 1, 10)].blockNumber}`);
return res;
});
}
@@ -927,34 +926,34 @@ const _getState = async (smartContract) => {
* @param {object} smartContract object from a collective
* @param {string} collectiveId this is being subscribed to
*/
-const _getEvents = async (smartContract, collectiveId) => {
+const _getEvents = async (smartContract, collectiveId, fromBlock, toBlock) => {
let eventLog;
if (_web3()) {
- log(`[web3] Getting past events for ${smartContract.publicAddress}..`);
+ log(`[web3] Getting events for ${smartContract.publicAddress} from block ${fromBlock} to ${toBlock}..`);
const abi = JSON.parse(smartContract.abi);
if (abi) {
- const state = await _getState(smartContract);
-
let events = [];
await new web3.eth.Contract(abi, smartContract.publicAddress).getPastEvents('allEvents', {
- fromBlock: START_BLOCK,
- toBlock: 'latest',
+ fromBlock,
+ toBlock,
}, (error, res) => {
if (error) {
log('[web3] Error fetching log data.');
log(error);
- } else {
- log(`[web3] Events consist of: ${JSON.stringify(_.uniq(_.pluck(res, 'event')))}`);
}
events = res;
return res;
});
- log(`[web3] Log for ${smartContract.publicAddress} has a length of ${events.length} events.`);
if (events.length > 0 && smartContract.map && smartContract.map.length > 0) {
+ log(`[web3] Log for ${smartContract.publicAddress} has a length of ${events.length} events.`);
+ const state = await _getState(smartContract);
await _writeEvents(events, smartContract, state, collectiveId);
+ computeDAOStats();
+ } else {
+ log('[web3] No new events found.');
}
}
}
@@ -1130,6 +1129,113 @@ const _askOracle = async (coin) => {
return finalNumber.toString();
};
+/**
+* @summary gets all the transfers (from and to) that happened betweent a token and an address
+* @param {object} coin with token data
+* @param {string} publicAddress interacting with token
+*/
+const _getTransfers = async (coin, publicAddress, eventName, fromBlock, toBlock, filter) => {
+ log(`[web3] Building chart for ${publicAddress} history with ${coin.code}...`);
+ let transfers;
+
+ await new web3.eth.Contract(erc20, coin.contractAddress).getPastEvents(eventName, {
+ filter,
+ fromBlock,
+ toBlock,
+ }, (error, res) => {
+ if (error) {
+ log('[web3] Error fetching log data.');
+ log(error);
+ }
+ transfers = res;
+ });
+
+ return transfers;
+};
+
+
+/**
+* @summary makes the information readable by the dapp
+* @param {object} feed with blockchain events
+* @param {object} coin with data to parse feed
+*/
+const _generateDataset = async (feed, coin) => {
+ const dataset = [];
+ let block;
+ console.log(feed);
+ for (let i = 0; i < feed.length; i += 1) {
+ block = await _getEventBlock(feed[i].blockNumber);
+ dataset.push({
+ t: new Date(block.timestamp * 1000),
+ y: parseFloat(_BNToNumber(feed[i].returnValues._value, coin), 10),
+ });
+ }
+ return dataset;
+};
+
+
+/**
+* @summary turns a dataset with historic activity into a cumulative dataset
+* @param {object} dataset to parse
+*/
+const _cummulative = (dataset) => {
+ const cummlativeSet = dataset;
+ for (let i = 1; i < dataset.length; i += 1) {
+ cummlativeSet[i].y = parseFloat(cummlativeSet[i].y + cummlativeSet[i - 1].y, 10);
+ }
+ return cummlativeSet;
+};
+
+
+/**
+* @summary creates a chart based on the activity of a public address with a token
+* @param {object} coin with token data
+* @param {string} publicAddress interacting with token
+*/
+const _buildChart = async (coin, publicAddress, fromBlock, toBlock) => {
+ const income = await _getTransfers(coin, publicAddress, 'Transfer', fromBlock, toBlock, { _to: publicAddress });
+ const outcome = await _getTransfers(coin, publicAddress, 'Transfer', fromBlock, toBlock, { _from: publicAddress });
+ const firstPass = await _generateDataset(income, coin);
+ const negativePass = _.map(await _generateDataset(outcome, coin), (item) => { const newItem = item; newItem.y *= -1; return newItem; });
+
+ const allBlocks = income.concat(outcome);
+ if (allBlocks.length > 0) {
+ const lastSyncedBlock = _.sortBy(allBlocks, 'blockNumber')[allBlocks.length - 1].blockNumber;
+ const dataset = _.reject(_.sortBy(firstPass.concat(negativePass), 't'), (item) => { return (item.y === 0); });
+ const final = {
+ data: _cummulative(dataset),
+ lastSyncedBlock,
+ };
+ return final;
+ }
+
+ return undefined;
+};
+
+
+/**
+* @summary when an existing chart needs updating and new information is added to the cummulative
+* @param {array} legacy values
+* @param {array} update values
+*/
+const _updateChart = (legacy, update) => {
+ const finalUpdate = legacy;
+ let newY;
+ for (let i = 0; i < update.length; i += 1) {
+ if (i === 0) {
+ newY = parseFloat(legacy[legacy.length - 1].y + update[i].y, 10);
+ } else {
+ newY = parseFloat(update[i].y + finalUpdate[finalUpdate.length - 1].y, 10);
+ }
+ finalUpdate.push({
+ t: update[i].t,
+ y: newY,
+ });
+ }
+
+ return finalUpdate;
+};
+
/**
* @summary gets the current value of the guild for each DAO
*/
@@ -1162,8 +1268,29 @@ const _syncDAOGuilds = async () => {
if (coin) {
daoGuild = _.findWhere(collectives[i].profile.blockchain.smartContracts, { label: 'GuildBank' });
finalGuild[k].value = await _balanceOf(coin, daoGuild.publicAddress);
+ const chart = _.findWhere(collectives[i].profile.chart, { guildLabel: collectives[i].profile.guild[k].name });
+ let finalChart;
+ let dataset;
+ if (!chart || !chart.dataset || chart.dataset.length === 0) {
+ dataset = await _buildChart(coin, daoGuild.publicAddress, defaults.START_BLOCK, 'latest');
+ finalChart = [{
+ guildLabel: collectives[i].profile.guild[k].name,
+ type: 'lineal',
+ dataset: [dataset],
+ lastSyncedBlock: dataset.lastSyncedBlock,
+ }];
+ } else if (chart.dataset && chart.dataset.length > 0) {
+ const initialBlock = parseInt(_.findWhere(collectives[i].profile.chart, { guildLabel: collectives[i].profile.guild[k].name }).lastSyncedBlock + 1, 10);
+ dataset = await _buildChart(coin, daoGuild.publicAddress, (!initialBlock) ? defaults.START_BLOCK : initialBlock, 'latest');
+ finalChart = [chart];
+ if (dataset) {
+ finalChart[0].dataset[0].data = _updateChart(finalChart[0].dataset[0].data, dataset.data);
+ finalChart[0].lastSyncedBlock = dataset.lastSyncedBlock;
+ }
+ }
+
log(`[dao] Total assets for this DAO... ${ticker} ${finalGuild[k].value}`);
- Collectives.update({ _id: collectives[i]._id }, { $set: { 'profile.guild': finalGuild } });
+ Collectives.update({ _id: collectives[i]._id }, { $set: { 'profile.guild': finalGuild, 'profile.chart': finalChart } });
}
break;
case 'guild-share-value':
diff --git a/package-lock.json b/package-lock.json
index a290dd641..fea47da10 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2334,6 +2334,42 @@
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
+ "chart.js": {
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz",
+ "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==",
+ "requires": {
+ "chartjs-color": "^2.1.0",
+ "moment": "^2.10.2"
+ }
+ },
+ "chartjs-color": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
+ "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
+ "requires": {
+ "chartjs-color-string": "^0.6.0",
+ "color-convert": "^1.9.3"
+ },
+ "dependencies": {
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ }
+ }
+ },
+ "chartjs-color-string": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
+ "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
+ "requires": {
+ "color-name": "^1.0.0"
+ }
+ },
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -8078,6 +8114,11 @@
}
}
},
+ "moment": {
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+ },
"more-entropy": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/more-entropy/-/more-entropy-0.0.7.tgz",
diff --git a/package.json b/package.json
index 3b26c8d27..b9c43f38e 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"blockstack": "^18.2.1",
"brace-expansion": "^1.1.11",
"braces": "^3.0.2",
+ "chart.js": "^2.9.3",
"core-js": "^2.5.7",
"cryptiles": "^4.1.2",
"deep-extend": "^0.5.1",
diff --git a/private/lib/dao.json b/private/lib/dao.json
index 1577cd01b..b0d56ed68 100644
--- a/private/lib/dao.json
+++ b/private/lib/dao.json
@@ -5,7 +5,7 @@
"domain": "molochdao.com",
"emails": [],
"profile": {
- "logo": "images/moloch.png",
+ "logo": "/images/moloch.png",
"website": "https://molochdao.com",
"bio": "A community DAO to fund Ethereum development in the name of Moloch the God of Coordination Failure.",
"guild": [
diff --git a/private/lib/token.json b/private/lib/token.json
index 6f38212eb..db8a008ca 100644
--- a/private/lib/token.json
+++ b/private/lib/token.json
@@ -2,7 +2,7 @@
"coin": [
{
"code": "DAI",
- "format": "0,0.0a",
+ "format": "0,0.00",
"emoji": "",
"unicode": "",
"name": "Dai Stablecoin",
@@ -13,7 +13,7 @@
"title": "DAI is a stable coin based on Collateral Debt Positions created with Maker DAO.",
"type": "ERC20",
"blockchain": "ETHEREUM",
- "contractAddress": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359",
+ "contractAddress": "0x6b175474e89094c44da98b954eedeac495271d0f",
"defaultVote": "0.1",
"editor": {
"allowBalanceToggle:": true,
@@ -140,6 +140,7 @@
"color": "#7885cb",
"type": "ERC20",
"blockchain": "ETHEREUM",
+ "abi": "[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"guy\",\"type\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"src\",\"type\":\"address\"},{\"name\":\"dst\",\"type\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"dst\",\"type\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"deposit\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"}]",
"contractAddress": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"defaultVote": "0.01",
"editor": {
diff --git a/public/templates/moloch/css/extra.css b/public/templates/moloch/css/extra.css
index 389ab69ef..3e3052b2b 100644
--- a/public/templates/moloch/css/extra.css
+++ b/public/templates/moloch/css/extra.css
@@ -608,7 +608,7 @@ width: 1px;
.election-rule {
background: transparent;
- color: #5a0074;
+ color: #48587d;
font-size: 0.7em;
display: inline-block;
padding: 0px;
@@ -789,11 +789,11 @@ width: 1px;
}
.parameter-name {
-background: #f2f2f8;
- color: #ae8abd;
- margin: 0px -25px 10px;
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
+ background: #eceef2;
+ color: #8d95a9;
+ margin: 0px -25px 10px;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
}
.parameter-value {
@@ -801,8 +801,8 @@ background: #f2f2f8;
}
.smart-contract {
- margin: 0 auto;
- text-align: left;
+ margin: 15px auto 0;
+ text-align: center;
}
.margin-top-minus-three {
@@ -860,7 +860,7 @@ h4 {
margin-bottom: 20px;
padding-right: 15px;
padding-left: 15px;
- color: #9a63aa;
+ color: #48587d;
font-size: 15px;
line-height: 28px;
font-weight: 100;
@@ -1048,10 +1048,6 @@ h4 {
box-shadow: none;
}
- body {
- background-color: #f4f4f4;
- }
-
.vote.vote-search.vote-feed.vote-delegation {
background-color: #fff;
}
@@ -1135,7 +1131,7 @@ h4 {
.guild-row-total {
color: white;
- background-color: #48587d;
+ background-color: #00c091;
border-bottom: none;
margin-bottom: -6px;
border-bottom-left-radius: 4px;
@@ -1147,10 +1143,10 @@ h4 {
}
.guild-icon {
- width: 42px;
- height: 42px;
+ width: 24px;
+ height: 24px;
float: left;
- margin: 10px 15px 10px 5px;
+ margin: 7px 10px 0px 0px;
}
.guild-title {
@@ -1160,12 +1156,13 @@ h4 {
margin-top: 0px;
}
-.guild-verifier {
- margin-top: -10px;
-}
-
.guild-detail {
clear: left;
+ width: 100%;
+ position: relative;
+ display: inline-block;
+ padding: 5px 0px 0px;
+ margin-bottom: 0px;
}
.guild-name {
@@ -1176,6 +1173,22 @@ h4 {
font-size: 1.2em;
}
+.guild-chart {
+ width: 100%;
+}
+
+.guild-meta {
+ float: left;
+ margin-right: 15px;
+ line-height: 28px;
+}
+
+.guild-row-last {
+ border-bottom: 0px;
+ margin-bottom: -7px;
+ font-weight: 500;
+}
+
.token-total {
border: 1px solid white;
color: white;
@@ -1191,6 +1204,20 @@ h4 {
}
.vote.vote-search.vote-feed.vote-delegation.guild-block {
- border: 3px solid #f9fafb;
+ border: 5px solid #f9fafb;
+}
+
+.chart {
+ width: 100% !important;
+ height: 150px !important;
+ /* background: #ffffff; */
+ /* border: #eef0f3 1px solid; */
+ padding: 0px 0px 0px;
+ border-radius: 6px;
+ margin: 0px 0px 10px;
}
+.verifier.verifier-live.verifier-feed.verifier-mini.verifier-key.verifier-guild {
+ float: none;
+ display: inline-block;
+}
diff --git a/public/templates/moloch/css/moloch.css b/public/templates/moloch/css/moloch.css
index 8ff4e04a4..b9ebb32b6 100755
--- a/public/templates/moloch/css/moloch.css
+++ b/public/templates/moloch/css/moloch.css
@@ -2323,7 +2323,7 @@ blockquote {
.identity-label {
display: inline-block;
- color: #5a0075;
+ color: #48587d;
font-size: 16px;
line-height: 22px;
text-decoration: none;
@@ -5845,7 +5845,7 @@ blockquote {
float: right;
border-radius: 3px;
/* background-color: #f9f9ff; */
- color: #5a0074;
+ color: #48587d;
font-size: 10px;
line-height: 18px;
font-weight: 400;
@@ -6668,9 +6668,9 @@ blockquote {
padding-right: 5px;
padding-left: 5px;
float: right;
- border: 1px solid #ff617e;
+ border: 1px solid #48587d;
border-radius: 100px;
- background-color: #ff617e;
+ background-color: #48587d;
color: #fff;
font-size: 10px;
line-height: 15px;