Skip to main content


Contract: JBSingleTokenPaymentTerminalStore​‌

Interface: IJBSingleTokenPaymentTerminalStore

Records newly redeemed tokens of a project.

Redeems the project's tokens according to values provided by a configured data source. If no data source is configured, redeems tokens along a redemption bonding curve that is a function of the number of tokens being burned.

The msg.sender must be an IJBSingleTokenPaymentTerminal.


function recordRedemptionFor(
address _holder,
uint256 _projectId,
uint256 _tokenCount,
string memory _memo,
bytes memory _metadata
returns (
JBFundingCycle memory fundingCycle,
uint256 reclaimAmount,
JBRedemptionDelegateAllocation[] memory delegateAllocations,
string memory memo
) { ... }
  • Arguments:
    • _holder is the account that is having its tokens redeemed.
    • _projectId is the ID of the project to which the tokens being redeemed belong.
    • _tokenCount is the number of project tokens to redeem, as a fixed point number with 18 decimals.
    • _memo is a memo to pass along to the emitted event.
    • _metadata are bytes to send along to the data source, if one is provided.
  • The resulting function overrides a function definition from the JBSingleTokenPaymentTerminalStore interface.
  • The function returns:
    • fundingCycle is the funding cycle during which the redemption was made.
    • reclaimAmount is the amount of terminal tokens reclaimed, as a fixed point number with 18 decimals.
    • delegateAllocations is the amount to send to delegates instead of sending to the beneficiary.
    • memo is a memo that should be passed along to the emitted event.


  1. Get a reference to the project's current funding cycle.

    // Get a reference to the project's current funding cycle.
    fundingCycle = fundingCycleStore.currentOf(_projectId);

    External references:

  2. Make sure the project's funding cycle isn't configured to pause redemptions.

    // The current funding cycle must not be paused.
    if (fundingCycle.redeemPaused()) revert FUNDING_CYCLE_REDEEM_PAUSED();

    Library references:

  3. The following scoped block is a bit of a hack to prevent a "Stack too deep" error.

    // Scoped section prevents stack too deep. `_reclaimedTokenAmount`, `_currentOverflow`, and `_totalSupply` only used within scope.
    { ... }
    1. Keep a reference to the reclaimed token amount, current overflow amount, and total supply variables to use outside of the subsequent scoped block.

      // Get a reference to the reclaimed token amount struct, the current overflow, and the total token supply.
      JBTokenAmount memory _reclaimedTokenAmount;
      uint256 _currentOverflow;
      uint256 _totalSupply;
    2. The following other scoped block uses the same hack to prevent a "Stack too deep" error.

      // Another scoped section prevents stack too deep. `_token`, `_decimals`, and `_currency` only used within scope.
      { ... }
      1. Get a reference to the terminal's token, decimals, and currency.

        // Get a reference to the terminal's tokens.
        address _token = IJBSingleTokenPaymentTerminal(msg.sender).token();

        // Get a reference to the terminal's decimals.
        uint256 _decimals = IJBSingleTokenPaymentTerminal(msg.sender).decimals();

        // Get areference to the terminal's currency.
        uint256 _currency = IJBSingleTokenPaymentTerminal(msg.sender).currency();

        External references:

      2. Get a reference to the amount of overflow the project has. Either the project's total overflow or the overflow local to the msg.sender's balance will be used depending on how the project's funding cycle is configured.

        // Get the amount of current overflow.
        // Use the local overflow if the funding cycle specifies that it should be used. Otherwise, use the project's total overflow across all of its terminals.
        _currentOverflow = fundingCycle.useTotalOverflowForRedemptions()
        ? _currentTotalOverflowOf(_projectId, _decimals, _currency)
        : _overflowDuring(

        Library references:

        Internal references:

      3. Get a reference to the total outstanding supply of project tokens.

        // Get the number of outstanding tokens the project has.
        _totalSupply = IJBController(directory.controllerOf(_projectId))
        .totalOutstandingTokensOf(_projectId, fundingCycle.reservedRate());

        Library references:

        Internal references:

        External references:

      4. Make sure the provided token count is within the bounds of the total supply.

        // Can't redeem more tokens that is in the supply.
        if (_tokenCount > _totalSupply) revert INSUFFICIENT_TOKENS();
      5. Get a reference to the reclaimable overflow if there is overflow.

        if (_currentOverflow != 0)
        // Calculate reclaim amount using the current overflow amount.
        reclaimAmount = _reclaimableOverflowDuring(

        Internal references:

      6. Construct the reclaim amount struct.

        _reclaimedTokenAmount = JBTokenAmount(_token, reclaimAmount, _decimals, _currency);
    3. If the project's current funding cycle is configured to use a data source when making redemptions, ask the data source for the parameters that should be used throughout the rest of the function given provided contextual values in a JBRedeemParamsData structure. Otherwise default parameters are used.

      if (fundingCycle.useDataSourceForRedeem() && fundingCycle.dataSource() != address(0)) {
      // Yet another scoped section prevents stack too deep. `_state` only used within scope.
      // Get a reference to the ballot state.
      JBBallotState _state = fundingCycleStore.currentBallotStateOf(_projectId);

      // Create the params that'll be sent to the data source.
      JBRedeemParamsData memory _data = JBRedeemParamsData(
      _state == JBBallotState.Active
      ? fundingCycle.ballotRedemptionRate()
      : fundingCycle.redemptionRate(),
      (reclaimAmount, memo, delegateAllocations) = IJBFundingCycleDataSource(
      } else {
      memo = _memo;

      Library references:

      • JBFundingCycleMetadataResolver
        • .useDataSourceForRedeem(...)
        • .dataSource(...)
        • .redemptionRate(...)
        • .ballotRedemptionRate(...)
        • .useTotalOverflowForRedemptions(...)
  4. Keep a reference the amount difference to apply to the balance. Initially this is the full reclaim value.

    // Keep a reference to the amount that should be subtracted from the project's balance.
    uint256 _balanceDiff = reclaimAmount;
  5. If delegate allocations were returned by the data source, increment the amount that will get subtracted from the project's balance by each delegated allocation.

    if (delegateAllocations.length != 0) {
    // Validate all delegated amounts.
    for (uint256 _i; _i < delegateAllocations.length; ) {
    // Get a reference to the amount to be delegated.
    uint256 _delegatedAmount = delegateAllocations[_i].amount;

    // Validate if non-zero.
    if (_delegatedAmount != 0)
    // Increment the total amount being subtracted from the balance.
    _balanceDiff = _balanceDiff + _delegatedAmount;

    unchecked {
  6. Make sure the amount being decremented from the balance is within the bounds of the project's balance.

    // The amount being reclaimed must be within the project's balance.
    if (_balanceDiff > balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId])

    Internal references:

  7. Decrement any claimed funds from the project's balance if needed.

    // Remove the reclaimed funds from the project's balance.
    if (_balanceDiff != 0) {
    unchecked {
    balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] =
    balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] -

    Internal references: