Files
kredits-web/app/services/kredits.js
T

244 lines
7.3 KiB
JavaScript

import ethers from 'npm:ethers';
import Kredits from 'kredits-web/lib/kredits';
import RSVP from 'rsvp';
import Ember from 'ember';
import Service from 'ember-service';
import computed, { alias } from 'ember-computed';
import { isEmpty, isPresent } from 'ember-utils';
import config from 'kredits-web/config/environment';
const {
getOwner,
Logger: {
debug
}
} = Ember;
export default Service.extend({
ethProvider: null,
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
currentUser: null,
currentUserIsContributor: computed('currentUser', function() {
return isPresent(this.get('currentUser'));
}),
currentUserIsCore: alias('currentUser.isCore'),
hasAccounts: computed('currentUserAccounts', function() {
return !isEmpty(this.get('currentUserAccounts'));
}),
accountNeedsUnlock: computed('currentUserAccounts', function() {
return this.get('currentUserAccounts') && isEmpty(this.get('currentUserAccounts'));
}),
// this is called called in the routes beforeModel(). So it is initialized before everything else
// and we can rely on the ethProvider and the potential currentUserAccounts to be available
initEthProvider: function() {
return new RSVP.Promise((resolve) => {
let ethProvider;
let networkId;
if (typeof window.web3 !== 'undefined') {
debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
networkId = parseInt(window.web3.version.network);
ethProvider = new ethers.providers.Web3Provider(window.web3.currentProvider, {chainId: networkId});
ethProvider.listAccounts().then((accounts) => {
this.set('currentUserAccounts', accounts);
this.set('ethProvider', ethProvider);
resolve(ethProvider);
});
} else {
debug('[kredits] Creating new instance from npm module class');
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
networkId = parseInt(config.contractMetadata.networkId);
ethProvider = new ethers.providers.JsonRpcProvider(providerUrl, {chainId: networkId});
this.set('ethProvider', ethProvider);
resolve(ethProvider);
}
window.ethProvider = ethProvider;
});
},
setup() {
return this.initEthProvider().then((ethProvider) => {
let signer = ethProvider.getSigner();
return Kredits.setup(ethProvider, signer, config.ipfs).then((kredits) => {
this.set('kredits', kredits);
// TODO: Cleanup
if (this.get('currentUserAccounts').length > 0) {
this.get('getCurrentUser').then((contributorData) => {
this.set('currentUser', contributorData);
});
}
return kredits;
});
});
},
totalSupply: computed(function() {
return this.get('kredits').Token.functions.totalSupply();
}),
contributors: [],
proposals: [],
loadContributorsAndProposals() {
return RSVP.hash({
contributors: this.getContributors(),
proposals: this.getProposals(),
}).then(({ contributors, proposals }) => {
this.set('contributors', contributors);
this.set('proposals', proposals);
});
},
addContractEventHandlers() {
// Operator events
this.get('kredits').Operator
.on('ProposalCreated', (proposalId) => {
this.handleProposalCreated(proposalId);
})
.on('ProposalVoted', (proposalId, voter, totalVotes) => {
this.handleProposalVoted(proposalId, totalVotes);
})
.on('ProposalExecuted', (proposalId, contributorId, amount) => {
this.handleProposalExecuted(proposalId, contributorId, amount);
});
// Token events
this.get('kredits').Token
.on('Transfer', (from, to, value) => {
this.handleTransfer(from, to, value);
});
},
// TODO: Only assign valid attributes
buildModel(name, attributes) {
debug('[kredits] build', name, attributes);
let model = getOwner(this).lookup(`model:${name}`);
model.setProperties(attributes);
return model;
},
addContributor(attributes) {
debug('[kredits] add contributor', attributes);
return this.get('kredits').Contributor.add(attributes)
.then((data) => {
debug('[kredits] add contributor response', data);
return this.buildModel('contributor', attributes);
});
},
getContributors() {
return this.get('kredits').Contributor.all()
.then((contributors) => {
return contributors.map((contributor) => {
return this.buildModel('contributor', contributor);
});
});
},
addProposal(attributes) {
debug('[kredits] add proposal', attributes);
return this.get('kredits').Operator.addProposal(attributes)
.then((data) => {
debug('[kredits] add proposal response', data);
return this.buildModel('proposal', attributes);
});
},
getProposals() {
return this.get('kredits').Operator.all()
.then((proposals) => {
return proposals.map((proposal) => {
return this.buildModel('proposal', proposal);
});
});
},
vote(proposalId) {
debug('[kredits] vote for', proposalId);
return this.get('kredits').Operator.functions.vote(proposalId)
.then((data) => {
debug('[kredits] vote response', data);
return data;
});
},
// TODO: Cleanup
getCurrentUser: computed('ethProvider', function() {
if (isEmpty(this.get('currentUserAccounts'))) {
return RSVP.resolve();
}
return this.get('kredits').Contributor
.functions.getContributorIdByAddress(this.get('currentUserAccounts.firstObject'))
.then((id) => {
id = id.toNumber();
// check if the user is a contributor or not
if (id === 0) {
return RSVP.resolve();
} else {
return this.get('kredits').Contributor.getById(id);
}
});
}),
findProposalById(proposalId) {
return this.get('proposals').findBy('id', proposalId.toString());
},
// Contract events
handleProposalCreated(proposalId) {
let proposal = this.findProposalById(proposalId);
if (proposal) {
debug('[events] proposal exists, not adding from event');
return;
}
this.get('kredits').Operator.getById(proposalId)
.then((proposal) => {
this.get('proposals').pushObject(proposal);
});
},
// TODO: We may want to reload that proposal to show the voter as voted
handleProposalVoted(proposalId, totalVotes) {
let proposal = this.findProposalById(proposalId);
if (proposal) {
proposal.set('votesCount', totalVotes);
}
},
handleProposalExecuted(proposalId, contributorId, amount) {
let proposal = this.findProposalById(proposalId);
if (proposal.get('isExecuted')) {
debug('[events] proposal already executed, not adding from event');
return;
}
proposal.set('executed', true);
this.get('contributors')
.findBy('id', contributorId.toString())
.incrementProperty('balance', amount);
},
handleTransfer(from, to, value) {
value = value.toNumber();
this.get('contributors')
.findBy('address', from)
.decrementProperty('balance', value);
this.get('contributors')
.findBy('address', to)
.incrementProperty('balance', value);
},
});