Making It Even Better

To address the challenges mentioned above, we added metadata into the proof for each payee, and we introduced the idea of “payment cycles” to the payment pool.

Payment Cycles

Within the payment pool smart contract we keep track of the payment cycle delineated by the submission of each Merkle root. The submission of a Merkle root to the payment pool by the contract owner signifies the end of the current payment cycle, and a new payment cycle begins.

Within the payment pool smart contract we maintain a mapping property that maps the payment cycle number to the Merkle root that governs that payment pool for that payment cycle. This way, when the paymentPool.withdraw() function is called with a proof, if we know the payment cycle the proof was generated against, we can validate the proof against the correct Merkle root.

This allows payees to use old proofs to claim their payments. It does mean, however, that a proof is tied to a particular amount of tokens. You cannot claim more tokens than the amount of tokens used in the generation of the leaf node’s hash for the proof that is supplied.

As long as the payment pool is keeping track of how many tokens have been withdrawn for each payee, we can make sure to deduct from the cumulative amount of tokens that are allocated to the payee, the amount of tokens that they have already withdrawn to arrive at the amount of tokens available for a given proof/address pair.

Proof Metadata

Another challenge to overcome is how to withdraw a token amount that is less than the amount of tokens used to create the proof. Additionally, how can we make it easier for the user to associate the proof to a particular payment cycle, so that the correct Merkle root can be used to validate the withdrawal request?

For these challenges we have introduced the idea of attaching metadata to the proof itself. What this means is that we can incorporate into the proof both the payment cycle number and the cumulative amount of tokens owed to the payee that was used to generate the payee’s payment leaf node in the Merkle tree. As a result, the payee invokes the paymentPool.withdraw() function with an amount up to, but not exceeding the amount of tokens available for the proof, as well as the proof itself.

Simple, right? The payee is calling paymentPool.withdraw() with the number of tokens they want and a special key that works just for them to unlock those tokens from the payment pool.

So here’s how that works: as I mentioned above, the proof is really just an array of the great-aunt & uncle hashes that has been serialized into a hexadecimal format. To include metadata in the proof, what we do is simply add a couple extra items to that proof array as part of the code library that we use to retrieve a proof for a node in the Merkle tree.

Specifically, we are adding the payment cycle number that corresponds to the Merkle root (we can get that by calling paymentPool.numPaymentCycles() on the paymentPool smart contract before the Merkle root is submitted to the contract) and we are adding the cumulative amount of tokens the payee is allowed to withdraw. Within the paymentPool.withdraw() function what we do is we strip the metadata off of the proof so that the paymentPool.withdraw() function knows the payment cycle the proof pertains to, as well as the amount of tokens that is part of the leaf node in the Merkle tree for this payee.