The workshop was recorded on the 3rd June 2021 and led by Santiago Palladino, Lead Developer at OpenZeppelin.
The workshop covers the following:
- Monitor user interactions with your contracts using Sentinels
- Run custom logic without running your own servers using Autotask
- Securely and reliably send transactions from your scripts through Relayers
You can watch the video, view the slides, and learn how to use OpenZeppelin’s Defender for automating reactions to events in your contracts by executing your own logic and sending any transaction in response.
Automating smart contract operations
A smart contract system is rarely composed exclusively of just smart contracts, as it usually requires off-chain processes that complement the expensive on-chain computations. These off-chain processes need secure building blocks to reliably send transactions to interact with the contracts, as well as monitor them for changes they may need to react to.
These building blocks are not easy to implement correctly. Sending transactions requires proper nonce management, gas price oracles, redundant network providers, and resubmission strategies in case of congestion, not to mention secure storage for the private key. As for monitoring, it requires a process permanently running, subscribed to or polling for changes.
How Defender solves this
Defender provides secure building blocks for automating the process of sending transactions to smart contracts via a simple API using Relayers, as well as monitoring specific events and function calls on your contracts via Sentinels. These are brought together by using Autotasks, serverless code snippets where you can define your custom logic for triggering a transaction, reacting to a monitored event, or scheduling a process.
About this workshop
In this workshop, we put Defender to the test by building a system that reacts to specific ERC20 trades on Uniswap by minting an NFT collectible to traders. The entire flow is set up just by using Relayers, Autotasks, and Sentinels.
Step 1: Create a Sentinel
The first step is to create a Sentinel that will monitor for specific Uniswap trades. In particular, we look for UNI purchases on the ETH/UNI UniswapV2 pair on Rinkeby, such as this one.
We do this by creating a Sentinel that monitors the contract on Rinkeby, filtering by Swap events where the funds sent by the user are at least 1 ETH.
Step 2: Deploy the ERC721
The next step is to create the ERC721 contract that we’ll use for minting collectibles to the users who have purchased the UNI token. We use the Contracts Wizard for bootstrapping it, creating a mintable ERC721 with role-based access control, and then export it to Remix to deploy it and verify on Sourcify.
Step 3: Set up the minter Relayer
Next, we need to set up a private key with minter rights in our ERC721 contract, that we can use to send mint transactions to the contract whenever there is a trade. We create a new Relayer for this on the Rinkeby network, and then use Defender Admin for granting it minter permissions on the contract via an Admin action.
Note that we use Admin to build a grantRole transaction that we send directly to the contract from our account. However, if the ERC721 were on mainnet, it’s a good practice to have a multisig wallet as the admin account. Defender Admin can seamlessly build transactions that are executed via Gnosis Safe or MultisigWallets for this purpose.
Step 4: Set up the Autotask
Last step is to set up a script that will react to the swap events from the Uniswap pair captured by the Sentinel, and send a safeMint transaction to the ERC721 using our minter relayer. We use the code from this repository, but the gist is the following:
/** * Mints an NFT for the recipient if it hasn't received one yet * @param {string} recipient the recipient's address * @param {ethers.Signer} signer ethers signer for sending the tx * @param {KeyValueStoreClient} storage optional key-value store for tracking the token */ async function main(recipient, signer, storage) { console.log(`Using relayer ${await signer.getAddress()}`); // Check if recipient was already awarded an nft const key = `nft-recipients/${NFT_ADDRESS}/${recipient}`; if (await storage.get(key)) { console.log(`Address ${recipient} already received an NFT`); return; } // Mint them an NFT const tokenId = recipient; const nft = new ethers.Contract(NFT_ADDRESS, ABI, signer); const tx = await nft.safeMint(recipient, tokenId); await storage.put(key, tokenId); console.log(`Minted an NFT for ${recipient} in ${tx.hash}`); }
We set this code into a new Autotask, which we configure to use the minter Relayer we created on step 3 for sending transactions. And as a last step, we edit the Sentinel from step 1 to trigger this new Autotask whenever it finds a match.
Step 5: Profit
With all the building blocks now connected, we can execute a purchase on the Uniswap pool, which gets picked up by the Sentinel, that triggers the Autotask with our custom logic, which sends a mint transaction to the ERC721 contract via the minter Relayer. And all this runs within Defender.
Video
Slides
https://github.com/OpenZeppelin/workshops/tree/master/07-automate-workflows/slides
How to use OpenZeppelin’s Defender for automating reactions to events in your contracts by executing your own logic and sending any transaction in response.
Each case was documented with contracts, test scripts, and gas usage reports. All code examples are available; https://github.com/OpenZeppelin/workshops/tree/master/07-automate-workflows
Learn more
Learn more about OpenZeppelin Contracts: openzeppelin.com/contracts
See the documentation: docs.openzeppelin.com/contracts
Sign up for a free OpenZeppelin Defender account: https://defender.openzeppelin.com/
Be part of the community
- Continue the discussion on our forum 📖
- Join our Discord ⚡
- Follow us on Twitter 🐥
- Even better, join the team 🚀