arrow-left

All pages
gitbookPowered by GitBook
1 of 14

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Redemption Delegate

Specifications

A contract can become a treasury redemption delegate by adhering to ISnowconeRedemptionDelegate3_1_1:

interface ISnowconeRedemptionDelegate3_1_1 is IERC165 {
  function didRedeem(SnowconeDidRedeemData3_1_1 calldata data) external payable;
}

When extending the redemption functionality with a delegate, the protocol will pass a SnowconeDidRedeemData3_1_1 to the didRedeem(...) function:

struct SnowconeDidRedeemData3_1_1 {
    address holder;
    uint256 projectId;
    uint256 currentFundingCycleConfiguration;
    uint256 projectTokenCount;
    SnowconeTokenAmount reclaimedAmount;
    SnowconeTokenAmount forwardedAmount;
    address payable beneficiary;
    string memo;
    bytes dataSourceMetadata;
    bytes redeemerMetadata;
}

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

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

In payment terminals based on the SnowconePayoutRedemptionPaymentTerminal3_1_1, such as SnowconeETHPaymentTerminal3_1_1's and SnowconeERC20PaymentTerminal3_1_1's, the redemption delegate hook gets called before the reclaimed amount is sent to the redemption beneficiary, but after all internal accounting has been updated. View the docs.

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

Attaching

New delegate contracts should be deployed independently. Once deployed, its address can be returned from a data source hook.

Getting Started

Import​arrow-up-right

Add the protocol files to the project.

# command linenpm install @Snowcones/snowcones-icy-contracts/

If referencing from typescript:

const contract = require(`@Snowcones/snowcones-icy-contracts/deployments/${network}/${contractName}.json`)

If referencing from a contract:

Now what​arrow-up-right

From here, you can build the following:

Basics - Interact with the protocol's basic functionality. Useful for building front-ends.

Pay a project - Deploy or inherit from a contract that makes it easy to forward funds to Snowcone projects.

Split payments - Deploy or inherit from a contract that makes it easy to forward funds to groups of splits whose members are either addresses, Snowcone projects, or arbitrary contracts that inherit from ISNOWSplitAllocator.

Program a treasury - Get familiar with the configurable properties available when launching a project.

Program project permissions - Build custom Snowcone Project NFT logic to create your own project access controls.

Program treasury extensions - Create custom contractual rules defining what happens when a project receives funds, and under what conditions funds can leave the treasury during a funding cycle.

import '@Snowcone/snowcones-icy-contracts/contracts/[file-path].sol'

Programmable treasury

In order to understand what Snowcone can do for your project, all you have to do is understand how one transaction works :SNOWController.launchProjectFor(...), which creates a project, configures its first funding cycle, and specifies where it can begin receiving and managing funds from.

Project NFT

Anyone can build on the SNOWProjects NFT contract. This allows developers to write new contracts which use SNOWProjects NFTs to manage permissions in a standardized way, and allows any project using Snowcone payment terminals to access your contracts, and vice versa.

Create a project​arrow-up-right

Instead of calling SNOWController.launchProjectFor(...) to create a project, configure its first funding cycle, and attach payment terminals and a Snowcone controller contract to it in the same transaction, SNOWProjects can be minted independently to represent ownership over projects with subsequent capabilities attached later on.

To create a project, call SNOWProjects.createFor(...). The SNOWProjectMetadata structure allows arbitrary metadata to be mapped to any namespace domain. Snowcones.io metadata uses a domain of 0 to store its formatted metadata.

Build

Welcome to the "Build" section of Snowcone DAO. This is your comprehensive guide to actively contributing to our ecosystem. Whether you're a seasoned developer or just starting out, you'll find resources here to help you understand our protocol, create your own projects, and extend the capabilities of our treasury. Here's what you can find in each subsection:

hashtag
Getting Started

New to Snowcone DAO or blockchain development? Start here. This subsection provides a step-by-step guide to setting up your development environment, understanding our protocol, and getting your first project off the ground.

hashtag

This subsection covers the fundamental concepts and components of the Snowcone DAO protocol. It's a must-read for anyone looking to build on our platform.

hashtag

