(Note: this started out as a Reddit post, but I need a backup - so here it is.)
LivePeer is using a “Merkle air drop” for their tokens, a concept I’ve first seen described by Richard Moore in an article about 4 months ago.
It’s an interesting one, since the mechanism is op-in, preventing some spam. (LivePeer’s particular distribution rules will eventually result in spam, when the “slow start” period ends; but that’s IMO and OT.)
To receive their tokens, one:
- goes to an IPFS-hosted page;
- downloads the 100-megabyte “list” (actually a merkle tree) of eligible accounts;
- looks themselves up in the “list”;
- submits a proof of being in the “list” to an on-chain contract.
This is rather streamlined when using MetaMask and a fast connection.
But what if one of the two is unavailable at the point where the private key resides?
Checking eligibility / generating the proof
Go to the proof generation page (replace the account address with yours):
http://gateway.ipfs.io/ipfs/QmUvAFQZxbgMCqCMLUSj2kQBpZUhj5qi8KhmPDVvv9apfj?address=…
Or, if you have a local IPFS node (perhaps with an IPFS Companion plugin):
http://127.0.0.1:8080/ipfs/QmUvAFQZxbgMCqCMLUSj2kQBpZUhj5qi8KhmPDVvv9apfj?address=…
(Note that you can’t edit the on-page “Input Data URL” without fiddling with the page source.)
Click “Load” to generate the merkle proof. It will be of the form:
0x6165d6cf7369700441b712144d8a84c953dcbe9920b088f970eb502b2f6f19166d721de3c88e901947bc24e1eadd1e6ca0330effaaae234eed46b986487aff49e14b9bfb3e913b61dc728d1b9723e2878bda2696f52df657c02f8f70a07e5b308b55289da0998744c56c9fc120819770f6f677eb896b35c46643749ea3a0ac56320a0d77462ffa8f65e00e4060ad46fdf4aaf8c0b3f889070b392916a9e57a11f83b842b1e22bb3fe0581140f1654ec33c29561da539fad6c0279cd9a3449eb0a582c0d5dd24f64ad0c0a00aa3e4b96d00a785b9b96b75b1f11012a75439f3036a8661a4743e25e01b4ecde186e081416a4b18e0ce671f46743ab8b919433ba36ad2fa4331c9ebbfc19b4454115240531aa6d6452c7f4b98ce89bcc0c0092dfe11580dc8fbcb38f8e6aa79ecc6b66cf8cf3d1705fd141c107681d1025532c9d5e5a1d50a2303f03e37593fe8ae6dc3d66097300d2f7a68e589c330b67a46a61936a4b0d7b0b35da7cc711fd2a3da189c9a1ef3dc07789e2675fc995cb07cf786d6300b12f87dc96cfd5c35a78b610f9ab9d23e512bbd2550e830899460a91c83eb65a228eb7a0b00238ebaf97019fb6968046b5f76547c90e578cb055a00633981f5ac1301ba36888811291fb57e2c34d2abe5d126f68cf5ea3e44133ade8a7721f2672c9930573127cc98dd63e59a49aabb846169543380ca4665a7f47dd96fd46f6243507e7da5820a26522e3ce2223234413087f9d83baedf0c244e356f5933ce5432faf5ec0e784e42c9f49ec57c0843998953de393caabfd906c9f78c8a878396323c59b1f49c7635610d774782e7e18a0feb43093b7a70f7ad98340accccb9167971b92fe9762830d3f20908fb10fafa9929adfde2f65a4e90327ebc1f7460f55592404a5bd804dd4060fbbaf11a6bdc0557c70c812ec5cd9aa8007ed51e74a86b2f0aceaa39987ce1ee5e1150dc99db6336cca1bf9a005c776d0e3fc3
Stitching together transaction data
To claim the token, a transaction must be sent to the MerkleMine contract.
I’ve previously used a MetaMask account to generate a sample transaction:
Function: generate(address _recipient, bytes _merkleProof) ***
MethodID: 0x2c84bfa6
[0]: 000000000000000000000000f75b78571f6563e8acf1899f682fb10a9248cce8 <-- claiming account address
[1]: 0000000000000000000000000000000000000000000000000000000000000040 <-- proof data[] location pointer
[2]: 0000000000000000000000000000000000000000000000000000000000000280 <-- data length
[3]: ... <-- start of data
[0]
is self-explanatory; [1]
will always be a pointer to the same
location - 0x40
, where the proof data length [2]
is; [3]
and all
the way to the end is the actual proof data (0x6165d6cf...
from the
previous step).
The item in position [2]
will likely require editing, as the length of
proof data depends on the location of a node in the merkle tree.
With the 0x
prefix, the proof data string is 1410 characters; so -
1408 without the prefix. Each character represents a nibble (half a
byte), so that’s 704 bytes. 704
decimal is 0x2c0
hexadecimal.
A Python console helper:
For my non-MetaMask account, the transaction data will look like:
Function: generate(address _recipient, bytes _merkleProof) ***
MethodID: 0x2c84bfa6
[0]: 00000000000000000000000004b3faaa7c8127a80eb6d24672cfdaf4aecabbf8 <-- claiming account address
[1]: 0000000000000000000000000000000000000000000000000000000000000040 <-- proof data[] location pointer
[2]: 00000000000000000000000000000000000000000000000000000000000002c0 <-- data length
[3]: 6165d6cf7369700441b712144d8a84c953dcbe9920b088f970eb502b2f6f1916 <-- start of data
[4]: ...
[24]: 1e74a86b2f0aceaa39987ce1ee5e1150dc99db6336cca1bf9a005c776d0e3fc3 <-- end of data
I didn’t bother writing code to generate the transaction data to-be-submitted, but stitched it together in a text editor. It’s of the form:
0x2c84bfa600000000000000000000000004b3faaa7c8127a80eb6d24672cfdaf4aecabbf8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c06165d6cf7369700441b712144d8a84c953dcbe9920b088f970eb502b2f6f19166d721de3c88e901947bc24e1eadd1e6ca0330effaaae234eed46b986487aff49e14b9bfb3e913b61dc728d1b9723e2878bda2696f52df657c02f8f70a07e5b308b55289da0998744c56c9fc120819770f6f677eb896b35c46643749ea3a0ac56320a0d77462ffa8f65e00e4060ad46fdf4aaf8c0b3f889070b392916a9e57a11f83b842b1e22bb3fe0581140f1654ec33c29561da539fad6c0279cd9a3449eb0a582c0d5dd24f64ad0c0a00aa3e4b96d00a785b9b96b75b1f11012a75439f3036a8661a4743e25e01b4ecde186e081416a4b18e0ce671f46743ab8b919433ba36ad2fa4331c9ebbfc19b4454115240531aa6d6452c7f4b98ce89bcc0c0092dfe11580dc8fbcb38f8e6aa79ecc6b66cf8cf3d1705fd141c107681d1025532c9d5e5a1d50a2303f03e37593fe8ae6dc3d66097300d2f7a68e589c330b67a46a61936a4b0d7b0b35da7cc711fd2a3da189c9a1ef3dc07789e2675fc995cb07cf786d6300b12f87dc96cfd5c35a78b610f9ab9d23e512bbd2550e830899460a91c83eb65a228eb7a0b00238ebaf97019fb6968046b5f76547c90e578cb055a00633981f5ac1301ba36888811291fb57e2c34d2abe5d126f68cf5ea3e44133ade8a7721f2672c9930573127cc98dd63e59a49aabb846169543380ca4665a7f47dd96fd46f6243507e7da5820a26522e3ce2223234413087f9d83baedf0c244e356f5933ce5432faf5ec0e784e42c9f49ec57c0843998953de393caabfd906c9f78c8a878396323c59b1f49c7635610d774782e7e18a0feb43093b7a70f7ad98340accccb9167971b92fe9762830d3f20908fb10fafa9929adfde2f65a4e90327ebc1f7460f55592404a5bd804dd4060fbbaf11a6bdc0557c70c812ec5cd9aa8007ed51e74a86b2f0aceaa39987ce1ee5e1150dc99db6336cca1bf9a005c776d0e3fc3
Assembling/signing/submitting the transaction
This depends on the software used - MEW/MyCrypto in offline mode, geth
console, parity
+ethers.js
, web3.py
+Infura… In general, the
transaction will have:
from
: the claiming account’s address;to
:0x8e306b005773bee6bA6A6e8972Bc79D766cC15c8
;data
: the “stitched together” data above (starting with0x2c84bfa6
);gas
: 200000 was enough in both cases for me - might work for you, too, if your proof’s length is around the same 700 bytes;gas_price
: the usual: tricky (read below);nonce
: the usual: the next number after the last transaction’snonce
for a given account.
The transaction is not currently time-critical, so anything that has a chance of getting confirmed within 3 hours would be an “OK” gas price. Check out EthGasStation’s recently-improved transaction pool report page, or use its general front-page estimate.