Skip to main content

_distributeToPayoutSplitsOf

Contract: JBPayoutRedemptionPaymentTerminal​‌

Pays out splits for a project's funding cycle configuration.

Definition

function _distributeToPayoutSplitsOf(
uint256 _projectId,
uint256 _domain,
uint256 _group,
uint256 _amount,
uint256 _feeDiscount
) private returns (uint256 leftoverAmount, uint256 feeEligibleDistributionAmount) { ... }
  • Arguments:
    • _projectId is the ID of the project for which payout splits are being distributed.
    • _domain is the domain of the splits to distribute the payout between.
    • _group is the group of the splits to distribute the payout between.
    • _amount is the total amount being distributed.
    • _feeDiscount is the amount of discount to apply to the fee, out of the MAX_FEE.
  • The function is private to this contract.
  • The function returns: the leftover amount if the splits don't add up to 100%
  • The function returns:
    • leftoverAmount is leftover amount if the splits don't add up to 100%.
    • feeEligibleDistributionAmount is the amount distributed to splits from which fees can be taken.

Body

  1. Save the passed in amount as the leftover amount that will be returned. The subsequent routine will decrement the leftover amount as splits are settled.

    // Set the leftover amount to the initial amount.
    leftoverAmount = _amount;
  2. Keep a reference to the leftover percents.

    // The total percentage available to split
    uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;

    Library references:

  3. Get a reference to payout splits for the current funding cycle configuration of the project.

    // Get a reference to the project's payout splits.
    JBSplit[] memory _splits = splitsStore.splitsOf(_projectId, _domain, _group);

    Internal references:

    External references:

  4. Loop through each split.

    // Transfer between all splits.
    for (uint256 _i; _i < _splits.length; ) { ... }
    1. Get a reference to the current split being iterated on.

      // Get a reference to the mod being iterated on.
      JBSplit memory _split = _splits[_i];
    2. Get a reference to the payout amount that should be sent to the current split. This amount is the total amount multiplied by the percentage of the split, which is a number out of 10000000.

      // The amount to send towards the split.
      uint256 _payoutAmount = _split.percent == leftoverPercentage
      ? leftoverAmount
      : PRBMath.mulDiv(_amount, _split.percent, JBConstants.SPLITS_TOTAL_PERCENT);

      Library references:

    3. Decrement the leftover percentage.

      // Decrement the leftover percentage.
      leftoverPercentage -= _split.percent;
    4. If there's at least some funds to send to the payout, determine where they should go, making sure to only debit a fee if the funds are leaving this contract and not going to a feeless terminal. If the split has an allocator set, send the funds to its allocate function, passing along any relevant params. Otherwise if a projectId is specified in the split, send the payout to that project. Add to the project's balance if the split has a preference to do so, otherwise send a payment and use the split's beneficiary as the address that should receive the project's tokens in return, or use the message sender if a beneficiary wasn't provided. If no project was specified, send the funds directly to the beneficiary address from the split if one was provided. If the split didn't give any routing information, send the amount to the messag sender. Decrement the leftoverAmount once the split is settled.

      // The payout amount substracting any applicable incurred fees.
      uint256 _netPayoutAmount;

      if (_payoutAmount > 0) {
      // Transfer tokens to the split.
      // If there's an allocator set, transfer to its `allocate` function.
      if (_split.allocator != IJBSplitAllocator(address(0))) {
      // If the split allocator is set as feeless, this distribution is not eligible for a fee.
      if (
      _feeDiscount == JBConstants.MAX_FEE_DISCOUNT ||
      isFeelessAddress[address(_split.allocator)]
      )
      _netPayoutAmount = _payoutAmount;
      // This distribution is eligible for a fee since the funds are leaving this contract and the allocator isn't listed as feeless.
      else {
      unchecked {
      _netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
      }

      // This distribution is eligible for a fee since the funds are leaving the ecosystem.
      feeEligibleDistributionAmount += _payoutAmount;
      }

      // Trigger any inherited pre-transfer logic.
      _beforeTransferTo(address(_split.allocator), _netPayoutAmount);

      // Create the data to send to the allocator.
      JBSplitAllocationData memory _data = JBSplitAllocationData(
      token,
      _netPayoutAmount,
      decimals,
      _projectId,
      _group,
      _split
      );

      // Trigger the allocator's `allocate` function.
      // If this terminal's token is ETH, send it in msg.value.
      _split.allocator.allocate{value: token == JBTokens.ETH ? _netPayoutAmount : 0}(_data);

      // Otherwise, if a project is specified, make a payment to it.
      } else if (_split.projectId != 0) {
      // Get a reference to the Juicebox terminal being used.
      IJBPaymentTerminal _terminal = directory.primaryTerminalOf(_split.projectId, token);

      // The project must have a terminal to send funds to.
      if (_terminal == IJBPaymentTerminal(address(0))) revert TERMINAL_IN_SPLIT_ZERO_ADDRESS();

      // Save gas if this contract is being used as the terminal.
      if (_terminal == this) {
      // This distribution does not incur a fee.
      _netPayoutAmount = _payoutAmount;

      // Send the projectId in the metadata.
      bytes memory _projectMetadata = new bytes(32);
      _projectMetadata = bytes(abi.encodePacked(_projectId));

      // Add to balance if prefered.
      if (_split.preferAddToBalance)
      _addToBalanceOf(_split.projectId, _netPayoutAmount, false, '', _projectMetadata);
      else
      _pay(
      _netPayoutAmount,
      address(this),
      _split.projectId,
      (_split.beneficiary != address(0)) ? _split.beneficiary : msg.sender,
      0,
      _split.preferClaimed,
      '',
      _projectMetadata
      );
      } else {
      // If the terminal is set as feeless, this distribution is not eligible for a fee.
      if (
      _feeDiscount == JBConstants.MAX_FEE_DISCOUNT || isFeelessAddress[address(_terminal)]
      )
      _netPayoutAmount = _payoutAmount;
      // This distribution is eligible for a fee since the funds are leaving this contract and the terminal isn't listed as feeless.
      else {
      unchecked {
      _netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
      }

      feeEligibleDistributionAmount += _payoutAmount;
      }

      // Trigger any inherited pre-transfer logic.
      _beforeTransferTo(address(_terminal), _netPayoutAmount);

      // If this terminal's token is ETH, send it in msg.value.
      uint256 _payableValue = token == JBTokens.ETH ? _netPayoutAmount : 0;

      // Send the projectId in the metadata.
      bytes memory _projectMetadata = new bytes(32);
      _projectMetadata = bytes(abi.encodePacked(_projectId));

      // Add to balance if prefered.
      if (_split.preferAddToBalance)
      _terminal.addToBalanceOf{value: _payableValue}(
      _split.projectId,
      _netPayoutAmount,
      token,
      '',
      _projectMetadata
      );
      else
      _terminal.pay{value: _payableValue}(
      _split.projectId,
      _netPayoutAmount,
      token,
      _split.beneficiary != address(0) ? _split.beneficiary : msg.sender,
      0,
      _split.preferClaimed,
      '',
      _projectMetadata
      );
      }
      } else {
      // Keep a reference to the beneficiary.
      address payable _beneficiary = _split.beneficiary != address(0)
      ? _split.beneficiary
      : payable(msg.sender);

      // If there's a full discount, this distribution is not eligible for a fee.
      // Don't enforce feeless address for the beneficiary since the funds are leaving the ecosystem.
      if (_feeDiscount == JBConstants.MAX_FEE_DISCOUNT)
      _netPayoutAmount = _payoutAmount;
      // This distribution is eligible for a fee since the funds are leaving this contract and the beneficiary isn't listed as feeless.
      else {
      unchecked {
      _netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
      }

      feeEligibleDistributionAmount += _payoutAmount;
      }

      // If there's a beneficiary, send the funds directly to the beneficiary. Otherwise send to the msg.sender.
      _transferFrom(address(this), _beneficiary, _netPayoutAmount);
      }

      unchecked {
      // Subtract from the amount to be sent to the beneficiary.
      leftoverAmount = leftoverAmount - _payoutAmount;
      }
      }

      Library references:

      Internal references:

      External references:

    5. Emit a DistributeToPayoutSplit event for the split being iterated on with the relevant parameters.

      emit DistributeToPayoutSplit(
      _projectId,
      _domain,
      _group,
      _split,
      _netPayoutAmount,
      msg.sender
      );

      Event references:

    6. Increment the loop counter in the most gas efficient way.

      unchecked {
      ++_i;
      }