Skip to main content


Contract: JBSingleTokenPaymentTerminalStore​‌

Interface: IJBSingleTokenPaymentTerminalStore

Records newly contributed tokens to a project.

Mints the project's tokens according to values provided by a configured data source. If no data source is configured, mints tokens proportional to the amount of the contribution.

The msg.sender must be an IJBSingleTokenPaymentTerminal. The amount specified in the params is in terms of the msg.sender's tokens.


function recordPaymentFrom(
address _payer,
JBTokenAmount calldata _amount,
uint256 _projectId,
uint256 _baseWeightCurrency,
address _beneficiary,
string calldata _memo,
bytes memory _metadata
returns (
JBFundingCycle memory fundingCycle,
uint256 tokenCount,
JBPayDelegateAllocation[] memory delegateAllocations,,
string memory memo
) { ... }
  • Arguments:
    • _payer is the original address that sent the payment to the terminal.
    • _amount is a JBTokenAmount data structure specifying the amount of tokens being paid. Includes the token being paid, the value, the number of decimals included, and the currency of the amount.
    • _projectId is the ID of the project being paid.
    • _baseWeightCurrency is the currency to base token issuance on.
    • _beneficiary is the specified address that should be the beneficiary of anything that results from the payment.
    • _memo is a memo to pass along to the emitted event, and passed along to the funding cycle's data source.
    • _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 project's funding cycle during which payment was made.
    • tokenCount is the number of project tokens that were minted, as a fixed point number with 18 decimals.
    • delegateAllocations is the amount to send to delegates instead of adding to the local balance.
    • memo is a memo that should be passed along to the emitted event.


  1. Get a reference to the project's current funding cycle that should have its properties used in the subsequent calculations and returned.

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

    External references:

  2. Make sure the project has a funding cycle configured. This is done by checking if the project's current funding cycle number is non-zero.

    // The project must have a funding cycle configured.
    if (fundingCycle.number == 0) revert INVALID_FUNDING_CYCLE();
  3. Make sure the project's funding cycle isn't configured to pause payments.

    // Must not be paused.
    if (fundingCycle.payPaused()) revert FUNDING_CYCLE_PAYMENT_PAUSED();

    Library references:

  4. Create a variable where the weight to use in subsquent calculations will be saved.

    // The weight according to which new token supply is to be minted, as a fixed point number with 18 decimals.
    uint256 _weight;
  5. If the project's current funding cycle is configured to use a data source when receiving payments, ask the data source for the parameters that should be used throughout the rest of the function given provided contextual values in a JBPayParamsData structure. Otherwise default parameters are used.

    // If the funding cycle has configured a data source, use it to derive a weight and memo.
    if (fundingCycle.useDataSourceForPay() && fundingCycle.dataSource() != address(0)) {
    // Create the params that'll be sent to the data source.
    JBPayParamsData memory _data = JBPayParamsData(
    (_weight, memo, delegateAllocations) = IJBFundingCycleDataSource(fundingCycle.dataSource())
    // Otherwise use the funding cycle's weight
    else {
    _weight = fundingCycle.weight;
    memo = _memo;

    Library references:

    External references:

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

    // Scoped section prevents stack too deep. `_balanceDiff` only used within scope.
    { ... }
    1. Keep a reference the amount difference to apply to the balance. Initially this is the full value.

      // Keep a reference to the amount that should be added to the project's balance.
      uint256 _balanceDiff = _amount.value;
    2. If delegate allocations were returned by the data source, make sure their sum does not exceed the amount paid. Decrement each delegated allocation from the amount that will get subtracted from the project's balance.

      // Validate all delegated amounts. This needs to be done before returning the delegate allocations to ensure valid delegated amounts.
      if (delegateAllocations.length != 0) {
      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) {
      // Can't delegate more than was paid.
      if (_delegatedAmount > _balanceDiff) revert INVALID_AMOUNT_TO_SEND_DELEGATE();

      // Decrement the total amount being added to the balance.
      _balanceDiff = _balanceDiff - _delegatedAmount;

      unchecked {
    3. If there is no amount being recorded, there's nothing left to do so the current validated values can be returned.

      // If there's no amount being recorded, there's nothing left to do.
      if (_amount.value == 0) return (fundingCycle, 0, delegateAllocations, memo);
    4. Add the appropriate amount to the stored balance.

      // Add the correct balance difference to the token balance of the project.
      if (_balanceDiff != 0)
      balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] =
      balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] +

      Internal references:

  7. If there is no weight, the resulting token count will be 0. There's nothing left to do so the current values can be returned.

    // If there's no weight, token count must be 0 so there's nothing left to do.
    if (_weight == 0) return (fundingCycle, 0, delegateAllocations, memo);
  8. Calculate the weight ratio. This allows a project to get paid in a certain token, but issue project tokens relative to a different base currency. The weight ratio will be used to divide the product of the paid amount and the weight to determine the number of tokens that should be distributed. Since the number of distributed tokens should be a fixed point number with 18 decimals, the weight ratio must have the same number of decimals as the amount to cancel it out and leave only the fidelity of the 18 decimal fixed point weight.

    // Get a reference to the number of decimals in the amount. (prevents stack too deep).
    uint256 _decimals = _amount.decimals;

    // If the terminal should base its weight on a different currency from the terminal's currency, determine the factor.
    // The weight is always a fixed point mumber with 18 decimals. To ensure this, the ratio should use the same number of decimals as the `_amount`.
    uint256 _weightRatio = _amount.currency == _baseWeightCurrency
    ? 10**_decimals
    : prices.priceFor(_amount.currency, _baseWeightCurrency, _decimals);

    External references:

  9. Determine the number of tokens to mint.

    // Find the number of tokens to mint, as a fixed point number with as many decimals as `weight` has.
    tokenCount = PRBMath.mulDiv(_amount.value, _weight, _weightRatio);

    Library references: