Tired of writing new Web3
in a JS script just to check a balance? Annoyed about the friction of converting things to BigNumber, loading Contract.json
, dealing with promises just to do something simple? Well let me introduce you to seth
. Using seth
, you can easily interact with smart contracts with the speed and dexterity of the command line. It's part of a suite of tools developed by the MakerDAO team, of DAI acclaim. In this post, you're going to go from 0 to hero.
Introduction to seth
The Seth reference guide on Github is great, but it misses some lessons that won't be obvious if you're not a power user of the CLI. I'm going to explain some really simple techniques from the perspective of these use cases:
- deploying a Truffle contract
- saving a contract's deployment address
- calling methods and passing arguments
- transacting from different accounts
- converting units (numbers, eth)
Installing seth
seth
is part of the dapptools
suite, which is a super-minimal package of software (like 5kB). Taken from their Github instructions:
curl https://dapp.tools/install | sh
Deploying a Truffle contract
Seth is based on the unix philosophy, which puts files at its core. You've probably gotten used to some monolithic framework like Truffle, which puts you in the backseat while it takes care of business. Running truffle compile
, it will generate contract artifacts in the form of build/contracts/ContractName.json
. And running truffle migrate
, it will automatically take the bytecode of these contracts and deploy them.
It might seem scary, but the details aren't actually that complex to do ourselves!
But wait, what's the difference between bytecode
and deployedBytecode
? Well, when you deploy a contract in Ethereum, you're doing two things:
1) you're creating a contract, which stores its runtime code on the chain
2) you're calling the constructor
Simply put - the deployedBytecode
is just the runtime, whereas the bytecode
includes the code of the constructor.
Let's see how easy it is to deploy a contract using seth. To begin with, we're going to install jq
, a CLI tool which can parse JSON. You can find installation instructions here.
# Get the bytecode from the Truffle artifact
export CODE=$(cat build/contracts/ERC20.json | jq -r .bytecode)
Setting up seth's environment
Run the below to setup the environment variables seth needs to know:
# Connect to the local node
export ETH_RPC_URL=http://127.0.0.1:8545
# Disable below if not on Truffle
export ETH_GAS=4712388
# Use the unlocked RPC accounts
export ETH_RPC_ACCOUNTS=yes
# Get the 1st account and use it
export ETH_FROM=$(seth accounts | head -n1 | awk '{ print $1 }')
Deploying the contract
# We can write it simply
seth send --create $CODE
# Passing variables to the constructor
# In this example, the constructor is ERC20Detailed
# constructor(string name, string symbol, uint decimals)
seth send --create $CODE TokenName TOK 18
Running the above, you will get an output like so:
seth-send: Published transaction with 3227 bytes of calldata.
seth-send: 0xb330d69093fd6b19d5afecffebf6734f77ef8d7cf47dabb87ad840a7d4b8e57f
seth-send: Waiting for transaction receipt....
seth-send: Transaction included in block 36.
0x243e72b69141f6af525a9a5fd939668ee9f2b354
The last line is the contract address.
Saving a contract's deployment address
I'm not that skilled in Bash - but I do know one thing: I'm not reinventing the wheel when I want to save the deployment address. Using Sourcegraph (which is grep+git on steroids), I just searched for seth send
across all of dapphub's repos and was able to grab the line below.
ERC20_ADDR=$(seth send --create $CODE --status 2>/dev/null)
# or even terser
ERC20_ADDR=$(seth send --create $(cat build/contracts/ERC20.json | jq -r .bytecode) --status 2>/dev/null)
2>/dev/null
basically redirects STDERR (see Standard streams) to null. All of the misc logging by seth is ignored and we are left with the contract address.
If the deployment fails, there's no .catch
or burdensome Promises to deal with. The $ERC20_ADDR
variable will be empty. If we are automating this in a script, it's wise to make sure it exits early in case of failure. Check this out:
#!/bin/bash
set -ex
# Connect to the local node
export ETH_RPC_URL=http://127.0.0.1:8545
# Disable below if not on Truffle
export ETH_GAS=4712388
# Use the unlocked RPC accounts
export ETH_RPC_ACCOUNTS=yes
# Get the 1st account and use it
export ETH_FROM=$(seth accounts | head -n1 | awk '{ print $1 }')
ERC20_ADDR=$(seth send --create $(cat build/contracts/ERC20.json | jq -r .bytecode) --status 2>/dev/null)
echo -n $ERC20_ADDR > ERC20.deployment
Boom! We've just saved the address to a file that we can load later!
Calling methods and passing arguments
This is probably the best part of Seth. Calling methods on contracts couldn't be easier with this tool, because you don't have to load the JSON ABI" to interact with contracts.
For example, say we're testing this token contract and we want to mint ourselves some tokens. This is how simple it is:
# Get the contracts address
export ERC20_ADDR=$(cat ERC20.deployment)
seth send $ERC20_ADDR "mint(uint256)" 1
Likewise, if a method has a return value, you just have to show seth how to decode it. What we're passing is the method signature, but in a much simpler form than the full method - method(<types>)(<return-types>)
.
# Get the balance of our address
seth send $ERC20_ADDR "balanceOf(address)(uint)" $ETH_FROM
Transacting from different accounts
Oftentimes, we're using msg.sender
in our contract logic. When it comes time to testing, then we probably want to use different accounts for interacting. Seth makes this super easy using the ETH_FROM
variable.
We've already been using a global ETH_FROM
to begin with, but it's pretty simple to use per-call too.
export BUYER=$(seth accounts | sed -n 2p | awk '{ print $1 }')
seth send --from $BUYER --value 402343423423423 $EXCHANGE "ethToTokenSwapInput(uint256,uint256)" 3 1000000000000
The above example calls ethToTokenSwapInput
on a Uniswap exchange, from a specific account I've allocated called the $BUYER. It also makes use of --value
to send some ether with the transaction.
Converting units (numbers, eth)
This brings me to my last important teaching on Seth - converting units. In Ethereum, we deal with uint256
as our default, and ether and other tokens are usually designed to be shown with 18 decimal places. Seth makes it easy to convert between these different formats.
seth --to-dec 0000000000000cb803 eth
# 833539
seth --from-wei <decimal-amount>
# 0.123
seth --to-wei <decimal-value>
# 123
seth --to-ascii 67646179206d617465
# ... ;)
Conclusion
I think seth
is a fantastic investment as a developer in Ethereum's ecocsystem. Given the inherent complexity in a whole new platform of tooling, seth
is a pragmatic approach to inspecting, experimenting, and iterating on blockchain software.
☕️ If you liked this guide, consider following me on Twitter.
😎 I enjoy learning and teaching, and do it regularly. Here's some links you might find interesting too:
- my awesome-solidity-patterns repo
- How to link libraries into Solidity contracts generated by sol-compiler
- my blog, Regular Fascination, with a bit more in-depth content
- @liamzebedee for interesting links/discussions in tech
Top comments (0)