HIP-6: Enhance Streaming Features for UBI [phase-1]

HIP: 6
title: Enhance Streaming Features for UBI
author: @0xc0de4c0ffee @santisiri
status: Draft
created: 2021-04-29


This proposal describes new features that would allow to re-direct the streaming of UBI to a delegated address, among other improvements. It is based on the code from an open Pull Request from the UBI code repository.


The community has been requesting the possibility to re-direct the accrued UBI to an address that might belong to someone else who might need it more. This could also enable the creation of UBI pools that could be controlled by charitable organizations and many other potential use cases.


The implementation can be checked on https://github.com/DemocracyEarth/ubi/pull/87 where an ongoing review is happening to the contribution of 0xc0de4c0ffee.


I don’t think we need any modification at the contract level to allow people to stream their UBI, we could simply have a streaming contract people approve which would have the same effect (balance display would not be updated in real time, but charities would be able to claim the streamed amount at any time).

Adding streaming within the UBI contract would make normal use of UBI cost more gas. It would also add complexity (thus risks of bugs) to all UBI users instead of only those using this feature.

I believe contracts should do one thing and do it well (i.e. in a secure manner and not costing much gas). This proposal goes against this principle.

It would also be important to show the impact on gas cost of this proposal.

I would think it would be way better to make a contract allowing this streaming (note that it wouldn’t really require a DAO proposal but could for example if part of a request for payment).


Is it like approving spending limits ?

Would it be possible to split the stream though, %50 to charity A, %50 to charity B?

1 Like

This is primary stream directly from PoH address’s accruing rate to any PoH/EoA/contract addresses.

Yes, it’ll cost lil more gas compared to current normal transfer tx but it’ll also cut huge gas cost for receivers/charity by merging all incoming streams into single claimable stream, and it’ll have realtime balance update even for non PoH address.

That’d be secondary prepaid streams from/to any EOA/contracts… but receivers have to batch claim multiple incoming streams and it won’t show realtime balance updates. We can fix both for lil extra gas cost + more for external contract calls…

We can chart gas cost, optimize codes and test everything per line before calling auditors.

Main reason for adding primary/secondary streams is to slowdown UBI velocity and make it work with cancellable subscription/recurring payments. We can settle final features that’ll be implemented on main UBI contract to make it easier for external contracts to buidl on top.

Here’s same code feature implemented using struct instead of normal mapping for readability/gas.

It is perfectly possible to to have primary streams with an external contract (approve the streaming contract for an amount greater than your current balance), particularly for charities where there isn’t any risk of users cancelling their streams retrospectively.
Changing the base layer to implement an application is a bad practice and here will lead to a significant gas costs to all the users to slightly reduce it for a minority.
For me it looks like over-engineering.

I understand minimalist/clean base layer argument but we need at least primary/secondary stream types in there so external contracts can buidl on top of that…

We can handle same logic/features in external contracts but that’ll push-up more gas cost for minority users/external contracts. && they’re not going to be minority forever if we expand UBI utility horizon.

e.g, Proof Of Human/Artist’ NFT store paying artists and fanclub membership with UBI streams. I made a prototype for that, it’ll be near 2x gas cost for external contracts implementing same features.

Current implementation :

  function transfer(address _recipient, uint256 _amount) public returns (bool) {
    uint256 newSupplyFrom;
    if (accruedSince[msg.sender] != 0 && proofOfHumanity.isRegistered(msg.sender)) {
        newSupplyFrom = accruedPerSecond.mul(block.timestamp.sub(accruedSince[msg.sender]));
        totalSupply = totalSupply.add(newSupplyFrom);
        accruedSince[msg.sender] = block.timestamp;
    balance[msg.sender] = balance[msg.sender].add(newSupplyFrom).sub(_amount, "ERC20: transfer amount exceeds balance");
    balance[_recipient] = balance[_recipient].add(_amount);
    emit Transfer(msg.sender, _recipient, _amount);
    return true;

Current PR using normal mapping… there’s still room for gas optimization and better time/value equation.

  function transfer(address _recipient, uint256 _amount) public returns (bool) {
    uint256 _accrued;
    if(activated[msg.sender] && proofOfHumanity.isRegistered(msg.sender)) {
      _accrued = (block.timestamp - accruedSince[msg.sender]) * ((accruedPerSecond + stream[address(0)][msg.sender]) - stream[msg.sender][address(0)]);
    } else if(stream[address(0)][msg.sender] != 0) {
      _accrued = ((block.timestamp - accruedSince[msg.sender]) * stream[address(0)][msg.sender]);
    balance[msg.sender] = (balance[msg.sender] + _accrued).sub(_amount, "ERC20: transfer amount exceeds balance");
    balance[_recipient] += _amount;
    accruedSince[msg.sender] = block.timestamp;
    emit Transfer(msg.sender, _recipient, _amount);
    return true;

Actual gas cost for transfer isn’t that big (vs feature) after removing total supply and adding primary stream check during transfer.

We can see it’s almost same with (now - lastTime) * accruedRate Vs. (now - lastTime) * ((accruedRate + inFlow) - outFlow) logic. I’m ok with rewriting and testing multiple implementations to optimize/save last wei from gas cost. :vulcan_salute:

At a high level, it makes sense to include this change in the core contract. Gasless streaming for UBI is an important feature on Ethereum where gas prices are a regressive tax that creates a barrier for entry for many people. Only the UBI contract has the authority to securely allocate streams to different addresses. Doing so opens up many use cases: supporting philanthropic organizations has been emphasized, but also family pooling of resources, individual budgeting, for profit services, etc… It could even be a building block for bridging UBI to L2 or sidechains where the UBI could function as a currency.

All that said, the size of this change warrants significant care and the final proposal should include a clear accounting for what the costs are (gas costs as well as upgrading and maintenance) so we can have an informed vote. I’ve been reviewing and analyzing the code and have found that even if we switch back to SafeMath, this change should impact frequent operations like transfer +/- 2%.

If streaming (cleaned up) makes it to mainnet, I’ll stream 10% of my UBI back to the DAO. :droplet::slightly_smiling_face:


We can save some more gas cost in transfer function if we settle HIP-1 for immutable 24 UBI per day, so POH accrued rate will be part on incoming primary stream.

I think we should focus on lowering gas cost for external users/contracts & exchanges instead of internal PoH verified users accruing UBI. On long run they’ll spend more gas in transfer, and PoH EoAs will be interacting with those external contracts more than others.

  if(transfer amount > user balance) {
      check and add accrued stream balance

  transfer UBI

That’ll also help with transition from current accruedSince using same variable for timer and PoH stream activation marker to use different storage slots/variables for both.

++ Thanks @bebopster for pointing transition/upgrade pattern problem in review and his minimal branch.

1 Like

Hey team, just checking if there were any other developments on this front.

Just would like to point out that I would love to have the ability to set a timeframe for the stream to be redirected back to the original account i.e. “stream to address B for two weeks”.

1 Like

We’re stuck at how to handle upgradable pattern, and lowering gas cost for everyone…

That feature is kinda impossible without external transaction to trigger stop stream in current model. We’ve to loop all incoming/outgoing transactions for that to happen, that’ll max out gas cost on mainnet.