#MetaMask / detect-provider: handle Accounts Changed - ethereum

I'm using #metamask/detect-provider, when I use the second account of my Metamask wallet, this second account is not considered in execution. This is the first account that is always taken in the execution

accountsChanged event will be emitted whenever you change the account. So write a callback to handle it. I just set it in a state.
useEffect(() => {
// you have to write code to detect provider. [#metamask/detect-provider][1]
// if there is no accound set it null as default
provider &&
provider.on("accountsChanged", () => setAccount(accounts[0] ?? null));
}, []);
or
useEffect(() => {
window.ethereum &&
window.ethereum.on("accountsChanged", () => setAccount(accounts[0] ?? null));
}, []);

Related

node js script exits prematurely

Promise newbie here.
I'm trying to retrieve icon_name field from asset database, Equipment table in mongodb
and update icon_id field in equipments database, equipments table in mysql.
I have about 12,000 records with icon_name field in Equipment.
The script runs successfully however it doesn't seem to go through all the records.
When I check the equipments table there are only about 3,000 records updated.
I tried running the script several times and it appears to update a few more records each time.
My suspicion is the database connection is close before all the queries are finished but since I use Promise.all I don't know why it happened.
Here is the script
const _ = require('lodash'),
debug = require('debug')('update'),
Promise = require('bluebird')
const asset = require('../models/asset'),
equipments = require('../models/equipments')
const Equipment = asset.getEquipment(),
my_equipments = equipments.get_equipments(),
icons = equipments.get_icons()
Promise.resolve()
.then(() => {
debug('Retrieve asset equipments, icons')
return Promise.all([
icons.findAll(),
Equipment.find({ icon_name: { $ne: null } })
])
})
.then(([my_icons, asset_equipments]) => {
debug('Update equipments')
const updates = []
console.log(asset_equipments.length)
asset_equipments.forEach((aeq, i) => {
const icon_id = my_icons.find(icon => icon.name === aeq.icon_name).id
up = my_equipments.update(
{ icon_id },
{ where: { code: aeq.eq_id } }
)
updates.push(up)
})
return Promise.all(updates)
})
.then(() => {
debug('Success: all done')
asset.close()
equipments.close()
})
.catch(err => {
debug('Error:', err)
asset.close()
equipments.close()
})
Thanks in advance.
Code looks fine but spawning 12000 promises in parallel might cause some trouble on the database connection level. I would suggest to batch the concurrent requests and limit them to let's say 100. You could use batch-promises (https://www.npmjs.com/package/batch-promises)
Basically something like
return batchPromises(100, asset_equipments, aeq => {
const icon_id = my_icons.find(icon => icon.name === aeq.icon_name).id;
return my_equipments.update({ icon_id }, { where: { code: aeq.eq_id } });
});

Detect MetaMask logout (Ethereum)

I've looked at the documentation here https://metamask.github.io/metamask-docs/Main_Concepts/Getting_Started
But I'm not sure how to detect a user logging out of MetaMask?
window.ethereum.on('accountsChanged', (accounts) => {
// If user has locked/logout from MetaMask, this resets the accounts array to empty
if (!accounts.length) {
// logic to handle what happens once MetaMask is locked
}
});
Thus, using the above you can detect lock/logout of MetaMask.
window.ethereum.on('accountsChanged', function (accounts) {
let acc = accounts[0]
acc will be undefined if they logged out.
From MetaMask Ethereum Provider API:
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);
The MetaMask provider emits this event whenever the return value of the eth_accounts RPC method changes. eth_accounts returns an array that is either empty or contains a single account address. The returned address, if any, is the address of the most recently used account that the caller is permitted to access. Callers are identified by their URL origin, which means that all sites with the same origin share the same permissions.
Metamask documentation suggest you to refresh the page if account is changed.
const setAccountListener = (provider) => {
provider.on("accountsChanged", (_) => window.location.reload());
provider.on("chainChanged", (_) => window.location.reload());
};
Then call this in useEffect
useEffect(() => {
// Load provider
if (provider) {
....
setAccountListener(provider);
// add more logic
} else {
console.error("Please, install Metamask.");
}
};
}, []);
New Feature: _metamask.isUnlocked()
Metamask adds _metamask.isUnlocked() experimental property on ethereum.
const reload = () => window.location.reload();
const handleAccount = (ethereum) => async () => {
const isLocked = !(await ethereum._metamask.isUnlocked());
if (isLocked) {
reload();
}
};
const setListener = (ethereum) => {
ethereum.on("chainChanged", reload);
ethereum.on("accountsChanged", handleAccount(ethereum));
};
const removeListener = (ethereum) => {
ethereum.removeListener("chainChanged", reload);
ethereum.removeListener("accountsChanged", handleAccount(ethereum));
};

