Recurrent transfer documentation

in #documentationlast year

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.

image.png

art by dall-e

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);
});
ParameterDescriptionDatatypeNotes
wifActive private key for the from accountstring
fromAccount name to take asset fromstringNo leading @ symbol
toAccount name to place asset intostringNo leading @ symbol
amountAmount of of asset to transferstring"X.XXX ASSET" must have 3 decimal places. e.g. "5.150 HBD"
recurrenceHow often will the payment be triggeredintegere.g. 48 - unit: hours
executionsThe times the recurrent payment will be executedintegere.g. 10 - one tranfer per recurrence
extensionsUnused at the momentarrayyou should just input [] here
function()Your callbackfunction

Some additional details:

  • The first recurrent transfer will be executed immediately, so the 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 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 max executions you'll be able to set will be 4
  • 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.

FieldTypeDescription
fromstringThe user that initiated the transfer (source of amount).
tostringThe user that is the target of the transfer (receiver of amount).
amountstringThe amount of HIVE or HBD transferred in the current iteration.
memostringA memo attached to the transfer.
remaining_executionsuint16The 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:

FieldTypeDescription
fromstringThe user that initiated the transfer (source of amount that has not enough balance to cover it).
tostringThe user that is the target of the transfer (would be receiver of amount, but no transfer actually happened).
amountstringThe amount of HIVE or HBD that was scheduled for transfer in the current iteration but failed.
memostringA memo attached to the transfer.
consecutive_failuresstringThe number of failed iterations.
remaining_executionsuint16The number of remaining pending transfers.
deletedbooltrue 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}
Sort:  

Good explanation. I guess it’s a new implementation and would soon be on hivedev portal soon. Also, why’s is this not posible with hive powers ?

Because hive power is not liquid and thus not transferable

I had wondered how these worked, great detailed post to go through it all - thanks for taking the time to enlighten us.

Maybe worth adding to https://developers.hive.io ;)

It's planned :)

Wow..
I just learnt a lot from this post
Thanks a lot... It has always been confusing for me but I guess I can understand now.... A little though but maybe by the time I go through it like 3times, I'll get more of it

Thanks for sharing

untitled.gif


The rewards earned on this comment will go directly to the people( @hivehotbot ) sharing the post on Twitter as long as they are registered with @poshtoken. Sign up at https://hiveposh.com.

Bookmarked, this will be really useful to me once I get my node up.

thanks for this

@howo question for you:

Is there any scope for Monthly based recurrence i.e. 15th of every Month or 2nd Tuesday of each Month?

Hi ! The answer is no.

I kinda figured that would be a reach for the back end... I think I'm going to push people toward weekly subscriptions at 1/4 the amount they were thinking of for a monthly one.

I mean I totally see the use case buuut it's a bit tricky to implement so idk if it's worth it

Not wanting to flog this too far but something akin to the CRON interface would cover everything.

Perhaps that's not impossible to implement? I do understand that whatever code is used has to run in the core of Hive and be extremely performant.

Anyway I don't see a lot of usage of recurrent transfers yet so we should keep an eye on this if my use case starts to get traction.