Autotask Tutorial 2: Token Balance Automation Autotask

Now that you have your feet wet from the getting started guide, let's take a step forward with smart contract automation.

In this guide, you'll be setting up an Autotask that does a routine check of your account balance. The check runs every five minutes. If the balance is below a certain threshold, tokens are transferred from a second account.

Note that you can achieve a similar use case by following this guide that makes use of the decentralized monitoring network Forta.

Deploy ERC20 Contract

Since this is a demo, we'll be using the tokens from an ERC20 contract that we control.

  1. Head over to the Contracts Wizard and create an ERC20 contract that pre-mints 1000000 tokens to the deployer.

  2. Deploy this contract and copy the address of the deployed contract. You'll need it later in the Autotask.

  3. Transfer the tokens from the deployer to a Relayer in your Defender account. That way, your Autotask can connect to the Relayer and be able to transfer tokens as needed.

With the Relayer funded, you're ready to set up a Discord notification channel. This will allow Defender to send you an alert when the token balance drops below the threshold and the transfer is executed.

Set up Discord Notification Channel

Follow the steps here to set up a Discord server to receive notifications from Defender.

Once that is set up, supply the Discord Webhook URL to Defender by selecting the menu at the top right --> Notification Channels.

Option 1: Create Autotask Via Web Interface

Option 1 shows you how to use the Defender web application to create the Autotask.

In Defender, select Autotask --> Create Autotask.

Give the Autotask a name and select Schedule --> 5 minutes.

Choose the Relayer you minted the tokens to.

By supplying the following code, the Autotask will check the monitored account balance every five minutes. If the balance is below the threshold, it will transfer tokens from the Relayer and send an alert notification to your Discord webhook.

const axios = require('axios')
const ethers = require('ethers')
const {
  DefenderRelaySigner,
  DefenderRelayProvider,
} = require('defender-relay-client/lib/ethers')
const URL =
  'https://discord.com/api/webhooks/1029457247115423755/6P68eymsHk6GBvgcwUeMSqlfu6HLXSKyGlhs1vY_ku-SzcXFo5g0QuGH25EtKV5ArV0X'
const TOKEN_CONTRACT = '0x317F20E77feF9965F4692233aa64bbb34124E33f'
const ABI = `[ { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "payable": true, "stateMutability": "payable", "type": "fallback" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "owner", "type": "address" }, { "indexed": true, "name": "spender", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" } ]`
const RECIPIENT = '0xa9550d1c55b1011c7946d90b283c2923e9f43e1d'

async function handler(event) {
  const provider = new DefenderRelayProvider(event)
  const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' })
  const contract = new ethers.Contract(TOKEN_CONTRACT, ABI, signer)

  //check balance
  const balance = await contract.balanceOf(RECIPIENT)
  if (ethers.utils.formatEther(balance) <= 200) {
    //transfer
    const amount = ethers.utils.parseUnits('100.0', 18)
    const tx = await contract.connect(signer).transfer(RECIPIENT, amount)
    console.log(tx)
    // send to discord webhook
    const res = axios.post(URL, {
      username: 'Funds Transferred!',
      avatar_url: '',
      content: `Current balance: ${ethers.utils.formatEther(balance)}`,
    }).then(res => {
      console.log(res.status, res.data)
    })
  } else {
    console.log('balance is ', ethers.utils.formatEther(balance))
  }
}

module.exports = {
  handler,
}

You can view the created Autotask in the Defender dashboard and trigger a manual run if you'd like.

Option 2: Create Autotask Via API

Option two shows you how to write a script that makes use of defender-client to create the Autotask code (supplied in Option 1, above):

const { AutotaskClient } = require('defender-autotask-client')

async function main() {
  require('dotenv').config()
  const credentials = {
    apiKey: process.env.API_KEY,
    apiSecret: process.env.API_SECRET,
  }
  const autotaskClient = new AutotaskClient(credentials)

  const params = {
    name: 'Balance Check',
    encodedZippedCode: await autotaskClient.getEncodedZippedCodeFromFolder(
      './autotasks2'
    ),
    trigger: {
      type: 'schedule',
      frequencyMinutes: 5
    },
    paused: false,
    relayerId: '3f12191a-26a3-44ca-a3c9-ab7b57a97b8e', 
  }

  const createdAutotask = await autotaskClient.create(params)
  console.log('Created Autotask with ID: ', createdAutotask.autotaskId)
}

if (require.main === module) {
  main()
    .then(() => process.exit(0))
    .catch((error) => {
      console.error(error)
      process.exit(1)
    })
}

Congratulations on setting this up! Feel free to customize things further, experiment, and share your findings with the community.