Build trigger to create new contract once a day

I have created the trigger below for testing purposes.

timedRule: Party -> TriggerA () ()
timedRule ctrl = do
  debug (show "executed")
  pure ()
timedTrigger : RelTime -> Trigger ()
timedTrigger heartbeat = Trigger with
  initialize = pure ()
  updateState = \_ -> pure ()
  rule = timedRule
  registeredTemplates = AllInDar
  heartbeat = Some heartbeat

My goal is to create a trigger to be executed once a day and create a new contract.
After looking into digital-asset/lib-finance/trigger/src/DA/Trigger/Finance/Instrument/Entitlement.daml trigger,
I thought it could be achieved somehow by using heartbeat value to run this trigger once a day.
My first challenge it’s been running this trigger using daml trigger command, as it throws an error “… is not a valid type for a trigger”
Is it possible have this kind of trigger that runs once a day ?

2 Likes

The type RelTime -> Trigger (), as suggested by the error message, is a function that takes a RelTime and returns a trigger; it is not a trigger itself, nor will the trigger runner summon an argument to pass to that function.

The same goes for the Entitlement trigger; you can see in FinLib’s tests that we must actually supply the arguments in order to get a “real” trigger out of it. Just as you see there, you can do the same by adding a separate top-level definition of type Trigger () like myTrigger = timedTrigger (days 1).

Once you have that working, you will note that rule is executed for various ledger events, not only when the heartbeat frequency triggers. For example, if you write your timedRule to always create a contract, then you will find that your heartbeat causes a contract to be created, which causes the rule to be triggered again, which causes another contract to be created, which causes…

To understand how to deal with that, imagine that your trigger is going to go off in a couple minutes, but you restart the service. Is it appropriate that you wait for a whole day for the next event to fire? Once you have the basics working here, here is what I would suggest:

  1. Place in registeredTemplates only those that should “trigger” your rule. If that is “none”, then the very template you are creating contracts of in this rule is probably a good choice.
  2. Before you create your contract, check whether the contract you mean to create is already there, perhaps using query or queryContractKey. A trigger rule should always be written in a way that immediately running it again won’t have any effect. (Using “the current day” alongside the party as a key might be a good choice.)
  3. If possible, arrange for your contract creation to be the correction to ledger state, rather than a heartbeat interaction. That is what triggers are best at, and it is very easy to naturally satisfy (2) if you take this design approach.
  4. If you cannot arrange for this to be a reaction to ledger events instead, run your heartbeat significantly more frequently than daily. Perhaps every 15 or 30 minutes will do. Following (2) you should already be checking that “today’s” contract has been created, and this handily does away with the “server restart” problem.
  5. Thinking in ledger state also solves a different problem, which you will otherwise have to think about: what if you take the server down for a couple days? In most cases, you will want to “catch up” with multiple contract creation, but I can’t say whether that is what your goal is. It is the natural result implied by the “correction to ledger state” model, because what you do is check for contracts you haven’t “dealt with” in your trigger, and just deal with all of them. In the “server down for a couple days” scenario, this would mean a backlog of contracts that haven’t been “dealt with”, in which case your trigger just handles all of them in one go. Consider an “expiry” trigger, for example. You would not merely expire things that have expired in the past day; you would expire all items whose expiration time has passed, no matter how long ago.

The heartbeat mechanism is not meant to be as complicated as cron, with all its options and sophisticated configuration features that solve the above questions in different ways; it is merely a simple complement to what triggers are really good at, which is dealing with ever-changing ledger state.

If it turns out that you have more complicated wallclock-based semantics in mind, and cannot deal with them by means of examining ledger state changes, then a different tool might be called for, be it cron or something more “modern”, so to speak. If I have accounted for all potential issues with whatever you have in mind in my suggestions above, then triggers are probably sufficient to solve your problem.

2 Likes

If the semantics you want are really just “run this once a day”, you may indeed be better off setting up a cron job with a Daml Script.

2 Likes

Thank you guys. That’s what I needed.
The final goal is to have a Contract A,
so first I will fetch contracts and filter endDate field < today,
then, foreach, check if Contract B for it doesn’t exist yet,
if true, then create a Contract B

2 Likes

Hi @rui.genesis,

Can I get sample code of above scenario and how to run DAML trigger from the command by passing heartbeat value? I am trying to achieve the same, but not able to do so.

@bernhard @cocreature Any help or comments will be appreciated