Files
kredits-web/app/services/kredits.js
T
2018-04-03 19:19:11 +02:00

283 lines
8.1 KiB
JavaScript

import ethers from 'npm:ethers';
import bs58 from 'npm:bs58';
import NpmBuffer from 'npm:buffer';
import RSVP from 'rsvp';
import Ember from 'ember';
import Service from 'ember-service';
import injectService from 'ember-service/inject';
import computed from 'ember-computed';
import config from 'kredits-web/config/environment';
import Proposal from 'kredits-web/models/proposal';
import abis from 'contracts/abis';
import addresses from 'contracts/addresses';
const {
getOwner,
Logger: {
debug,
warn
}
} = Ember;
const Buffer = NpmBuffer.Buffer;
export default Service.extend({
ipfs: injectService(),
web3Instance: null,
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
web3: function() {
if (this.get('web3Provider')) {
return this.get('web3Provider');
}
let web3Provider;
if (typeof window.web3 !== 'undefined') {
debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
let networkId = parseInt(web3.version.network);
web3Provider = new ethers.providers.Web3Provider(web3.currentProvider, {chainId: networkId});
this.set('web3Provided', true);
} else {
debug('[kredits] Creating new instance from npm module class');
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
let networkId = web3.version.network;
web3Provider = new ethers.providers.JsonRpcProvider(providerUrl, {chainId: network});
}
this.set('web3Provider', web3Provider);
return web3Provider;
}.property('web3Provider'),
listAccounts: function() {
return this.get('web3').listAccounts();
}.property('web3'),
currentUserAccounts: function() {
// TODO: listAccounts returns now a promise
return [];
return ethers.listAccounts();
// return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
}.property('web3Provided', 'web3'),
registryContract: computed('web3', function() {
let networkId = this.get('web3').chainId;
let registry = new ethers.Contract(addresses['Registry'][networkId], abis['Registry'], this.get('web3'));
return registry;
}),
contributorsContract: computed('web3', function() {
return this.contractFor('Contributors');
}),
kreditsContract: computed('web3', function() {
return this.contractFor('Operator');
}),
tokenContract: computed('web3', 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('web3').getSigner());
});
},
getContributorData(id) {
return this.get('contributorsContract')
.then((contract) => contract.contributors(id))
.then((data) => {
debug('[kredits] contributor', data);
let [ address, digest, hashFunction, size, isCore ] = data;
let isCurrentUser = this.get('currentUserAccounts').includes(address);
let profileHash = this.getMultihashFromBytes32({
digest,
hashFunction: hashFunction.toNumber(),
size: size.toNumber()
});
return this.get('tokenContract')
.then((contract) => contract.balanceOf(address))
.then((balance) => {
balance = balance.toNumber();
let contributor = getOwner(this).lookup('model:contributor');
contributor.setProperties({
id,
address,
profileHash,
isCore,
isCurrentUser,
balance
});
// Load data from IPFS
contributor.loadProfile();
return contributor;
});
});
},
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.getContributorData(id));
}
return RSVP.all(contributors);
});
},
getProposalData(i) {
return this.get('kreditsContract')
.then((contract) => contract.proposals(i))
.then(p => {
let ipfsHash = this.getMultihashFromBytes32({ digest: p.ipfsHash, hashFunction: p.hashFunction, size: p.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 : contributionIpfsHash
});
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: move into utils
getMultihashFromBytes32(multihash) {
const { digest, hashFunction, size } = multihash;
if (size === 0) {
return;
}
const hashBytes = Buffer.from(digest.slice(2), 'hex');
const multiHashBytes = new (hashBytes.constructor)(2 + hashBytes.length);
multiHashBytes[0] = hashFunction; //contributorData[2];
multiHashBytes[1] = size; //contributorData[3];
multiHashBytes.set(hashBytes, 2);
return bs58.encode(multiHashBytes);
},
getBytes32FromMultihash(multihash) {
const decoded = bs58.decode(multihash);
return {
digest: `0x${decoded.slice(2).toString('hex')}`,
hashFunction: decoded[0],
size: decoded[1],
};
},
addContributor(contributor) {
debug('[kredits] add contributor', contributor);
return this.get('ipfs')
.storeFile(contributor.serialize())
.then(profileHash => {
contributor.setProperties({
profileHash: profileHash,
kredits: 0,
isCurrentUser: this.get('currentUserAccounts').includes(contributor.address)
});
let {
digest, hashFunction, size
} = this.getBytes32FromMultihash(profileHash);
return this.get('kreditsContract')
.then((contract) => {
return contract.addContributor(
contributor.address,
digest,
hashFunction,
size,
contributor.isCore
);
})
.then((data) => {
debug('[kredits] add contributor response', data);
return contributor;
});
});
},
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;
});
});
},
});