Yield Boost - Auto-restaking

Enable auto-compounding for your validator/collator stake.

Dotsama blockchains that use the parachain-staking pallet deposit rewards into the delegator’s wallet at the beginning of every round. Currently in the Dotsama ecosystem, parachain stakers that want to maximize their gains have to manually increase the size of their delegation each time they receive a staking reward. The Turing blockchain allows delegators to auto-compound their stake on a fixed schedule without risking their private key or having to run their own infrastructure.

Scheduling an Auto-Compounding Task

This extrinsic schedules a task that increases a delegator’s stake based on configurable parameters. In this example, Charlie is scheduling a task to auto-compound his delegation to the collator Alice every week as long as his account has a minimum of 100 TUR.

Module: AutomationTime

Method: scheduleAutoCompoundDelegatedStakeTask

Args:

  • executionTime: A unix timestamp (to the nearest hour); when the task should run for the first time.
  • frequency: An integer number of seconds (to the nearest hour); how often the wallet balance should be auto compounded.
  • collatorId: The account id of the target collator.
  • accountMinimum: The minimum amount that should be kept in the user’s wallet.

Detecting Success

When the task is successfully scheduled the websocket will return a success response and the chain will emit the automationTime.TaskScheduled event. The taskId can be used to track and interact with a scheduled task.

When the task executes successfully the chain will emit the automationTime.SuccesfullyAutoCompoundedDelegatorStake event. This event will include the amount by which the delegation was increased.

Error Handling

Errors can occur both at the time of initial scheduling and when the task is executed. Scheduling errors will be returned as a websocket response while execution errors will be signaled by on-chain events.

List of scheduling errors

Viewing Active Auto-Compounding Tasks

The Turing blockchain exposes a rpc endpoint that allows developers to query a user’s scheduled auto compounding tasks.

Module: AutomationTime

Method: getAutoCompoundDelegatedStakeTaskIds

Args:

  • accountId: The user’s ss58 encoded account id

Example Response:

{
    "jsonrpc": "2.0",
    "result": [
        "0xa22d90bd566e7abfcb3d7a06032a046677c2f33e5a9404ec62724cc07dc6fbad", // TaskId
        "0x43aa90bd566e7abfcb3d7a06032a046677c2f33e5a9404ec62724cc07dc6fdba",
        ...
    ]
    },
    "id": 1
}

Cancelling an Auto-Compounding Task

Active auto-compounding tasks can be cancelled by submitting an extrinsic to the Turing blockchain. In this example, Charlie is cancelling the task that he scheduled earlier. Users can only cancel tasks that they have scheduled.

Module: AutomationTime

Method: cancelTask

Args:

  • taskId: Id of the task to cancel

Detecting Success

When a task is successfully cancelled the websocket will return a success response and the chain will emit an automationTime.TaskCancelled event that includes the taskId of the cancelled task.

Error Handling

On error the websocket will return a response that includes a dispatch error of either: TaskDoesNotExist or NotTaskOwner.

Querying for an Optimal Auto-Compounding Time

An RPC call is provided to perform a calculation to return the period of time in days that maximizes APY given a user’s principal and target collator. Access to this RPC via API is not currently available in the UI but can be called manually.

Module: AutomationTime

Method: calculateOptimalAutostaking

Args:

  • principal: An integer balance (in Planck); the initial balance a user wishes to stake.
  • collator: The account id of the target collator

Results:

  • period: An integer number of days; how often restaking is optimal for the given parameters
  • apy: A decimal APY.

Example Response:

{
    "jsonrpc": "2.0",
    "result": {
        "apy":0.029450359762913332,
        "period":46
    },
    "id": 1
}

Events

  • AutomationTime.TaskScheduled { who: AccountId, task_id: Hash }
  • AutomationTime.TaskCancelled { who: AccountId, task_id: Hash }
  • AutomationTime.TaskMissed { who: AccountId, task_id: Hash, execution_time: UnixTime }
    • The task could not be performed at the scheduled time
  • AutomationTime.SuccessfullyAutoCompoundedDelegatorStake { task_id: Hash, amount: Balance }
  • AutomationTime.AutoCompoundDelegatorStakeFailed { task_id: Hash, error_message: String, error: DispatchError }
  • ParachainStaking.DelegationIncreased { delegator: AccountId, candidate: AccountId, amount: Balance }

APR calculation

The goal is to calculate the return of a delegator if one delegates tokens to a particular collator. We start with the fact that there is inflation config (inflation_config = ParachainStaking.InflationConfig) value that determines how many tokens are issued per round based on amount of currently issued tokens (total_issued = Balances.TotalIssued).

Also there is total staked amount (total_staked = ParachainStaking.Staked) which is updated each round. Having this information one can calculate annual return per staked token:

staked_portion = total_staked / total_issued
annual_return = annual_inflation / staked_portion

For annual_inflation the following logic is applied:

1) annual_inflation = inflation_config.annual.min when total_staked < inflation_config.expect.min;
2) annual_inflation = inflation_config.annual.max when total_staked > inflation_config.expect.max;
3) annual_inflation = inflation_config.annual.ideal otherwise;

Then one also need to take into account that part of the tokens goes to parachain bond account (par_bond_percent = ParachainStaking.ParachainBondInfo.percent_of_inflation) and the fact that the lower collator’s stake the higher reward will be given per delegator. Last important point is that collator’s commission (commission = ParachainStaking.CollatorCommission) must be taken into account too. So the final formulat is the following:

apr(collator) = annual_return * (1 - par_bond_percent - commission) * (average_stake/collator.stake);

average_stake = sum(collators.stake) / count(collators);

apr_avg = annual_return * (1 - par_bond_percent - commission);

apr_max = apr(c) where c is a selected collator with minimum stake;

Turing APR calculation

For Turing network APR calculation is different in a way that inflation is not only depends on total_issued but also from a additional value that is retrived from the storage Vesting.TotalUnvestedAllocation. Thus the staked_portion is calculated in the following way:

staked_portion = total_staked / (total_issued + additional)

References:

  1. Ruslan Rezin, Nova Wallet. APR calculation for Turing Network staking

Price Automation Explained
Develop Custom Workflow