A Trigger or Hook is a type of app that can be used to connect an external or internal action that when done it can perform an intent in a DAO. It is the equivalent to a webhook in traditional web development.
Triggers allow for maximum customization of DAOs without requiring users to write code. Triggers could also be composable in the future, so complex actions could be performed through a chain of triggers.
Triggers can be implemented as a special type* of forwarder that could follow the current forwarding interface (at least in one of the two possible technical implementations).
*special type in the sense that we would need a way to communicate to the client the pre-conditions before executing the forwarder (i.e. create a token allowance to the Trigger address)
User story: DAppNode NFT purchases
DAppNode created a NFT app to issue tokens as software licenses to hardware vendors. Something that we weren’t able to tackle in the first iteration is the ability for vendors or end users to directly purchase the tokens from the DAO by sending some DAI to it. Currently a trusted account can mint tokens and when the vendor pays them they transfer the tokens, then DAppNode deposits the funds into their Vault.
Even though this process was good enough for the first version (specially given the short project timeline), it requires a fair amount of trust as the transaction is non-atomic, and there is some manual interaction every time one token is sold.
The flow, had the Triggers app existed by then, would have been:
DAppNode assigns the permission to mint tokens to anyone pays who 100 DAI.
A Payment Trigger app instance is deployed in the background and the permission to mint tokens is granted to it.
A purchaser willing to get tokens would just use the NFT app and click on the Mint action. They will be prompted send tokens along with their transaction in the confirmation panel.
When the final transaction is mined (in case of a token transfer 2 txs are needed, for ETH just 1) the purchaser will have their token minted for their account and the funds will be deposited in the Vault.
1. Payment Trigger
Extending the user story above, Triggers easily allow for users to grant permissions in a DAO to anyone that pays the DAO a certain amount.
This type of Trigger could have a time limit, or a limit on how many times it can be executed before it deactivates itself. Just with Payment Triggers alone basic fundraising can be implemented and can be customized without the need to write any fundraising specific code.
Payment Triggers can also be used for actions that require a deposit, for example requiring the creator of a proposal to deposit a certain amount in order to create it, which gets returned to them if the proposal passes. Just by setting a Payment Trigger as the only entity that can create new proposals this effect can be achieved, and proposers can add a call that transfers their deposit back from the Vault as the final part of the script that is executed if the proposal passes.
2. Staking Trigger
In a similar fashion, instances of this Trigger can pull tokens from the user, stake and lock them on their account before forwarding a certain intent.
The workflow would be really similar to the one exposed above (the user would be prompted to approve the trigger to pull some tokens from their account), but this Trigger would have a bit more of logic before performing the execution.
1. Preset scripts with ParametrizableCallsScript
When deploying the Trigger instance, the EVMScript (or a hash of it, depending on gas optimizations) that can be executed if the conditions are met is provided and saved in storage.
In order for these scripts to be parametrized depending on runtime parameters (for example if buying tokens who the receiver of such tokens is, given that the minting call sender will be the Trigger address) we can introduce a new type of EVMScript executor that allows for some basic parametrization: ParametrizableCallsScript.
The ParametrizableCallsScript inherits most of its logic from CallsScript but these scripts define a few script locations that can be modified depending on the script’s input.
A rough outline of how these script payloads could look like:
We add the concept of placeholders before the actual CallsScript. The valid placeholders payload defines the sections of the CallsScript that can be modified depending on the input: it specifies the needle in the script where the modification starts and the amount of bytes that will be modified. The input to this script is expected to be a concatenation of all the bytes that need to be changed, in case the input length doesn’t match all the placeholders length, the script execution should error.
An extra check could be added to make sure that the bytes being modified in the script are all 0s or another arbitrary pattern (though zero is probably better as having zeros in the transaction data is cheaper than non-zero bytes).
2. Liberal triggers with call-limited CallsScript
Another less complex alternative for implementing this would be to upgrade the CallsScript executor to take as its input parameter the maximum number of calls that the script can perform (which could be performed by the caller before executing the script but it would require logic specific to this executor type).
The input to the CallsScript execution would have to be either empty (and assume that there is no limit, as it is working right now, providing backwards compatibility) or 2 bytes representing an integer that is the maximum amount of calls that the script execution can perform.
By having this limit in the CallsScript we can have Triggers that are merely a Forwarder that just gets assigned some permissions, but doesn’t have a previously knowledge of the kinds of scripts that it can execute (as it can do anything as long as the Trigger preconditions are met). Thanks to this we can make sure that even if the Trigger can forward anything, it cannot perform multiple actions with just one token deposit for example.
The Triggers should however make sure that the script being executed will do so using the modified CallsScript executor.
Potential required changes to aragonOS
In order for the “Payment Trigger app instance is deployed in the background and the permission to mint tokens is granted to it” to actually be done securely in a non-atomic way (it is impossible to do it atomically with a CallsScript) we need to add
CREATE2support when deploying app instances from the Kernel or implement another EVMScript executor that supports passing the result of a call as the parameter to another one.
For proposal #2, the CallsScript executor would need to be slightly modified and DAOs upgraded to this new version before being able to use Triggers securely.
- DAppNode’s Edu implemented an NFT Purchaser app that could be abstracted into a more generic Payment Trigger: https://github.com/eduadiez/NFTReseller