diff --git a/app/components/topbar-account-panel/component.js b/app/components/topbar-account-panel/component.js
index ed1bcfb..4ecb2df 100644
--- a/app/components/topbar-account-panel/component.js
+++ b/app/components/topbar-account-panel/component.js
@@ -1,45 +1,37 @@
-import Component from '@ember/component';
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
-import { computed } from '@ember/object';
+import { action } from '@ember/object';
import { isPresent } from '@ember/utils';
-export default Component.extend({
+export default class TopbarAccountPanelComponent extends Component {
+ @service router;
+ @service kredits;
- tagName: '',
+ @tracked setupInProgress = false;
- kredits: service(),
- router: service(),
-
- setupInProgress: false,
-
- userHasEthereumWallet: computed(function() {
+ get userHasWallet () {
return isPresent(window.ethereum);
- }),
-
- showConnectButton: computed('userHasEthereumWallet',
- 'kredits.hasAccounts', function() {
- return this.userHasEthereumWallet &&
- !this.kredits.hasAccounts;
- }),
-
- actions: {
-
- signup() {
- this.router.transitionTo('signup');
- },
-
- async connectAccount() {
- try {
- await window.ethereum.enable();
- this.set('setupInProgress', true);
- await this.kredits.setup();
- this.set('setupInProgress', false);
- } catch (error) {
- this.set('setupInProgress', false);
- console.log('Opening Ethereum wallet failed:', error);
- }
- }
-
}
-});
+ get walletConnected () {
+ return this.userHasWallet && this.kredits.hasAccounts;
+ }
+
+ get walletDisconnected () {
+ return this.userHasWallet && !this.kredits.hasAccounts;
+ }
+
+ @action
+ signup () {
+ this.router.transitionTo('signup');
+ }
+
+ @action
+ async connectWallet () {
+ this.setupInProgress = true;
+ await this.kredits.connectWallet();
+ this.setupInProgress = false;
+ }
+
+}
diff --git a/app/components/topbar-account-panel/template.hbs b/app/components/topbar-account-panel/template.hbs
index 1c113cf..28dc713 100644
--- a/app/components/topbar-account-panel/template.hbs
+++ b/app/components/topbar-account-panel/template.hbs
@@ -2,16 +2,15 @@
{{#if this.setupInProgress}}
Connecting account...
{{else}}
- {{#if (and this.kredits.hasAccounts this.kredits.currentUser)}}
+ {{#if (and this.walletConnected this.kredits.currentUser)}}
{{this.kredits.currentUser.name}}
- {{#if this.kredits.currentUserIsCore}}
- (core)
- {{/if}}
{{else}}
Anonymous
-
- {{#if this.showConnectButton}}
-
+
+ {{#if this.walletDisconnected}}
+
{{/if}}
{{/if}}
{{/if}}
diff --git a/app/services/browser-cache.js b/app/services/browser-cache.js
index 33cac37..2d828f7 100644
--- a/app/services/browser-cache.js
+++ b/app/services/browser-cache.js
@@ -4,8 +4,8 @@ import config from 'kredits-web/config/environment';
function createStore(name) {
let networkName;
- if (config.web3RequiredNetworkName) {
- networkName = config.web3RequiredNetworkName.toLocaleLowerCase().replace(' ', '-');
+ if (config.web3NetworkName) {
+ networkName = config.web3NetworkName.toLocaleLowerCase().replace(' ', '-');
} else {
networkName = 'custom';
}
diff --git a/app/services/kredits.js b/app/services/kredits.js
index 3436e7e..bd1d5e1 100644
--- a/app/services/kredits.js
+++ b/app/services/kredits.js
@@ -5,7 +5,7 @@ import Service from '@ember/service';
import EmberObject from '@ember/object';
import { computed } from '@ember/object';
import { alias, filterBy, notEmpty, sort } from '@ember/object/computed';
-import { isEmpty, isPresent } from '@ember/utils';
+import { isEmpty } from '@ember/utils';
import { inject as service } from '@ember/service';
import { task, taskGroup } from 'ember-concurrency';
@@ -15,6 +15,7 @@ import processContributorData from 'kredits-web/utils/process-contributor-data';
import processContributionData from 'kredits-web/utils/process-contribution-data';
import processReimbursementData from 'kredits-web/utils/process-reimbursement-data';
import formatKredits from 'kredits-web/utils/format-kredits';
+import switchNetwork from 'kredits-web/utils/switch-network';
import config from 'kredits-web/config/environment';
import Contributor from 'kredits-web/models/contributor';
@@ -55,6 +56,21 @@ export default Service.extend({
this.set('contributors', []);
this.set('contributions', []);
this.set('reimbursements', []);
+
+ if (window.ethereum) {
+ window.ethereum.on('chainChanged', this.handleUserChainChanged);
+ window.ethereum.on('accountsChanged', this.handleAccountsChanged);
+ }
+ },
+
+ handleUserChainChanged (chainId) {
+ console.log('User-provided chain ID changed to', chainId);
+ window.location.reload();
+ },
+
+ handleAccountsChanged (accounts) {
+ console.log('User-provided accounts changed to', accounts);
+ window.location.reload();
},
// This is called in the application route's beforeModel(). So it is
@@ -64,7 +80,7 @@ export default Service.extend({
let ethProvider;
return new Promise(resolve => {
- function instantiateWithoutAccount () {
+ function instantiateWithoutWallet () {
console.debug('[kredits] Creating new instance from npm module class');
console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`);
ethProvider = new ethers.providers.JsonRpcProvider(config.web3ProviderUrl);
@@ -74,44 +90,50 @@ export default Service.extend({
});
}
- async function instantiateWithAccount (web3Provider, context) {
+ async function instantiateWithWallet (web3Provider, context) {
console.debug('[kredits] Using user-provided Web3 instance, e.g. from Metamask');
- ethProvider = new ethers.providers.Web3Provider(web3Provider);
+ ethProvider = new ethers.providers.Web3Provider(web3Provider);
+ const network = await ethProvider.getNetwork();
+ const accounts = await ethProvider.listAccounts();
+ const chainId = config.web3ChainId;
- const network = await ethProvider.getNetwork();
- if (isPresent(config.web3RequiredChainId) &&
- network.chainId !== config.web3RequiredChainId) {
- return instantiateWithoutAccount();
- }
+ if (isEmpty(accounts)) return instantiateWithoutWallet();
- ethProvider.listAccounts().then(accounts => {
+ if (network.chainId !== chainId) {
+ return switchNetwork();
+ } else {
context.set('currentUserAccounts', accounts);
const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner();
resolve({
ethProvider,
ethSigner
});
- });
+ }
}
if (window.ethereum) {
- if (window.ethereum.isConnected()) {
- instantiateWithAccount(window.ethereum, this);
- } else {
- instantiateWithoutAccount();
- }
- }
- // Legacy dapp browsers...
- else if (window.web3) {
- instantiateWithAccount(window.web3.currentProvider, this);
- }
- // Non-dapp browsers...
- else {
- instantiateWithoutAccount();
+ instantiateWithWallet(window.ethereum, this);
+ } else {
+ instantiateWithoutWallet();
}
});
},
+ async connectWallet () {
+ const provider = new ethers.providers.Web3Provider(window.ethereum);
+ const network = await provider.getNetwork();
+ const chainId = config.web3ChainId;
+ const chainIdHex = `0x${Number(chainId).toString(16)}`;
+
+ try {
+ await window.ethereum.request({ method: 'eth_requestAccounts' });
+ if (network.chainId !== chainId) await switchNetwork(chainIdHex);
+ } catch (err) {
+ console.log('Connecting wallet failed:', err);
+ return false;
+ }
+ },
+
async setup () {
const kredits = await this.getEthProvider().then(providerAndSigner => {
return new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
diff --git a/app/templates/dashboard.hbs b/app/templates/dashboard.hbs
index edbdb03..be6609b 100644
--- a/app/templates/dashboard.hbs
+++ b/app/templates/dashboard.hbs
@@ -45,14 +45,16 @@
- Contributions by type
- Contributions by type
+
- In order to interact with the system you will need a Rootstock wallet/account. + In order to interact with the system you will need a + Rootstock + wallet/account.
-
diff --git a/app/utils/switch-network.js b/app/utils/switch-network.js
new file mode 100644
index 0000000..5e22c55
--- /dev/null
+++ b/app/utils/switch-network.js
@@ -0,0 +1,29 @@
+import config from 'kredits-web/config/environment';
+
+export default async function () {
+ const networkName = config.web3NetworkName;
+ const chainId = config.web3ChainId;
+ const chainIdHex = `0x${Number(chainId).toString(16)}`;
+
+ try {
+ await window.ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: chainIdHex }]
+ });
+ } catch (err) {
+ // This error code indicates that the chain has not been added to MetaMask
+ if (err.code === 4902) {
+ await window.ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [{
+ chainId: chainIdHex,
+ chainName: networkName,
+ rpcUrls: [ config.web3ProviderUrl ],
+ nativeCurrency: { name: 'tRBTC', symbol: 'tRBTC', decimals: 18 }
+ }]
+ });
+ } else {
+ console.warn('Failed to switch chains:', err.message);
+ }
+ }
+}
diff --git a/config/environment.js b/config/environment.js
index dcd354d..a3afbd6 100644
--- a/config/environment.js
+++ b/config/environment.js
@@ -26,8 +26,8 @@ module.exports = function(environment) {
},
web3ProviderUrl: 'https://rsk-testnet.kosmos.org',
- web3RequiredChainId: 31,
- web3RequiredNetworkName: 'RSK Testnet',
+ web3ChainId: 31,
+ web3NetworkName: 'RSK Testnet',
githubConnectUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/connect/github',
githubSignupUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/github',
@@ -82,7 +82,12 @@ module.exports = function(environment) {
if (process.env.WEB3_PROVIDER_URL) {
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
- ENV.web3RequiredChainId = null;
+ }
+ if (process.env.WEB3_CHAIN_ID) {
+ ENV.web3ChainId = parseInt(process.env.WEB3_CHAIN_ID);
+ }
+ if (process.env.WEB3_NETWORK_NAME) {
+ ENV.web3NetworkName = process.env.WEB3_NETWORK_NAME;
}
return ENV;
diff --git a/package.json b/package.json
index 6ba8540..c3bfdea 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"start": "ember serve",
"test": "npm-run-all lint:* test:*",
"test:ember": "ember test",
- "start:local": "WEB3_PROVIDER_URL=http://localhost:8545 ember serve",
+ "start:local": "WEB3_PROVIDER_URL=http://localhost:8545 WEB3_CHAIN_ID=1337 WEB3_NETWORK_NAME='Hardhat Devchain' ember serve",
"build": "ember build --environment=production",
"build-prod": "rm -rf release/* && ember build -prod --output-path release",
"preversion": "npm test",
diff --git a/tests/integration/components/topbar-account-panel/component-test.js b/tests/integration/components/topbar-account-panel/component-test.js
index 251c7ed..457280d 100644
--- a/tests/integration/components/topbar-account-panel/component-test.js
+++ b/tests/integration/components/topbar-account-panel/component-test.js
@@ -3,37 +3,90 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
+function stubWalletApi () {
+ window.ethereum = {
+ on: function() { return true; },
+ request: function() { return Promise.resolve(); }
+ }
+}
+
module('Integration | Component | topbar-account-panel', function(hooks) {
setupRenderingTest(hooks);
- test('unknown user without wallet (or no permission to get wallet/account info)', async function(assert) {
+ test('Unknown user without wallet', async function(assert) {
await render(hbs`