Keepers-compatible contracts

Overview

Learn how to make smart contracts Keepers-compatible with the arkadiko-automation-trait-v1 trait and its functions.

Example Contract

Keepers-compatible contracts must meet the following requirements:

  • Implement trait arkadiko-automation-trait-v1. You can refer to the Arkadiko Contracts on GitHub to find the trait interface.

  • The automation trait has three functions that need to be implemented: initialize, check-job and run-job

  • Include a read-only check-job function that contains the logic that will be executed off-chain to see if run-job should be executed. The run-job function can use on-chain data from any other Clarity smart contract, including use of the at-block function to access historical data.

  • Include a public run-job function that will be executed on-chain when check-job returns (ok true).

After you register the contract as an Arkadiko Job, the Keepers Network simulates the check-job read-only function off-chain during every block to determine if the job needs to run. When check-job returns (ok true), the Keepers Network calls run-job on-chain and charges 10 DIKO plus the STX gas fee from your account. This cycle repeats until the job is cancelled or runs out of funding.

;; Add DIKO to liquidation pool every 1008 blocks
(impl-trait .arkadiko-automation-trait-v1.automation-trait)

(define-public (initialize)
  (ok true)
)

(define-read-only (check-job)
  (let (
    (end-epoch-block (unwrap-panic (contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.arkadiko-liquidation-rewards-diko-v1-1 get-end-epoch-block)))
  )
    (asserts! (>= block-height end-epoch-block) (ok false))

    (ok true)
  )
)

(define-public (run-job)
  (begin
    (asserts! (unwrap-panic (check-job)) (ok false))

    ;; TODO - Update for mainnet
    (unwrap-panic (contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.arkadiko-liquidation-rewards-diko-v1-1 add-rewards 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.arkadiko-liquidation-rewards-v1-2))
    
    (ok true)
  )
)

In the above example, we would like to add an amount of DIKO tokens to the Arkadiko liquidation pool which is used as rewards for people depositing USDA. More information about the liquidation pool can be found here. Let's analyze the above lines of code.

On line 2, we import the arkadiko automation trait that will require us to implement the three required methods: initialize, check-job and run-job. In this case, we don't need any initialization logic (which you need to execute yourself on deployment), so we simply return (ok true).

The check-job read-only function will call an Arkadiko read-only function to get the end-epoch-block. Or in other words, we get the block height at which we should add new DIKO to the liquidation rewards. Alternatively, we could have also written custom logic in this smart contract to keep the last block execution and execute it every 1008 blocks. When the current block-height is greater or equal to the end-epoch-block height, we should return (ok true). Otherwise, we return (ok false).

IMPORTANT: Your check-job function should never return an error (e.g. (err false)), since that would make the whole execution process fail. Simply return (ok true) or (ok false)

The run-job public function simply executes the transfer of DIKO through a liquidation rewards contract and returns (ok true). Always end your execution with an error code or an ok code.

Best practices

Trigger ONLY when conditions are met

Some actions must be performed only when specific conditions are met. Check all of the preconditions within check-job and run-job as well to ensure that state change occurs only when necessary.In this pattern, it is undesirable for the state change to occur until the next time the Arkadiko Job is checked by the network and the conditions are met. It is a best practice to stop any state change or effects by performing the same checks or similar checks that you use in check-job. These checks validate the conditions before doing the work.For example, if you have a contract where you create a block-height based timer in check-job that is designed to start a transfer at a specific time, validate the condition to ensure third-party calls to your run-job function do not start the transfer at a different block height.

Trigger ONLY when data is verified

Some actions must be performed using data you intend to use. Validated that the run-job function is allowed before execution.For example, if you have a run-job that funds a wallet, ensure you have a list of permissable addresses to compare against to prevent third-party calling your function to send money to their address.

When triggering is not harmful

Sometimes actions must be performed when conditions are met, but performing actions when conditions are not met is still acceptable. Condition checks within run-job might not be required, but it can still be a good practice to short circuit expensive and unnecessary on-chain processing when it is not required.It might be desirable to call run-job when the check-job conditions haven't yet been tested by Arkadiko Keepers, so any specific checks that you perform are entirely use case specific.

Test your contract

As with all smart contract testing, it is important to test the boundaries of your smart contract in order to ensure it operates as intended. Similarly, it is important to make sure your Keepers-compatible contract operates within the parameters of the Job Registry.Test all of your mission-critical contracts, and stress-test the contract to confirm the performance and correct operation of your use case under load and adversarial conditions. The Arkadiko Keeper Network will continue to operate under stress, but so should your contract.

Last updated