Recurrent transfers can be a little tricky to understand, they do not in fact work like a series of transfer operations. So I thought I would write a doc to explain all of the details.
The recurrent transfer operation takes a lot of parameters, here's an example in javascript:
Transfers assets, such as HIVE or HBD, from one account to another.
hive.broadcast.recurrentTransfer(wif, from, to, amount, memo, recurrence, executions, extensions, function(err, result) {
console.log(err, result);
});
| Parameter | Description | Datatype | Notes |
|---|---|---|---|
| wif | Active private key for the from account | string | |
| from | Account name to take asset from | string | No leading @ symbol |
| to | Account name to place asset into | string | No leading @ symbol |
| amount | Amount of of asset to transfer | string | "X.XXX ASSET" must have 3 decimal places. e.g. "5.150 HBD" |
| recurrence | How often will the payment be triggered | integer | e.g. 48 - unit: hours |
| executions | The times the recurrent payment will be executed | integer | e.g. 10 - one transfer per recurrence |
| extensions | Unused at the moment | array | you should just input [] here |
| function() | Your callback | function |
Some additional details:
from account must have at least the amount in its account.executions needs to be at least 2, otherwise there is no point in creating a recurrent transfer as it will expire instantlyrecurrence is every 6 months, the max executions you'll be able to set will be 4memo size is 2048 (same for regular transfer memos)The pair_id is an optional extension that allows you to create multiple independent recurrent transfers between the same two accounts. Without pair_id, you can only have one recurrent transfer from account A to account B. With pair_id, you can have multiple different recurrent transfers between the same sender and receiver.
0[[1, {"pair_id": 120}]]When you create a recurrent transfer, the blockchain identifies it by three values:
from)to)pair_id (defaults to 0 if not specified)This means alice -> bob with pair_id=0 is a completely different recurrent transfer than alice -> bob with pair_id=1. Both can exist simultaneously and operate independently with different amounts, recurrence schedules, and execution counts.
In cli_wallet, you can use the recurrent_transfer_with_id command which accepts a pair_id parameter. This is different from the standard recurrent_transfer command which doesn't support pair_id.
Two virtual operations have been added to track recurrent transfer execution:
It's a virtual operation that indicates that a recurrent transfer has been executed correctly.
| Field | Type | Description |
|---|---|---|
| from | string | The user that initiated the transfer (source of amount). |
| to | string | The user that is the target of the transfer (receiver of amount). |
| amount | string | The amount of HIVE or HBD transferred in the current iteration. |
| memo | string | A memo attached to the transfer. |
| remaining_executions | uint16 | The number of remaining pending transfers. |
| extensions | array | Contains pair_id if non-zero: [["recurrent_transfer_pair_id", {"pair_id": N}]] |
You can see a live example in this block: https://hiveblocks.com/b/55391310
It can happen that a recurrent transfer fails if the from account does not have enough tokens to transfer when the recurrent transfer reaches it's due date.
When this happens, the blockchain pushes a failed_recurrent_transfer:
| Field | Type | Description |
|---|---|---|
| from | string | The user that initiated the transfer (source of amount that has not enough balance to cover it). |
| to | string | The user that is the target of the transfer (would be receiver of amount, but no transfer actually happened). |
| amount | string | The amount of HIVE or HBD that was scheduled for transfer in the current iteration but failed. |
| memo | string | A memo attached to the transfer. |
| consecutive_failures | string | The number of failed iterations. |
| remaining_executions | uint16 | The number of remaining pending transfers. |
| deleted | bool | true if the whole recurrent transfer was discontinued due to too many consecutive failures. |
| extensions | array | Contains pair_id if non-zero: [["recurrent_transfer_pair_id", {"pair_id": N}]] |
Some details:
if there are too many consecutive_failures, the blockchain will delete the recurrent transfer. At that point the deleted flag will be set to true to indicate of the deletion. Right now the threshold is set to 10 consecutive failed payments.
In case of failure, the recurrent transfer will not be retried. This means that if your recurrent transfer is set to execute weekly, the next time the recurrent transfer would execute is in a week.
Here's a few examples to help you get started:
const hive = require('@hiveio/hive-js');
async function recurrent_transfer(wif, from, to, amount, memo, recurrence, executions) {
return new Promise(resolve => {
hive.broadcast.recurrentTransfer(wif, from, to, amount, memo, recurrence, executions, [], function (err, result) {
console.log(err, result);
return resolve("=")
});
})
}
// Send 2 HBD from bob to initminer every week for a month
await recurrent_transfer("5KS4VfGeaWLVG76P4QcBUiJD...", "bob", "initminer", "2.000 HBD", "this is a memo", 7, 4)
The best way to track the execution of recurrent transfers is to get the account history and filter on the two virtual operations:
const { ChainTypes, makeBitMaskFilter } = require('@hiveio/hive-js/lib/auth/serializer')
const op = ChainTypes.operations
const walletOperationsBitmask = makeBitMaskFilter([
// op.recurrent_transfer,
op.fill_recurrent_transfer,
op.failed_recurrent_transfer,
])
hive.api.getAccountHistory("bob", 200, 200, ...walletOperationsBitmask, function(err, result) {
console.log(err, result);
});
Output is an array with all the operations:
[
[
6,
{
"trx_id": "0000000000000000000000000000000000000000",
"block": 75,
"trx_in_block": 4294967295,
"op_in_trx": 2,
"virtual_op": true,
"timestamp": "2023-01-04T19:02:06",
"op": [
"fill_recurrent_transfer",
{
"from": "initminer",
"to": "bob",
"amount": "1.100 TBD",
"memo": "this is a memo",
"remaining_executions": 0
}
]
}
],
[
7,
{
"trx_id": "0000000000000000000000000000000000000000",
"block": 75,
"trx_in_block": 4294967295,
"op_in_trx": 2,
"virtual_op": true,
"timestamp": "2023-01-04T19:02:06",
"op": [
"failed_recurrent_transfer",
{
"from": "bob",
"to": "alice",
"amount": "2.000 TBD",
"memo": "this is a memo",
"consecutive_failures": 10,
"remaining_executions": 1,
"deleted": true
}
]
}
]
]
Create a 1 HBD transfer from alice to bob every 24 hours for 5 days:
recurrent_transfer alice bob "1.000 HBD" "This is a memo" 24 5 true true
get the ops in the account history to track completion:
get_account_history bob 20 20
example output:
# BLOCK # TRX ID OPERATION DETAILS
---------------------------------------------------------------------------------------------------
1 11 2ad56e183d3498dcdf2ec3a187a8ed9b7499a4e3 account_created {"new_account_name":"bob","creator":"initminer","initial_vesting_shares":"0.000000 VESTS","initial_delegation":"0.000000 VESTS"}
2 36 f843d208a773b5abb8bc9370caf228ad94868d4d recurrent_transfer {"from":"initminer","to":"bob","amount":"1.100 HBD","memo":"this is a memo","recurrence":1,"executions":200,"extensions":[]}
3 36 0000000000000000000000000000000000000000 fill_recurrent_transfer {"from":"initminer","to":"bob","amount":"1.100 HBD","memo":"this is a memo","remaining_executions":199}
4 46 36322b5d787f77a40ac6037b20f3d0118b6284e8 recurrent_transfer {"from":"initminer","to":"bob","amount":"1.100 HBD","memo":"this is a memo","recurrence":1,"executions":2,"extensions":[]}
5 55 0000000000000000000000000000000000000000 fill_recurrent_transfer {"from":"initminer","to":"bob","amount":"1.100 HBD","memo":"this is a memo","remaining_executions":1}
6 75 0000000000000000000000000000000000000000 fill_recurrent_transfer {"from":"initminer","to":"bob","amount":"1.100 HBD","memo":"this is a memo","remaining_executions":0}
7 76 18d281ec590f09e31a719eaa6fdd1a1f95d0d76e transfer_to_vesting {"from":"initminer","to":"bob","amount":"5000.000 TESTS"}
8 76 18d281ec590f09e31a719eaa6fdd1a1f95d0d76e transfer_to_vesting_completed {"from_account":"initminer","to_account":"bob","hive_vested":"5000.000 TESTS","vesting_shares_received":"129.595700 VESTS"}
9 78 0159c7ebb95385accf979afddc3eba15b17d770a recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","recurrence":1,"executions":10,"extensions":[]}
10 78 0000000000000000000000000000000000000000 fill_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","remaining_executions":9}
11 97 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":1,"remaining_executions":8,"deleted":false}
12 117 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":2,"remaining_executions":7,"deleted":false}
13 137 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":3,"remaining_executions":6,"deleted":false}
14 157 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":4,"remaining_executions":5,"deleted":false}
15 177 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":5,"remaining_executions":4,"deleted":false}
16 197 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":6,"remaining_executions":3,"deleted":false}
17 217 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":7,"remaining_executions":2,"deleted":false}
18 237 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":8,"remaining_executions":1,"deleted":false}
19 257 0000000000000000000000000000000000000000 failed_recurrent_transfer {"from":"bob","to":"initminer","amount":"2.000 HBD","memo":"this is a memo","consecutive_failures":9,"remaining_executions":0,"deleted":false}