One response after few async functions

I have a web page with a form where a user can edit personal info, education, work history and etc.
And the user can add more than one degree, for example: bs, ms, phd. And a few job positions as well.
When the user push 'save' button I send all this data to my server. I send it all in one request. In the server I have a point to handle the request.
app.post(config.version + '/profile', (req, res, next) => {});
And there I do a few MySQL queries to insert/update/delete a data. I use mysql package from npm to do that.
new Promise((resolve, reject) => {
const userQuery = `INSERT INTO user ...;`;
const degreesQuery = 'INSERT INTO degree ...;';
const positionsQuery = 'UPDATE position SET ...;';
this.connection.query(userQuery, err => {});
this.connection.query(degreesQuery, err => {});
this.connection.query(positionsQuery, err => {});
resolve({});
})
In the end I do resolve({}) but I want to select updated profile and send it back (because in MySQL tables for degrees I add ids that helps me to not insert again duplicate data). So, my question is how to do resolve({}) only when all my async this.connection.querys finished?
My suggestion is to run all the queries in a Promise.all().
Example:
const queries = [
`INSERT INTO user ...;`;,
'INSERT INTO degree ...;',
'UPDATE position SET ...;'
];
Promise.all(queries.map((query) => {
return new Promise((resolve, reject) => {
this.connection.query(query, err => {
return err ? reject(err) : resolve();
});
});
})
.then(() => {
// continue
// get your updated data here with and send it as response
})
If your db library has support for Promise write it this way
Promise.all(queries.map((query) => {
return this.connection.query(query);
})
.then(() => {
// continue
// get your updated data here with and send it as response
})

Tracking DB querying time - Bookshelf/knex

I would like to monitor the time taken by a query on my API's db. I so created the following function, using bookshelf-signals, a Bookshelf plugin. :
bookshelf.on('fetching', () => {
server.app.fetching = new Date().valueOf();
});
bookshelf.on('counting', () => {
server.app.fetching = new Date().valueOf();
});
bookshelf.on('fetched', () => {
server.statsd.gauge('db_query', new Date().valueOf() - server.app.fetching);
});
... so that I can retrieve the time just before and just after a fetch/count; I did the same with deleting-deleted and saving-saved.
What I think I fail to understand is when fetching and fetched are supposed to be triggered... When I tried to to see when fetching and fetched were triggered, basically it ended up with this :
'fetching event A'
'fetching event B'
'fetching event C'
'fetched event C'
'fetched event B'
'fetched event A'
Resulting in the timers returning wrong values obliviously, do you have any lead/clue ?
I also saw that one could trigger 'query' events on Knex, and thought of using this as an alternative solution. However, it seems that it only works if I specify the table where I query, ie :
knex('whatever_table').on('query', () => {///});
Making it impracticable in the case where I want to apply an event handler on every model...
I think I should stick with Bookshelf, but how can I do with the way the events are handled?
Thank you in advance!
I just wrote some small test code how to trace transaction duration with knex.
https://runkit.com/embed/679qu91ylu4w
/**
* Calculate transaction durations in knex
*
*/
require('sqlite3');
var knex = require("knex")({
client: 'sqlite',
connection: ':memory:',
pool: { min: 1, max: 10 }
});
function isTransactionStart(querySpec) {
return querySpec.sql === 'BEGIN;';
}
function isTransactionEnd(querySpec) {
return querySpec.sql === 'COMMIT;' || querySpec.sql === 'ROLLBACK;';
}
const transactionDurations = {};
knex.on('query', querySpec => {
console.log('On query', querySpec);
if (isTransactionStart(querySpec)) {
if (transactionDurations[querySpec.__knexUid]) {
console.error('New transaction started, before earlier was ended');
return;
}
transactionDurations[querySpec.__knexUid] = new Date().getTime();
}
if (isTransactionEnd(querySpec)) {
const startTime = transactionDurations[querySpec.__knexUid];
if (!startTime) {
console.error('Transaction end detected, but start time not found');
}
const endTime = new Date().getTime();
transactionDurations[querySpec.__knexUid] = null;
console.log('TRANSACTION DURATION', endTime - startTime);
}
});
// just as an example of other available events to show when they are called
knex.on('query-response', (res, querySpec) => {
// console.log('On query response', res, querySpec);
});
knex.on('query-error', (err, querySpec) => {
// console.log('On query error', err, querySpec);
});
try {
a = await Promise.all([
knex.transaction(trx => {
return trx.raw('select 1');
}),
knex.transaction(trx => {
return trx.raw('select 2');
}),
knex.transaction(trx => {
return trx.raw('error me');
})
]);
} catch (e) {
console.log('Got ERROR:', e);
}
The same king of approach should work also for query timing. To prevent timer bookkeeping from leaking memory you should add some cleanup code though.
Query duration timer should be started in query event and stopped in query-response or query-error depending which one triggers first.
To be able to match query - query-response pair querySpec.__knexQueryUid attribute can be used.
Based on Mikael Lepistö snippet I came up with this :
const dbEvents = (server, sdc) => {
knex.on('query', data => {
server.app[data.__knexQueryUid + ''] = new Date().valueOf();
});
knex.on('query-response', (data, obj, builder) => {
sdc.counter('db_queries_time', new Date().valueOf() - server.app[obj.__knexQueryUid + '']);
sdc.increment('nr_db_queries');
});
};
And I then call the function when I start the server - I am working with Hapijs.
EDIT: sdc is a statsd client, I use it to send the DB time :)

Unable to change a state variable in a contract

I am developing an Ethereum contract using Truffle and TestRPC. But I am unable to get a state variable to update. I think it might just be that I'm accessing it too early, but other example tests seem to work just fine and are very similar.
I have reduced my contract down to the simplest possible thing that breaks:
pragma solidity ^0.4.11;
contract Adder {
uint public total;
function add(uint amount) {
total += amount;
}
function getTotal() returns(uint){
return total;
}
}
And this is my test:
var Adder = artifacts.require("./Adder.sol");
contract('Adder', accounts => {
it("should start with 0", () =>
Adder.deployed()
.then(instance => instance.getTotal.call())
.then(total => assert.equal(total.toNumber(), 0))
);
it("should increase the total as amounts are added", () =>
Adder.deployed()
.then(instance => instance.add.call(10)
.then(() => instance.getTotal.call())
.then(total => assert.equal(total.toNumber(), 10))
)
);
});
The first test passes ok. But the second test fails because getTotal is still returning 0.
I believe that the issue is that you are always using the .call() method.
This method will, in fact, execute the code but will not save to the blockchain.
You should use the .call() method, only when reading from the blockchain or testing for throws.
Just remove the .call() in the adding function and it should work.
var Adder = artifacts.require("./Adder.sol");
contract('Adder', accounts => {
it("should start with 0", () =>
Adder.deployed()
.then(instance => instance.getTotal.call())
.then(total => assert.equal(total.toNumber(), 0))
);
it("should increase the total as amounts are added", () =>
Adder.deployed()
.then(instance => instance.add(10)
.then(() => instance.getTotal.call())
.then(total => assert.equal(total.toNumber(), 10))
)
);
});
Also, consider declaring the instance variable outside the chain of functions of the promise since the context is not shared. Consider using async/await for tests instead of promises.
var Adder = artifacts.require("./Adder.sol");
contract('Adder', accounts => {
it("should start with 0", async () => {
let instance = await Adder.deployed();
assert.equal((await instance.getTotal.call()).toNumber(), 0);
});
it("should increase the total as amounts are added", async () => {
let instance = await Adder.deployed();
await instance.add(10);
assert.equal((await instance.getTotal.call()).toNumber(), 10);
});
});