Learn about the role of NFTs (Non-Fungible Tokens) in the Snowcone DAO ecosystem. This subsection covers how to create and manage Project NFTs, which represent ownership and control over a project's treasury.

hashtag

This subsection dives into the details of our programmable treasury. Learn how to set rules for token creation and fund distribution, and how to change these rules over the course of a project's lifecycle.

hashtag

Discover how to extend the capabilities of a project's treasury. This subsection provides guides on creating and integrating treasury extensions, allowing you to customize the functionality of your project's treasury.

hashtag

This subsection provides a collection of utility functions and tools that can help you in building and managing your Snowcone DAO project.

Basics
Project NFT
Programmable Treasury
Treasury Extensions
Utilities

Basics

Workflows

The first transaction to call when getting started is SNOWController.launchProjectFor(...).

​arrow-up-right

Split Allocator

Specifications

A contract can become a split allocator by adhering to ISnowconeSplitAllocator:

interface ISnowconeSplitAllocator {
  function allocate(SnowconeSplitAllocationData calldata _data) external payable;
}

When extending payout distribution or reserved token distribution functionality with an allocator, the protocol will pass a SnowconeSplitAllocationData to the allocate(...) function:

The msg.sender to the allocator will either be the payment terminal that facilitated the payout distribution, or the controller that facilitated the reserved tokens distribution.

In payment terminals based on the SnowconePayoutRedemptionPaymentTerminal3_1_1, such as SnowconeETHPaymentTerminal3_1_1's and SnowconeERC20PaymentTerminal3_1_1's, the allocator hook gets called while the payouts are being distributed to splits. View the docs.

If the allocation is coming from an ETH payment terminal such as SnowconeETHPaymentTerminal3_1_1, the ETH will be included in the call to allocate(...). If the allocation is coming from an ERC20 payment terminal such as SnowconeERC20PaymentTerminal3_1_1, the tokens will be pre-approved for the allocator contract to transfer them to it. Make sure to initiate the transfer, and make sure to not leave allocated tokens stuck in the allocator contract. If the allocation is coming from a controller such as SnowconeController3_1 distributing reserved tokens, the tokens will be minted pre-distributed to the allocator's address. If the split's preferClaimed property is true and the project has a token a contract attached, the tokens will be minted directly to the allocator contract. Otherwise, they will be allocated in the SnowconeTokenStore as unclaimed tokens from which the allocator can then claimFor(...) itself or transferFrom(...) itself to another address. Make sure to not leave allocated tokens stuck in the allocator contract or unclaimed in the SnowconeTokenStore contract.

Attaching

New allocator contracts should be deployed independently. Once deployed, its address can be configured into a project's payout splits or reserved token splits so that any distribution triggered while the funding cycle is active sends the relevant token to the allocator contract's allocate(...) hook.

struct SnowconeSplitAllocationData {
  address token;
  uint256 amount;
  uint256 decimals;
  uint256 projectId;
  uint256 group;
  SnowconeSplit split;
}

struct SnowconeSplit {
  bool preferClaimed;
  bool preferAddToBalance;
  uint256 percent;
  uint256 projectId;
  address payable beneficiary;
  uint256 lockedUntil;
  ISnowconeSplitAllocator allocator;
}

Project payer

SNOWETHERC20ProjectPayer contracts make it easy to route funds to projects' treasuries from other contracts or within inheriting contracts. This is useful for routing funds to a Snowcone treasury within other contracts such as an NFT's minting function, or creating contracts that will automatically route any received funds to a project's treasury with preconfigured parameters to send along with the payment.

The SNOWETHERC20ProjectPayer can be inherited from any contract to facilitate internal transactions to Snowcone treasuries in AVAX or any ERC-20, assuming the project is using a payment terminal that accepts the tokens. They can also be deployed as standalone project payer copies using SNOWProjectPayerDeployer.

Inheriting SNOWProjectPayer​arrow-up-right

Inheriting from SNOWETHERC20ProjectPayer will give a contract access to a public SNOWProjectPayer.pay(...) function, a public SNOWProjectPayer.addToBalanceOf(...)function, an internal SNOWProjectPayer._pay(...) function, and an internal SNOWProjectPayer._addToBalanceOf(...) function. These can be used from within the contract to route funds to a Snowcone treasury while specifying all relevant parameters to contextualize the payment. Use the internal versions if the inheriting contract has already handled receiving the funds being forwarded.

