206c657b98
Use getContributorById call to get contributor data
330 lines
9.7 KiB
JavaScript
330 lines
9.7 KiB
JavaScript
import ethers from 'npm:ethers';
|
|
|
|
import RSVP from 'rsvp';
|
|
import Ember from 'ember';
|
|
import Service from 'ember-service';
|
|
import injectService from 'ember-service/inject';
|
|
import computed, { alias } from 'ember-computed';
|
|
import { isEmpty, isPresent } from 'ember-utils';
|
|
|
|
import config from 'kredits-web/config/environment';
|
|
import Proposal from 'kredits-web/models/proposal';
|
|
|
|
import abis from 'contracts/abis';
|
|
import addresses from 'contracts/addresses';
|
|
import {
|
|
ContributorSerializer,
|
|
fromBytes32,
|
|
toBytes32
|
|
} from 'kredits-web/lib/kredits';
|
|
|
|
const {
|
|
getOwner,
|
|
Logger: {
|
|
debug,
|
|
warn,
|
|
error
|
|
}
|
|
} = Ember;
|
|
|
|
|
|
export default Service.extend({
|
|
|
|
ipfs: injectService(),
|
|
|
|
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 Ember.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);
|
|
if (accounts.length > 0) {
|
|
this.get('getCurrentUser').then((contributorData) => {
|
|
this.set('currentUser', contributorData);
|
|
resolve(ethProvider);
|
|
});
|
|
} else {
|
|
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;
|
|
});
|
|
},
|
|
|
|
registryContract: computed('ethProvider', function() {
|
|
let networkId = this.get('ethProvider').chainId;
|
|
let registry = new ethers.Contract(addresses['Registry'][networkId], abis['Registry'], this.get('ethProvider'));
|
|
return registry;
|
|
}),
|
|
|
|
contributorsContract: computed('ethProvider', function() {
|
|
return this.contractFor('Contributors');
|
|
}),
|
|
|
|
kreditsContract: computed('ethProvider', function() {
|
|
return this.contractFor('Operator');
|
|
}),
|
|
|
|
tokenContract: computed('ethProvider', function() {
|
|
return this.contractFor('Token');
|
|
}),
|
|
|
|
contractFor(name) {
|
|
return this.get('registryContract').functions.getProxyFor(name)
|
|
.then((address) => {
|
|
debug('[kredits] get contract', name, address);
|
|
return new ethers.Contract(address, abis[name], this.get('ethProvider').getSigner());
|
|
});
|
|
},
|
|
|
|
// TODO: Should be part of the service
|
|
buildContributor(attributes) {
|
|
debug('[kredits] buildContributor', attributes);
|
|
|
|
let contributor = getOwner(this).lookup('model:contributor');
|
|
contributor.setProperties(attributes);
|
|
return contributor;
|
|
},
|
|
|
|
getContributorById(id) {
|
|
return this.get('contributorsContract')
|
|
.then((contract) => contract.getContributorById(id))
|
|
// Set basic data
|
|
.then(({
|
|
account: address,
|
|
hashFunction,
|
|
hashSize,
|
|
isCore,
|
|
profileHash: digest,
|
|
balance
|
|
}) => {
|
|
let isCurrentUser = this.get('currentUserAccounts').includes(address);
|
|
let profileHash = fromBytes32({ digest, hashFunction, hashSize });
|
|
|
|
return {
|
|
id,
|
|
address,
|
|
isCore,
|
|
isCurrentUser,
|
|
profileHash,
|
|
balance: balance.toNumber()
|
|
};
|
|
})
|
|
// Fetch IPFS data if available
|
|
.then(this.loadContributorProfile.bind(this))
|
|
.then((attributes) => {
|
|
return this.buildContributor(attributes);
|
|
});
|
|
},
|
|
|
|
getContributors() {
|
|
return this.get('contributorsContract')
|
|
.then((contract) => contract.contributorsCount())
|
|
.then(contributorsCount => {
|
|
debug('[kredits] contributorsCount:', contributorsCount.toNumber());
|
|
let contributors = [];
|
|
|
|
for(var id = 1; id <= contributorsCount.toNumber(); id++) {
|
|
contributors.push(this.getContributorById(id));
|
|
}
|
|
|
|
return RSVP.all(contributors);
|
|
});
|
|
},
|
|
|
|
|
|
/**
|
|
* Loads the contributor's profile data from IPFS and returns the attributes
|
|
*
|
|
* @method
|
|
* @public
|
|
*/
|
|
loadContributorProfile(data) {
|
|
let profileHash = data.profileHash;
|
|
|
|
if (!profileHash) {
|
|
return data;
|
|
}
|
|
|
|
return this.get('ipfs')
|
|
.getFile(profileHash)
|
|
.then(ContributorSerializer.deserialize)
|
|
.then((attributes) => {
|
|
debug('[kredits] loaded contributor profile', attributes);
|
|
return Object.assign({}, data, attributes);
|
|
})
|
|
.catch((err) => {
|
|
error(
|
|
'[kredits] error trying to load contributor profile',
|
|
profileHash,
|
|
err
|
|
);
|
|
});
|
|
},
|
|
|
|
getProposalData(i) {
|
|
return this.get('kreditsContract')
|
|
.then((contract) => contract.proposals(i))
|
|
.then(p => {
|
|
let { ipfsHash: digest, hashFunction, hashSize } = p;
|
|
let ipfsHash = fromBytes32({ digest, hashFunction, hashSize });
|
|
|
|
let proposal = Proposal.create({
|
|
id : i,
|
|
creatorAddress : p.creator,
|
|
recipientId : p.recipientId.toNumber(),
|
|
votesCount : p.votesCount.toNumber(),
|
|
votesNeeded : p.votesNeeded.toNumber(),
|
|
amount : p.amount.toNumber(),
|
|
executed : p.executed,
|
|
ipfsHash
|
|
});
|
|
|
|
if (proposal.get('ipfsHash')) {
|
|
// TODO: move ipfs into model
|
|
return proposal
|
|
.loadContribution(this.get('ipfs'))
|
|
.then(() => { return proposal; });
|
|
} else {
|
|
warn('[kredits] proposal from blockchain is missing IPFS hash', proposal);
|
|
return proposal;
|
|
}
|
|
});
|
|
},
|
|
|
|
getProposals() {
|
|
return this.get('kreditsContract')
|
|
.then((contract) => contract.proposalsCount())
|
|
.then(proposalsCount => {
|
|
let proposals = [];
|
|
|
|
for(var i = 0; i < proposalsCount.toNumber(); i++) {
|
|
proposals.push(this.getProposalData(i));
|
|
}
|
|
|
|
return RSVP.all(proposals);
|
|
});
|
|
},
|
|
|
|
vote(proposalId) {
|
|
debug('[kredits] vote for', proposalId);
|
|
|
|
return this.get('kreditsContract')
|
|
.then((contract) => contract.vote(proposalId))
|
|
.then((data) => {
|
|
debug('[kredits] vote response', data);
|
|
return data;
|
|
});
|
|
},
|
|
|
|
// TODO: extract common logic to module
|
|
addContributor(attributes) {
|
|
debug('[kredits] add contributor', attributes);
|
|
|
|
let json = ContributorSerializer.serialize(attributes);
|
|
|
|
return this.get('ipfs')
|
|
.storeFile(json)
|
|
// Set profileHash
|
|
.then((profileHash) => {
|
|
attributes.profileHash = profileHash;
|
|
return attributes;
|
|
})
|
|
.then((attributes) => {
|
|
return this.get('kreditsContract')
|
|
.then((contract) => {
|
|
let { address, isCore, profileHash } = attributes;
|
|
let { digest, hashFunction, hashSize } = toBytes32(profileHash);
|
|
|
|
let contributor = [
|
|
address,
|
|
digest,
|
|
hashFunction,
|
|
hashSize,
|
|
isCore,
|
|
];
|
|
debug('[kredits] addContributor', ...contributor);
|
|
return contract.addContributor(...contributor);
|
|
});
|
|
})
|
|
.then((data) => {
|
|
debug('[kredits] add contributor response', data);
|
|
return this.buildContributor(attributes);
|
|
});
|
|
},
|
|
|
|
addProposal(proposal) {
|
|
const {
|
|
recipientAddress,
|
|
amount,
|
|
url
|
|
} = proposal.getProperties('recipientAddress', 'amount', 'url');
|
|
|
|
return this.get('ipfs')
|
|
.storeFile(proposal.serializeContribution())
|
|
.then(ipfsHash => {
|
|
return this.get('kreditsContract')
|
|
.then((contract) => {
|
|
return contract.addProposal(
|
|
recipientAddress,
|
|
amount,
|
|
url,
|
|
ipfsHash
|
|
);
|
|
})
|
|
.then((data) => {
|
|
debug('[kredits] add proposal response', data);
|
|
return data;
|
|
});
|
|
});
|
|
},
|
|
|
|
getCurrentUser: computed('ethProvider', function() {
|
|
if (isEmpty(this.get('currentUserAccounts'))) {
|
|
return RSVP.resolve();
|
|
}
|
|
return this.get('contributorsContract')
|
|
.then((contract) => {
|
|
return contract.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.getContributorById(id);
|
|
}
|
|
});
|
|
});
|
|
})
|
|
});
|