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
Specifications
A contract can become a split allocator by adhering to ISnowconeSplitAllocator:
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.
Specifications
A contract can be transformed into a funding cycle ballot by adhering to ISnowconeFundingCycleBallot:
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.
Specifications
A contract can become a treasury redemption delegate by adhering to ISnowconeRedemptionDelegate3_1_1:
When extending the redemption functionality with a delegate, the protocol will pass a SnowconeDidRedeemData3_1_1 to the didRedeem(...)
function:
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.
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:
When extending pay functionality with a delegate, the protocol will pass a SNWDidPayData3_1_1 to the didPay(...) function:
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
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