Repositories
- https://github.com/tdreid/tp
- https://github.com/tj/commander.js/
- https://github.com/steemit/steem-js
Remixed from a photo by Larm Rmah on Unsplash
What Will I Learn?
This tutorial explains one way to use JavaScript for creation of command line interfaces (CLI) that interact with the Steem blockchain. Command line tools are useful for making scripts and bots as well as great utilities for ad hoc personal tasks.
As an example this tutorial will walk through making a CLI that transfers a specified amount of Steem Based Dollars (SBD) every so often to a particular account. Use cases for a command line tool like this include:
- Making periodic interest or dividend payments;
- Buying a service on a regular basis;
- Sweeping returns from one account to another.
Topics covered include how to:
- Use the Commander package to create command line tools following established conventions;
- Define a flag to switch between using a Steem testnet and the live blockchain (steem-js);
- Make a recurring transfer of SBD at set intervals and stopping when a certain total has been transferred (again, steem-js).
Requirements
You will need:
- Text editor;
- Comfort using a terminal and command line;
- Steady grasp of JavaScript with Node.js and npm already installed;
- Familiarity with ES6 arrow function syntax and template literals.
It's also recommended to use an Integrated Development Environment (IDE) and git, though these specifics will not be covered.
Difficulty
- Intermediate
Tutorial Contents
Our goal will be to create a CLI that follows familiar conventions, consisting of a command name, some flags that can be set in long or short form, and a few required parameters.
The idea is to accomplish the exact same transfer as a user would make from the SteemIt wallet with a few twists:
- Skip the graphical user interface;
- Repeat the same transaction every so often with no further intervention;
- Stop the transfer machine once it has hit a certain limit so we don't unintentionally spend all our cryptocurrency;
- Provide a test flag so we can experiment safely with play money.
The Steemit wallet transfer prompt shows us most of the input involved. Upon hitting next we would be prompted to authenticate. So our command line program will likewise require a way to provide an active key.
If our program already existed we might expect it to describe itself like this:
Usage: tp [options] <payee> <amount> <limit> <memo> <interval>
Options:
-p, --payor <payor> Payor account on the steem blockchain
-w, --wif <wif> Active key for payor account
-t, --test Test mode. Direct transaction(s) to wss://testnet.steem.vc
-v, --version output the version number
-h, --help output usage information
Here the name tp
is arbitrary. You can choose any name for your command as long as it does not conflict with another on your system. I chose this one because it's short to type and easy for me to remember.
Maybe it stands for "transfer periodically" or "@tdre's protocol". Choose a name that suits your needs.
Let's begin.
Create a new folder and switch to it.
$ mkdir tp
$ cd tp
Next use npm's built in utility to walk you through creating a package.json file.
$ npm init
For starters it's okay to accept most of the defaults, but when prompted for entry point let's use the name we've chosen for our command—just to keep things consistent.
{
"name": "tp",
"version": "0.1.0",
"description": "",
"main": "tp.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "@tdre (https://steemit.com/@tdre)",
"license": "MIT"
}
Your output should resemble mine above but of course will have a different default author. All of the defaults for npm init
are configurable. Check out the foregoing documentation links if you want to learn more nuanced options.
Next we will add the Commander.js package. This is an open source solution that makes it easy for our program to follow common interface conventions such as --version
and --help
options. Commander.js will also make it easy to define our own custom arguments, options and flags.
$ npm install commander --save
The --save
option here ensures the package.json
file will be updated. Since npm is modifying package.json
it will also add a package-lock.json
file.
This is basically a more precise representation of the dependency tree interpreted from package.json
. It guarantees consistency when your work is shared in different environments.
At this point we need to make a small manual addition to package.json
. Supplying a bin
field tells npm to add our package's executable to the PATH upon installation. Thus we want this field to point to the script we specified as main
when we ran npm init
. Like so:
{
"name": "tp",
"version": "0.1.0",
"description": "",
"bin": {
"tp": "./tp.js"
},
"main": "tp.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "@tdre (https://steemit.com/@tdre)",
"license": "MIT",
"dependencies": {
"commander": "^2.16.0"
}
}
At last it is time to create the file we've been hinting at all along and open it up in your text editor or IDE.
$ touch tp.js
Remember ultimately this script will be aliased in PATH . So, as whenever writing a CLI using Node.js, the very first thing we need in the file is a shebang directive. This will indicate the node interpreter should be invoked whenever the file is called.
#!/usr/bin/env node
After that let's enable strict mode for ES6. And pull in commander so we can access its API goodness.
#!/usr/bin/env node
'use strict';
const tp = require('commander');
From here things get very easy. With an instance of commander in play we set up the CLI by chaining commander's various configuration methods.
You can cut and paste the relevant lines here.
Commander will use the configuration we've provided to output a familiar --help
guide without any further coding. Let's handle a few niceties before moving on.
Another handy couple already built in to commander are '-V' and --version
. These will output a string configured via the version()
method. But I prefer using lowercase -v
for that flag. It just looks more consistent. We can override the default:
tp.version('0.1.0', '-v, --version')
But hey! I don't want to update the version string here and in the package.json
every time it bumps. What if I forget? And even if I remember every single time that is double the work. We could pull in theversion
field from package.json
though. So let's do this instead:
const version = require('./package.json').version;
tp.version(version, '-v, --version')
Another nice thing to do for our user is to show the usage information when the command is entered without any arguments. To do that simply call the built in help method when no arguments are parsed from the comand line:
if (tp.args.length === 0) tp.help();
Since I've illustrated code so far in snippets this is a good time to review the big picture of what we've implemented so far. You should have a directory structure that resembles this:
.
├── node_modules
│ └── commander
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── Readme.md
│ ├── index.js
│ ├── package.json
│ └── typings
│ └── index.d.ts
├── package-lock.json
├── package.json
└── tp.js
And a JavaScript file (tp.js
for me) that looks more or less like this:
You can cut and paste from this gist if you need to.
Alright. So in fewer than 20 lines of code we've succinctly defined our command line interface. Now let's make it do something useful.
The heart of a CLI written with commander is the action()
method. Here we will define a function that is called with the arguments parsed from the command line and which has access to all our options as properties of the tp
object. All it takes to accomplish this is:
You can cut and paste from this gist if you need to.
Of course we will want a function that does more than output our inputs. The mission of this CLI is to interact with the Steem blockchain. For that our go to package is going to be steem-js.
$ npm i steem --save
That package is installed. Let's pull it in to our code and write two functions that do some real work:
As always here is the gist.
And this...
Yeah. The gist.
Admittedly using individual global variables to pass information between functions isn't the best solution. Cleaning that up would be a good exercise to test your understanding. That said let's tie all this together in a way that works. As mentioned earlier commander's action()
method is exactly what we need:
Conclusion
Bringing all these concepts together here's where we end up.
File structure
.
├── node_modules
│ ├── @steemit
│ └── [full_tree_omitted_for_brevity]
│ └── commander
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── Readme.md
│ ├── index.js
│ ├── package.json
│ └── typings
│ └── index.d.ts
│ ├── steem
│ └── [full_tree_omitted_for_brevity]
├── package-lock.json
├── package.json
└── tp.js
package.json
{
"name": "tp",
"version": "0.1.0",
"description": "",
"bin": {
"tp": "./tp.js"
},
"main": "tp.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "@tdre (https://steemit.com/@tdre)",
"license": "MIT",
"dependencies": {
"commander": "^2.16.0",
"steem": "^0.7.1"
}
}
Finally on the machine where you are developing you can use a symbolic link to the root folder of your project to make your brand new command globally available.
$ npm link
If you publish your command to GitHub or a public repository users would install your new package as they would any other. For example you could install my working version of tp
this way...
$ npm i -g https://github.com/tdreid/tp
And here's a quick demonstration.
Which makes things happen in tptest's wallet on the testnet.
Note — testnet accounts are deleted after a while. That wallet may not exist when you are reading this. But it happened. So try it for yourself ;)
Proof of Authorship & Work Done
Special thanks to @almost-digital for running http://testnet.steem.vc/ and to the steemsnippets project for explaining transfers & options way back when.
Thank you for your contribution.
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Thanks for taking the time to moderate @portugalcoin. Awesome!
Hey @tdre
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!
Congratulations! This post has been upvoted from the communal account, @minnowsupport, by tdre from the Minnow Support Project. It's a witness project run by aggroed, ausbitbank, teamsteem, theprophet0, someguy123, neoxian, followbtcnews, and netuoso. The goal is to help Steemit grow by supporting Minnows. Please find us at the Peace, Abundance, and Liberty Network (PALnet) Discord Channel. It's a completely public and open space to all members of the Steemit community who voluntarily choose to be there.
If you would like to delegate to the Minnow Support Project you can do so by clicking on the following links: 50SP, 100SP, 250SP, 500SP, 1000SP, 5000SP.
Be sure to leave at least 50SP undelegated on your account.
@resteemator is a new bot casting votes for its followers. Follow @resteemator and vote this comment to increase your chance to be voted in the future!