Contracts

Specifications

A contract can become a treasury pay delegate by adhering to ISnowconePayDelegate3_1_1:

interface ISnowconePayDelegate3_1_1 is IERC165 {
  function didPay(SnowconeDidPayData3_1_1 calldata data) external payable;
}

When extending pay functionality with a delegate, the protocol will pass a SnowconeDidPayData3_1_1 to the didPay(...) function:

struct SnowconeDidPayData3_1_1 {
    address payer;
    uint256 projectId;
    uint256 currentFundingCycleConfiguration;
    SnowconeTokenAmount amount;
    SnowconeTokenAmount forwardedAmount;
    uint256 projectTokenCount;
    address beneficiary;
    bool preferClaimedTokens;
    string memo;
    bytes dataSourceMetadata;
    bytes payerMetadata;
}

struct SnowconeTokenAmount {
  address token;
  uint256 value;
  uint256 decimals;
  uint256 currency;
}

The msg.sender to the delegate will be the payment terminal that facilitated the payment.

In payment terminals based on the SnowconePayoutRedemptionPaymentTerminal3_1_1, such as SnowconeETHPaymentTerminal3_1_1's and SnowconeERC20PaymentTerminal3_1_1's, the pay delegate hook gets called after the project's tokens have been minted and distributed. View the docs.

Ensure to only allow trusted contracts to access the didPay(...) transaction.

Attaching

New delegate contracts should be deployed independently. Once deployed, its address can be returned from a data source hook. See how to build a data source for more.

Examples

import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import '@snowcone-protocol/contracts-v2/contracts/interfaces/ISnowconeFundingCycleDataSource.sol';
import '@snowcone-protocol/contracts-v2/contracts/interfaces/ISnowconePayDelegate.sol';
import '@snowcone-protocol/contracts-v2/contracts/structs/SnowconeTokenAmount.sol';

contract NFTPayDelegate is ERC721, ISnowconeFundingCycleDataSource, ISnowconePayDelegate {
  error INVALID_PAYMENT_EVENT();

  ISnowconeDirectory directory;
  uint256 projectId;
  SnowconeTokenAmount contributionThreshold;
  uint256 supply;

  // This contract can be used as a funding cycle data source to ensure its didPay function is called once the payment has gone through.
  function payParams(SnowconePayParamsData calldata _data)
    external
    view
    override
    returns (
      uint256 weight,
      string memory memo,
      ISnowconePayDelegate delegate
    )
  {
    // Forward the received weight and memo, and use this contract as a pay delegate.
    return (_data.weight, _data.memo, ISnowconePayDelegate(address(this)));
  }

  // This is unused but needs to be included to fulfill ISnowconeFundingCycleDataSource.
  function redeemParams(SnowconeRedeemParamsData calldata _data)
    external
    pure
    override
    returns (
      uint256 reclaimAmount,
      string memory memo,
      ISnowconeRedemptionDelegate delegate
    )
  {
    // Return the default values.
    return (_data.reclaimAmount.value, _data.memo, ISnowconeRedemptionDelegate(address(0)));
  }

  constructor(ISnowconeDirectory _directory, uint256 _projectId, SnowconeTokenAmount _contributionThreshold, string calldata _name, string calldata _symbol) ERC721(_name, _symbol) {
    directory = _directory;
    projectId = _projectId;
  },

  // Called once the payment has gone through if the project's current funding cycle is using a data source that returns this delegate.
  function didPay(SnowconeDidPayData calldata _data) external override {
    // Make sure the caller is a terminal of the project, and the call is being made on behalf of an interaction with the correct project.
    if (
      !directory.isTerminalOf(projectId, ISnowconePaymentTerminal(msg.sender)) ||
      _data.projectId != projectId
    ) revert INVALID_PAYMENT_EVENT();

    // Make the contribution is being made in the expected token.
    if (_data.amount.token != contributionThreshold.token) return;

    // Make sure the values use the same number of decimals.
    if (_data.amount.decimals < contributionThreshold.decimals) return;

    // Make sure the threshold is met.
    if (_data.amount.value < contributionThreshold.value) return;

    uint256 _tokenId = ++supply;

    _mint(_data.beneficiary, _tokenId);
  }
}

Last updated

SnowconeDAO