Follow instructions in Getting started to import the SNOWProjectPayer files into a project.

Treasury extensions

Treasury extensions allow projects to override or extend the default Snowcone protocol functionality with custom contract logic.

Data source

Projects can attach a data source contract address to a funding cycle configuration to provide custom data to be used when processing a payment or a redemption. Build

Pay delegate

Projects can return a pay delegate contract address from its data source that will be called when it receives a payment. Build

Redemption delegate

Projects can return a redemption delegate contract address from its data source that will be called when its token holders redeem. Build

Funding cycle ballot

Projects can attach a ballot contract address to a funding cycle configuration to provide certain conditions which subsequent reconfigurations must adhere to in order to take effect. Build

Split allocator

Projects can route treasury payouts or reserved tokens to an allocator contract that will be called upon distribution. Build

Edit this pagearrow-up-right

Data Source

Specifications

A contract can be transformed into a treasury data source by adhering to ISnowconeFundingCycleDataSource3_1_1:

Two functions must be implemented, payParams(...) and redeemParams(...). Either one can be left empty if the intent is to only extend the treasury's pay functionality or redeem functionality.

Pay

When extending the pay functionality with a data source, the protocol will pass a SnowconePayParamsData to the payParams(...) function:

Using these params, the data source's payParams(...) function is responsible for either reverting or returning a few bits of information:

weight is a fixed point number with 18 decimals that the protocol can use to base arbitrary calculations on. For example, payment terminals based on the SnowconePayoutRedemptionPaymentTerminal3_1_1, such as SnowconeETHPaymentTerminal3_1_1's and SnowconeERC20PaymentTerminal3_1_1's, use the weight to determine how many project tokens to mint when a project receives a payment (see the calculation). By default, the protocol will use the weight of the project's current funding cycle, which is provided to the data source function in SnowconePayParamsData.weight. Increasing the weight will mint more tokens and decreasing the weight will mint fewer tokens, both as a function of the amount paid. Return the SnowconePayParamsData.weight value if no altered functionality is desired.

memo is a string emitted within the Pay event and sent along to any delegate that this function also returns. By default, the protocol will use the memo directly passed in by the payer, which is provided to this data source function in SnowconePayParamsData.memo. Return the SnowconePayParamsData.memo value if no altered functionality is desired.

delegateAllocations is an array containing delegates, amounts to send them, and metadata to pass to them.

delegateAllocations.delegate is the address of a contract that adheres to ISnowconePayDelegate3_1_1 whose didPay(...) function will be called once the protocol finishes its standard payment routine. Check out how to build a pay delegate for more details. If the same contract is being used as the data source and the pay delegate, return address(this). Return the zero address if no additional functionality is desired.

delegateAllocations.amount is the amount of tokens to send to the delegate.

delegateAllocations.metadata is the metadata to pass to the delegate.

The payParams(...) function can also revert if it's presented with any conditions it does not want to accept payments under.

The payParams(...) function has implicit permission to SnowconeController3_1.mintTokensOf(...) for the project.

Redeem

When extending redeem functionality with a data source, the protocol will pass a SnowconeRedeemParamsData to the redeemParams(...) function:

Using these params, the data source's redeemParams(...) function is responsible for either reverting or returning a few bits of information:

reclaimAmount is the amount of tokens in the treasury that the terminal should send out to the redemption beneficiary as a result of burning the amount of project tokens tokens specified in SnowconeRedeemParamsData.tokenCount, as a fixed point number with the same amount of decimals as SnowconeRedeemParamsData.decimals. By default, the protocol will use a reclaim amount determined by the standard protocol bonding curve based on the redemption rate the project has configured into its current funding cycle, which is provided to the data source function in SnowconeRedeemParamsData.reclaimAmount. Return the SnowconeRedeemParamsData.reclaimAmount value if no altered functionality is desired.

