Recently, I and another Steemit user were discussing (I don't remember exactly why) what user accounts were created on '2017-10-11' by anonsteem. Those of you who don't know, anonsteem is an account that will instantly create Steemit accounts for you at the expense of 5 STEEM. This tutorial is not about that though.
As we were asking this, we realized that the API for account history is just a stream of information. It is not easy to filter or query. As we hacked away at it, I realized that this would be a valuable tutorial to give including some lessons learned.
What will you learn
- Fetching Steemit Account history with
nodejsapi - Steemit api with promises
- Typescript usage of nodejs API
- Generator functions and iterating results from asynchronous calls using a
forloop - Knowledge of what the account history object looks like
- Manipulating the account history object
Requirements
This tutorial is written for Typescript. To use typescript you really just need nodejs.
Setup
In my Github Repository, there is a module I created for this tutorial for this called steemit-account-history. Please clone this project and open the following file in your IDE: steemit-account-history/src/manage.ts(https://github.com/r351574nc3/steem-bot-examples/blob/topic/steemit-account-history/steemit-account-history/src/manage.ts)
Account History
Account history are transactions that happen to your account like when you change your profile picture or permissions on your keys. Here's an example of what the JSON looks like:
{"trx_id":"092fb94e7ba1b3a66fda64d3fbf4262d5d1c686d","block":16255559,"trx_in_block":0,"op_in_trx":0,"virtual_op":0,"timestamp":"2017-10-12T04:27:03","op":["account_create_with_delegation",{"fee":"6.000 STEEM","delegation":"0.000000 VESTS","creator":"anonsteem","new_account_name":"tapcrypto","owner":{"weight_threshold":1,"account_auths":[],"key_auths":[["STM5ukwG1g39NvtCuG9EfpLWMULFDVCcWUrpsCzufBSebHDmq9RAh",1]]},"active":{"weight_threshold":1,"account_auths":[],"key_auths":[["STM8DFbKF5U2pXScbfm6V6Ro4V52e87Das5RuZtL47JXYcR1cD75d",1]]},"posting":{"weight_threshold":1,"account_auths":[],"key_auths":[["STM7QcUVhUyae3Ec8UFAiqyqFBDb69MX91wn8xmBwk636yxHRCRY9",1]]},"memo_key":"STM5VrJZ4LJ4YZEdkXDk32DqeM7SAV5SFRhfXreUYaL8oYDbHxbBM","json_metadata":"","extensions":[]}]}
getAccountHistory
steem.api.getAccountHistoryAsync(account, from, limit)
My first mistake here was thinking from is a date. It's actually the start index or record number. The reason is the amount of account history can be really HUGE! limit is given to reduce the amount of data which then forces the need to page through data. What you would normally do is start at record 0 and page through the data until you find what you're looking for.
steem.api.getAccountHistoryAsync(account, 0, 100)
steem.api.getAccountHistoryAsync(account, 100, 100)
steem.api.getAccountHistoryAsync(account, 200, 100)
The above shows how multiple calls can be made to page 3 times through 100 account history records (300 in total).
That is pretty much what you would have to do. You can't just retrieve all account history records. Retrieving so much information isn't reliable. In many cases, you will run into a Gateway Timeout. It's best to avoid those; therefore, paging is our only option.
Iterating through every record
Suppose there is some information and you don't know what record you're looking for. How do you query it?
First, what I like to do is find out what the absolute last record is. This is actually not a difficult task. I created a function for this:
async function find_last_record(user: string): Promise<number> {
let retval = 0;
await steem.api.getAccountHistoryAsync(user, Number.MAX_SAFE_INTEGER, 1)
.then((history: any[]) => {
const record = history[0];
retval = record[0];
});
return retval;
}
You can see I used a really huge number (Number.MAX_SAFE_INTEGER) .
Now, I can safely page through the data knowing just how far I need to go.
for (let start = step; start < end; start = start + step) {
await steem.api.getAccountHistoryAsync(user, start, step)
end represents that upper bound I discovered by grabbing the last record. Now, what I need are all the records after 2017-10-11. To get these, I'm going go iterate through all the records and skip them until I arrive at the first record I care about. Here's my full loop.
for (let start = step; start < end; start = start + step) {
await steem.api.getAccountHistoryAsync(user, start, step)
.each((history: any[]) => {
if (!history || history.length < 1) {
return;
}
const record = history[1];
const on_date = moment(date);
const ts = moment(record.timestamp);
if (ts.startOf("day").isAfter(on_date.startOf("day"))) {
retval.push(record);
}
});
}
You can see that I check for ts.startOf("day").isAfter(on_date.startOf("day") to determine if the record is important or not. If it is, I push it onto a buffer.
I then stuff all this goodness into a generator function
async function* seek(user: string, date: string): any {
const step = 1000;
const retval: any[] = [];
let end = 0;
await find_last_record(user).then((result: number) => {
end = result;
});
for (let start = step; start < end; start = start + step) {
await steem.api.getAccountHistoryAsync(user, start, step)
.each((history: any[]) => {
if (!history || history.length < 1) {
return;
}
const record = history[1];
const on_date = moment(date);
const ts = moment(record.timestamp);
if (ts.startOf("day").isAfter(on_date.startOf("day"))) {
retval.push(record);
}
});
}
yield* retval;
}
The reason I did this is because I want to be able to loop through my account history records with a for loop.
async function main() {
const result = seek("anonsteem", "2017-10-11");
for await (const item of result) {
console.log("Record %s", JSON.stringify({ operation: item.op[0], timestamp: item.timestamp }));
}
}
I slimmed down the records with { operation: item.op[0], timestamp: item.timestamp } because I just wanted to know what the operation was. I could always add more details if I want later. My results end up looking like this:
Record {"operation":"transfer","timestamp":"2017-10-12T04:02:54"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T04:03:09"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T04:20:21"}
Record {"operation":"transfer","timestamp":"2017-10-12T04:25:42"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T04:27:03"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T05:03:03"}
Record {"operation":"transfer","timestamp":"2017-10-12T06:43:45"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T06:44:09"}
Record {"operation":"transfer","timestamp":"2017-10-12T07:17:03"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T07:17:57"}
Record {"operation":"transfer","timestamp":"2017-10-12T10:15:00"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T10:16:15"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T10:29:27"}
Record {"operation":"account_create_with_delegation","timestamp":"2017-10-12T14:47:42"}
Thank you
I hope you enjoyed this and that it was helpful to you.
Other Tutorials in this Series
Posted on Utopian.io - Rewarding Open Source Contributors