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
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 tranfer per recurrence |
extensions | Unused at the moment | array | you should just input [] here |
function() | Your callback | function |
Some additional details:
- The first recurrent transfer will be executed immediately, so the
from
account must have at least theamount
in its account. executions
needs to be at least 2, otherwise there is no point in creating a recurrent transfer as it will expire instantly- The recurrence has to be at least 24, aka 24 hours
- The maximum time a recurrent transfer can exists is 730 days (2 years), so if your
recurrence
is every 6 months, the maxexecutions
you'll be able to set will be4
- You cannot recurrent transfer Hive power
- The max
memo
size is 2048 (same for regular transfer memos) - You cannot execute recurrent transfers to yourself. If you really want to you could setup a recurrent transfer to another account that has a recurrent transfer set back to you: Bob -> Alice -> Bob
Validating that a recurrent transfer has been executed
Two virtual operations have been added to track recurrent transfer execution:
fill_recurrent_transfer
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. |
You can see a live example in this block: https://hiveblocks.com/b/55391310
failed_recurrent_transfer
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. |
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.
Recurrent transfers in practice:
Here's a few examples to help you get started:
javascript
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
}
]
}
]
]
cli_wallet
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}