memo is a string emitted within the RedeemTokens event and sent along to any delegate that this function also returns. By default, the protocol will use the memo passed in directly by the redeemer, which is provided to this data source function in SnowconeRedeemParamsData.memo. Return the SnowconeRedeemParamsData.memo value if no altered functionality is desired.

delegateAllocations is an array containing delegates, amounts to send them, and metadata to pass to them.

delegateAllocations.delegate is the address of a contract that adheres to ISnowconeRedemptionDelegate3_1_1 whose didRedeem(...) function will be called once the protocol finishes its standard redemption routine (but before the reclaimed amount is sent to the beneficiary). Check out how to build a redemption delegate for more details. If the same contract is being used as the data source and the redemption delegate, return address(this). Return the zero address if no additional functionality is desired.

delegateAllocations.amount is the amount of tokens to send to the delegate.

delegateAllocations.metadata is the metadata to pass to the delegate.

The redeemParams(...) function can also revert if it's presented with any conditions it does not want to accept redemptions under.

Attaching

New data source contracts should be deployed independently. Once deployed, its address can be configured into a project's funding cycle metadata to take effect while that funding cycle is active. Additionally, the metadata's useDataSourceForPay and/or useDataSourceForRedeem should be set to true if the respective data source hook should be referenced by the protocol.

Examples

Splits Payer

SNOWETHERC20SplitsPayer contracts make it easy to route funds to a group of splits from other contracts or within inheriting contracts. This is useful for routing funds to a number of Snowcone project treasuries and other addresses within other contracts such as an NFT marketplaces.

The SNOWETHERC20SplitsPayer can be inherited from any contract to facilitate internal transactions to split groups in AVAX or any ERC-20, assuming the projects in the split group are using a payment terminal that accepts the tokens. They can also be deployed as standalone splits payer copies using SNOWSplitsPayerDeployer.

Inheriting SNOWSplitsPayer

Inheriting from SNOWETHERC20SplitsPayer

Utilities

Welcome to the Utilities section of Snowcone documentation. In this part, we delve into the various utility functions that Snowcone provides to make your development journey smoother and more efficient.

hashtag
Contents

interface ISnowconeFundingCycleDataSource3_1_1 is IERC165 {
  function payParams(
    SnowconePayParamsData calldata data
  )
    external
    view
    returns (
      uint256 weight,
      string memory memo,
      SnowconePayDelegateAllocation3_1_1[] memory delegateAllocations
    );

  function redeemParams(
    SnowconeRedeemParamsData calldata data
  )
    external
    view
    returns (
      uint256 reclaimAmount,
      string memory memo,
      SnowconeRedemptionDelegateAllocation3_1_1[] memory delegateAllocations
    );
}
will give a contract access to a public
SNOWSplitsPayer.pay(...)
function, a public
SNOWSplitsPayer.addToBalanceOf(...)
function, and two functions
SNOWSplitsPayer._payToSplits(...)
and
SNOWSplitsPayer._payTo(...)
. These can be used from within the contract to route funds to a group of splits while specifying where leftover funds should go. Use the internal function if the inheriting contract has already handled receiving the funds being forwarded.

Follow instructions in Getting started to import the SNOWSplitsPayer files into a project.

​arrow-up-right
struct SnowconePayParamsData {
  ISnowconePaymentTerminal terminal;
  address payer;
  SnowconeTokenAmount amount;
  uint256 projectId;
  uint256 currentFundingCycleConfiguration;
  address beneficiary;
  uint256 weight;
  uint256 reservedRate;
  string memo;
  bytes metadata;
}

struct SnowconeTokenAmount {
  address token;
  uint256 value;
  uint256 decimals;
  uint256 currency;
}
struct SnowconeRedeemParamsData {
  ISnowconePaymentTerminal terminal;
  address holder;
  uint256 projectId;
  uint256 currentFundingCycleConfiguration;
  uint256 tokenCount;
  uint256 totalSupply;
  uint256 overflow;
  SnowconeTokenAmount reclaimAmount;
  bool useTotalOverflow;
  uint256 redemptionRate;
  uint256 ballotRedemptionRate;
  string memo;
  bytes metadata;
}
import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import '@snowcone-protocol/contracts-v2/contracts/interfaces/ISnowconeFundingCycleDataSource.sol';

