(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:

>>> proof = '0x6165d6cf...'
>>> l = (len(proof)-2)/2
>>> datalen = hex(int(l))
>>> print(datalen)
0x2c0

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 with 0x2c84bfa6);
  • 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’s nonce 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.

Published July 19 2018 by veox
Permalink