Skip to content

Commit

Permalink
Merge pull request #500 from DemocracyEarth/fortmatic
Browse files Browse the repository at this point in the history
Support for iFrame wallets
  • Loading branch information
santisiri authored Feb 11, 2020
2 parents b092202 + c2d745c commit cd3b9e3
Show file tree
Hide file tree
Showing 11 changed files with 465 additions and 436 deletions.
12 changes: 10 additions & 2 deletions config/development/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@
}
},
"sentryPrivateDSN": "",
"smtpServer": ""
"smtpServer": "",
"web3": {
"config": "mainnet",
"network": "",
"secret": "",
"id": "",
"fortmatic": ""
}
},
"public": {
"AWSHostingURL": "",
Expand Down Expand Up @@ -134,7 +141,8 @@
"config": "mainnet",
"network": "",
"secret": "",
"id": ""
"id": "",
"fortmatic": ""
}
}
}
6 changes: 3 additions & 3 deletions i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -586,13 +586,13 @@
"no-tokens": "NO TOKENS",
"voting-token": "Voting Token",
"metamask-sign-nonce": "I am authenticating with the blockchain address of this account to access {{collectiveName}}.",
"metamask-install": "Please install MetaMask to connect your browser with the Ethereum blockchain first.",
"metamask-activate": "Please activate MetaMask first.",
"metamask-install": "Please install a Web3 wallet to connect your browser with the Ethereum blockchain first.",
"metamask-activate": "Please activate Web3 wallet first.",
"metamask-login-error": "Login error with Metamask",
"metamask-sign-fail": "Signature verification failed.",
"send": "Send",
"close": "Close",
"metamask-denied-signature": "Transaction signature was denied.",
"metamask-denied-signature": "Signature was denied.",
"metamask-invalid-address": "Address mismatch.<br>Verify wallet account is the same used to sign in.",
"metamask-invalid-argument": "An invalid argument was passed to the contact.",
"blockchain-address": "Blockchain Address",
Expand Down
30 changes: 22 additions & 8 deletions imports/api/blockchain/modules/web3Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,29 @@ import { Session } from 'meteor/session';

import { token } from '/lib/token';

const Fortmatic = require('fortmatic');
const numeral = require('numeral');

// Set web3 provider
let web3;
const provider = Meteor.settings.public.web3.network;
/**
* @summary setups a wallet either via plugin or iframe
*/
const _setupWallet = () => {
if (Meteor.isClient) {
if (typeof window.web3 !== 'undefined') {
console.log('using current provider for wallet');
return new Web3(window.web3.currentProvider);
}
console.log('creating wallet via fortmatic');
const fm = new Fortmatic(Meteor.settings.public.web3.fortmatic);
return new Web3(fm.getProvider());
} else if (Meteor.isServer) {
const provider = Meteor.settings.private.web3.network;
return new Web3(new Web3.providers.HttpProvider(provider));
}
return undefined;
};

if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider(provider));
}
const web3 = _setupWallet();