contract AllowlistDataSource is ISnowconeFundingCycleDataSource {
  error NOT_ALLOWED();

  mapping(address => bool) allowed;

  function payParams(SnowconePayParamsData calldata _data)
    external
    view
    override
    returns (
      uint256 weight,
      string memory memo,
      ISnowconePayDelegate delegate
    )
  {
    if (!allowed[_data.payer]) revert NOT_ALLOWED();

    // Forward the received weight and memo, and use no delegate.
    return (_data.weight, _data.memo, ISnowconePayDelegate(address(0)));
  }

  // 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)));
  }
}

hashtag
Project Payer

This section explores the Project Payer utility, which is instrumental in managing payments within a Snowcone project. Understand how this utility can simplify transactions for your project.

hashtag
Splits Payer

In this section, we cover the Splits Payer utility. Learn how this feature can help manage and distribute payments among different stakeholders in a project.

We hope this guide helps you understand the various utilities available in the Snowcone protocol. If you have any questions or need further clarification, please reach out to our team.

Project Payer
Splits Payer
Read More
Read More

Pay delegate

Before implementing, learn about delegates here. Also see snow-delegate-template.

Specs

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

interface ISNWPayDelegate3_1_1 is IERC165 {
  function didPay(SNWDidPayData3_1_1 calldata data) external payable;
}

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

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

struct SNWTokenAmount {
  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 SNWPayoutRedemptionPaymentTerminal3_1_1, such as SNWETHPaymentTerminal3_1_1's and SNWERC20PaymentTerminal3_1_1's, the pay delegate hook gets called after the project's tokens have been minted and distributed. View the docs.

Make sure 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 '@snx-protocol/contracts-v2/contracts/interfaces/ISNWFundingCycleDataSource.sol';
import '@snx-protocol/contracts-v2/contracts/interfaces/ISNWPayDelegate.sol';
import '@snx-protocol/contracts-v2/contracts/structs/SNWTokenAmount.sol';

contract NFTPayDelegate is ERC721, ISNWFundingCycleDataSource, ISNWPayDelegate {
  error INVALID_PAYMENT_EVENT();

  ISNWDirectory directory;
  uint256 projectId;
  SNWTokenAmount 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(SNWPayParamsData calldata _data)
    external
    view
    override
    returns (
      uint256 weight,
      string memory memo,
      ISNWPayDelegate delegate
    )
  {
    // Forward the received weight and memo, and use this contract as a pay delegate.
    return (_data.weight, _data.memo, ISNWPayDelegate(address(this)));
  }

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

  constructor(ISNWDirectory _directory, uint256 _projectId, SNWTokenAmount _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(SNWDidPayData 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, ISNWPaymentTerminal(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);
  }
}

Ballot

Specifications

A contract can be transformed into a funding cycle ballot by adhering to ISnowconeFundingCycleBallot:

interface ISnowconeFundingCycleBallot {
  function duration() external view returns (uint256);

  function stateOf(
    uint256 _projectId,
    uint256 _configuration,
    uint256 _start
  ) external view returns (SnowconeBallotState);
}

Two functions must be implemented: duration(...) and stateOf(...). The result of duration(...) is the number of seconds the ballot lasts for from the moment the reconfiguration is proposed. During this time, the protocol automatically interprets the ballot's state as SnowconeBallotState.Active. The result of stateOf(...) returns the SnowconeBallotState. If a configuration is approved and the duration has expired, the SnowconeFundingCycleStore will use it as the project's current funding cycle when it becomes active. Otherwise, it will make a copy of the latest approved cycle to use.

When extending the pay functionality with a delegate, the protocol will pass a projectId, a configuration, and a start to the stateOf(...) function. configuration is the identifier of the funding cycle being evaluated, and also the unix timestamp in seconds of when the reconfiguration was proposed. start is the unix timestamp the reconfiguration is scheduled to start at if that reconfiguration is approved.

Once the duration(...) has expired, the returned value of stateOf(...) should no longer change.

Attaching

New ballot contracts should be deployed independently. Once deployed, its address can be configured into a project's funding cycle. The ballot will take effect while that funding cycle is active, and will be used for evaluating subsequent reconfigurations.