/**
* @summary get coin from corpus
Expand Down Expand Up @@ -295,3 +307,5 @@ export const getCoin = _getCoin;
export const getTokenData = _getTokenData;
export const getBalance = _getBalance;
export const numToCryptoBalance = _numToCryptoBalance;
export const setupWallet = _setupWallet;

164 changes: 96 additions & 68 deletions imports/startup/both/modules/metamask.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Contracts } from '/imports/api/contracts/Contracts';
import { displayModal, alert } from '/imports/ui/modules/modal';
import { transact } from '/imports/api/transactions/transaction';
import { displayNotice } from '/imports/ui/modules/notice';
import { addDecimal, smallNumber, removeDecimal, getCoin, numToCryptoBalance } from '/imports/api/blockchain/modules/web3Util';
import { addDecimal, setupWallet, getCoin, numToCryptoBalance } from '/imports/api/blockchain/modules/web3Util';
import { animatePopup } from '/imports/ui/modules/popup';
import { Transactions } from '/imports/api/transactions/Transactions';
import { sync } from '/imports/ui/templates/layout/sync';
Expand All @@ -19,10 +19,7 @@ import { getShares, setTransaction } from '/lib/web3';
import { getTransactionObject } from '/lib/interpreter';

import { BigNumber } from 'bignumber.js';

import abi from 'human-standard-token-abi';
import { debug } from 'util';
import { SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG } from 'constants';

const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
Expand Down Expand Up @@ -68,24 +65,28 @@ const _formatCryptoValue = (value, tokenCode) => {
*/
const _web3 = (activateModal) => {
if (!window.web3) {
if (activateModal) {
modal.message = TAPi18n.__('metamask-install');
displayModal(true, modal);
web3 = setupWallet();
if (!web3) {
if (activateModal) {
modal.message = TAPi18n.__('metamask-install');
displayModal(true, modal);
}
return false;
}
return false;
}
if (!web3) {
web3 = new Web3(window.web3.currentProvider);
}

web3.eth.getCoinbase().then(function (coinbase) {
web3.eth.getCoinbase().then((coinbase) => {
if (!coinbase) {
if (activateModal) {
modal.message = TAPi18n.__('metamask-activate');
displayModal(true, modal);
return false;
}
}
return undefined;
});

return web3;
Expand Down Expand Up @@ -306,6 +307,37 @@ const _pendingTransaction = (voterAddress, hash, contract, choice) => {
}
};

/**
* @summary prompt a message of an error with the wallet
* @param {object} error with code and message
*/
const _walletError = (err) => {
let message;
switch (err.code) {
case -32602:
message = TAPi18n.__('metamask-invalid-argument');
break;
case -32603:
message = TAPi18n.__('metamask-invalid-address');
break;
case 4001:
message = TAPi18n.__('metamask-denied-signature');
break;
default:
message = err.message;
}
displayModal(
true,
{
icon: Meteor.settings.public.app.logo,
title: TAPi18n.__('wallet'),
message,
cancel: TAPi18n.__('close'),
alertMode: true,
}
);
};

/**
* @summary call a method from a dao using a collective map
* @param {string} methodName to call from contract
Expand All @@ -327,30 +359,7 @@ const _callDAOMethod = async (methodName, parameterList, collectiveId, walletMet

await dao.methods[`${methodName}`](...parameterList)[walletMethod](walletParameters, (err, res) => {
if (err) {
let message;
switch (err.code) {
case -32602:
message = TAPi18n.__('metamask-invalid-argument');
break;
case -32603:
message = TAPi18n.__('metamask-invalid-address');
break;
case 4001:
message = TAPi18n.__('metamask-denied-signature');
break;
default:
message = err.message;
}
displayModal(
true,
{
icon: Meteor.settings.public.app.logo,
title: TAPi18n.__('wallet'),
message,
cancel: TAPi18n.__('close'),
alertMode: true,
}
);
_walletError(err);
return err;
}
response = res;
Expand Down Expand Up @@ -523,7 +532,11 @@ const handleSignMessage = (publicAddress, nonce, message) => {
web3.utils.utf8ToHex(`${message}`),
publicAddress,
function (err, signature) {
if (err) return reject(err);
if (err) {
_hideLogin();
_walletError(err);
return reject(err);
}
return resolve({ signature });
}
);
Expand Down Expand Up @@ -765,6 +778,43 @@ const _getLastTimestamp = async () => {
return undefined;
};

/**
* @summary does a web3 login without privacy mode;
*/
const _loginWeb3 = () => {
const nonce = Math.floor(Math.random() * 10000);
let publicAddress;

return web3.eth.getCoinbase().then(function (coinbaseAddress) {
publicAddress = coinbaseAddress.toLowerCase();
return handleSignMessage(publicAddress, nonce, TAPi18n.__('metamask-sign-nonce').replace('{{collectiveName}}', Meteor.settings.public.app.name));
}).then(function (signature) {
console.log(signature);
const verification = verifySignature(signature, publicAddress, nonce);

if (verification === 'success') {
const methodName = 'login';
const methodArguments = [{ publicAddress }];
Accounts.callLoginMethod({
methodArguments,
userCallback: (err) => {
Accounts._pageLoadLogin({
type: 'metamask',
allowed: !err,
error: err,
methodName,
methodArguments,
});
Session.set('newLogin', true);
Router.go('/');
},
});
} else {
_hideLogin();
console.log(TAPi18n.__('metamask-login-error'));
}
});
};

if (Meteor.isClient) {
/**
Expand All @@ -778,7 +828,14 @@ if (Meteor.isClient) {
if (Meteor.Device.isPhone()) {
// When mobile, not supporting privacy-mode for now
// https://github.com/DemocracyEarth/sovereign/issues/421
return web3.eth.getCoinbase().then(function (coinbaseAddress) {
return _loginWeb3();
}

if (window.ethereum) {
// Support privacy-mode in desktop only for now and if web3 installed
window.ethereum.enable().then(function () {
return web3.eth.getCoinbase();
}).then(function (coinbaseAddress) {
publicAddress = coinbaseAddress.toLowerCase();
return handleSignMessage(publicAddress, nonce, TAPi18n.__('metamask-sign-nonce').replace('{{collectiveName}}', Meteor.settings.public.app.name));
}).then(function (signature) {
Expand All @@ -797,46 +854,17 @@ if (Meteor.isClient) {
methodName,
methodArguments,
});
Session.set('newLogin', true);
Router.go('/');
_hideLogin();
},
});
} else {
console.log(TAPi18n.__('metamask-login-error'));
_hideLogin();
}
});
} else {
return _loginWeb3();
}

// Support privacy-mode in desktop only for now
window.ethereum.enable().then(function () {
return web3.eth.getCoinbase();
}).then(function (coinbaseAddress) {
publicAddress = coinbaseAddress.toLowerCase();
return handleSignMessage(publicAddress, nonce, TAPi18n.__('metamask-sign-nonce').replace('{{collectiveName}}', Meteor.settings.public.app.name));
}).then(function (signature) {
const verification = verifySignature(signature, publicAddress, nonce);

if (verification === 'success') {
const methodName = 'login';
const methodArguments = [{ publicAddress }];
Accounts.callLoginMethod({
methodArguments,
userCallback: (err) => {
Accounts._pageLoadLogin({
type: 'metamask',
allowed: !err,
error: err,
methodName,
methodArguments,
});
Router.go('/');
_hideLogin();
},
});
} else {
console.log(TAPi18n.__('metamask-login-error'));
}
});
} else {
modal.message = TAPi18n.__('metamask-activate');
displayModal(true, modal);
Expand Down
9 changes: 0 additions & 9 deletions imports/ui/modules/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ let modalCallback;
const modal = (active, settings, callback, cancel) => {
Session.set('displayModal', settings);
Session.set('showModal', active);
/*
if (active) {
$('#content').css('overflow', 'hidden');
$('.alert').css('overflow', 'scroll');
} else {
$('#content').css('overflow', 'scroll');
$('.alert').css('overflow', 'hidden');
}
*/

if (callback !== undefined) {
globalObj.modalCallback = callback;
Expand Down
2 changes: 0 additions & 2 deletions imports/ui/templates/layout/url/home/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,6 @@ Template.homeFeed.onCreated(function () {
if (subscription.ready()) {
_generateReplica(instance);

console.log(Contracts.findOne());

const collectiveId = Contracts.findOne().collectiveId;
Session.set('search', {
input: '',
Expand Down
18 changes: 16 additions & 2 deletions imports/ui/templates/layout/url/topbar/topbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Session } from 'meteor/session';
import { timers } from '/lib/const';
import { resetSplit } from '/imports/ui/modules/split';
import { shortenCryptoName } from '/imports/ui/templates/components/identity/avatar/avatar';
import { displayModal } from '/imports/ui/modules/modal';

import { getTemplate } from '/imports/ui/templates/layout/templater';
import { promptLogin } from '/imports/ui/templates/components/collective/collective.js';
Expand Down Expand Up @@ -110,15 +111,28 @@ Template.topbar.events({
Session.set('userLoginVisible', true);
Meteor.loginWithMetamask({}, function (err) {
if (err.reason) {
console.log('ERROR');
throw new Meteor.Error('Metamask login failed', err.reason);
}
Session.set('userLoginVisible', false);
});
}
// _prompt(Template.instance());
},
'click #sign-out-button'() {
Meteor.logout();
displayModal(
true,
{
icon: Meteor.settings.public.app.logo,
title: TAPi18n.__('sign-out'),
message: TAPi18n.__('sign-out-prompt'),
cancel: TAPi18n.__('not-now'),
action: TAPi18n.__('sign-out'),
displayProfile: false,
},
() => {
Meteor.logout();
}
);
},
'click #nav-home'(event) {
event.preventDefault();
Expand Down
Loading

0 comments on commit cd3b9e3

Please sign in to comment.