Daml about "trigger"

Mod note: As of SDK 1.5.0 Scenarios have been superseded by the more powerful Daml Script. We now recommend using that for all purposes. For more information, and to learn how to use Script please check out @Andreas’ post on our blog.

There is not the bug , it is only my mistake who can help me.
I want match orders automatically by trigger. All orders contractIds written in Template TestTrigger. I want to use “for” to circle sendOrderIds and buyOrderIds of “TestTrigger” .
Here is my code :

module Test_trigger where

-- import Trade
import DA.Date
import DA.List hiding (dedup)
import DA.Next.Map (Map)
import Daml.Trigger

template TestTrigger
  with 
    sendOrderIds : [ContractId OrderTest]
    buyOrderIds : [ContractId OrderTest]
    operator : Party
    typeId : Text
    regulator : Party
    persons : [Party]
   where 
    signatory operator
    observer regulator ,persons 
    
    key (typeId,operator): (Text,Party)
    maintainer key._2

copyTrigger : Trigger ()
copyTrigger = Trigger
  { initialize = \_acs -> ()
  , updateState = \_acs _message () -> ()
  , rule = matchOrders
  , registeredTemplates = AllInDar
  , heartbeat = Prelude.None
  }

-- "matchOrders" function is : match each selling and buying order automatically 
-- TestTrigger sendOrderIds record all selling orderCid which had not matched ; 
-- TestTrigger buyOrderIds record all buying orderCid which had not matched

matchOrders : Party -> ACS -> Time -> Map CommandId [Command] -> () -> TriggerA ()
matchOrders party acs _time autoMatch () = do  
  let triggerdatas :[(ContractId TestTrigger , TestTrigger)] = getContracts @TestTrigger acs
  let creationTime = date 2019 Feb 8
  let typeId = "f1"
--   let operator <- getParty "Tester1"
  (matchCid , _) <- fetchByKey @TestTrigger (typeId,triggerdatas.operator)
  oldMatch <- fetch matchCid
  forA sendOrderIds(\cid->do
    forA buyOrderIds(\oid ->do
        let sends = delete cid sendOrderIds
        let buys = delete oid buyOrderIds
        sendOrder <- fetch cid
        buyOrder <- fetch oid
        create SuccMatched with orderId = sendOrder.orderId , status = "Succ" ,operator2=operator
        create SuccMatched with orderId = buyOrder.orderId , status = "Succ",operator2=operator
        create TestTrigger with sendOrderIds = sends ;buyOrderIds = buys ; operator; typeId; regulator; persons
        )
    pure()    
    )
  pure()

-- -- The following two templates are omitted 

template SuccMatched
  with
    orderId : Text
    status : Text
    operator2 : Party
  where  
    signatory operator2
    
template OrderTest
  with
    orderId : Text
    datavalue : Text
    owner : Party
    operator3 : Party
  where 
    signatory owner
    observer operator3
      

name = scenario do
  -- two orders
  seller <- getParty "seller"
  buyer <- getParty "buyer"
  operator <- getParty "operator"

  let orderId = "o11"
      orderId2 = "o22"

  submit seller do create OrderTest with orderId = "o11" , datavalue = "Test1", owner = seller, operator3=operator
  submit buyer do create OrderTest with orderId = "o22" , datavalue = "Test2", owner = buyer, operator3=operator
1 Like

Hi @huixin_liu, welcome to the forum!

There are a few crucial things to keep in mind when writing triggers:

  1. The only state a trigger can access is the active contract set. You cannot call functions like fetchByKey in a trigger. If you do find yourself needing to do that, you can write filters on the ACS to find a contract with a given id or key.
  2. The only way to change the ledger is by calling emitCommands (or one of the wrappers like dedupCreate).
  3. Triggers are asynchronous. This means that calling emitCommands will not block until the commands have been comitted to the ledger and your rule can be called again before that happened. To avoid sending the same command multiple times, emitCommands accepts a second argument that allows you to filter out contracts until the commands have been comitted (or the transaction failed).

Here is my attempt at turning your example into a working trigger. Note that I’m assuming that you want to match orders pairwise so the first buying order with the first selling order and so on. If you want a different matching, you’ll have to adapt it a bit:

import DA.Foldable
import DA.Next.Map (Map)
import Daml.Trigger

template TestTrigger
  with
    sendOrderIds : [ContractId OrderTest]
    buyOrderIds : [ContractId OrderTest]
    operator : Party
    typeId : Text
    regulator : Party
    persons : [Party]
   where
    signatory operator
    observer regulator ,persons

    key (typeId,operator): (Text,Party)
    maintainer key._2

copyTrigger : Trigger ()
copyTrigger = Trigger
  { initialize = \_acs -> ()
  , updateState = \_acs _message () -> ()
  , rule = matchOrders
  , registeredTemplates = AllInDar
  , heartbeat = Prelude.None
  }

matchOrders : Party -> ACS -> Time -> Map CommandId [Command] -> () -> TriggerA ()
matchOrders party acs _time autoMatch () = do
  -- Get all active TestTrigger contracts.
  let testTriggers : [(ContractId TestTrigger , TestTrigger)] = getContracts @TestTrigger acs
  -- Filter it down to contracts where we are the signatory.
  let myTestTriggers = filter (\(_, t) -> t.operator == party) testTriggers
  -- Get all active OrderTest contracts.
  let orderIds = getContracts @OrderTest acs
  forA_ myTestTriggers $ \(cid, testTrigger@TestTrigger{..}) -> do
      -- We match pairwise here.
      case (sendOrderIds, buyOrderIds) of
        -- Match the first elements of both lists.
        (x :: xs, y :: ys)
          | Some (_, sendOrder) <- find (\(cid, _) -> cid == x) orderIds -- Find the contracts with the corresponding ids
          , Some (_, buyOrder) <- find (\(cid, _) -> cid == y) orderIds -> do
          emitCommands
            [ createCmd (SuccMatched with orderId = sendOrder.orderId, status = "Succ", operator2 = operator)
            , createCmd (SuccMatched with orderId = buyOrder.orderId, status = "Succ", operator2 = operator)
            , exerciseCmd cid Archive -- Archive the existing TestTrigger contract
            , createCmd testTrigger with sendOrderIds = xs, buyOrderIds = ys
              -- Create a new TestTrigger contract without the orders we have matched.
            ]
            [ toAnyContractId cid ] -- mark TestTrigger as pending to avoid sending commands multiple times.
        -- If either of the lists is empty or the contract ids are not found, we archive the TestTrigger contract.
        _ -> emitCommands [exerciseCmd cid Archive] [toAnyContractId cid]

I tried to stick somewhat close to your approach of matching only one pair of orders at a time. However, you could also match all of them at once depending on your needs.

2 Likes

Thanks for @cocreature .While when is it triggered ? Daml trigger base on time or monitor some things ?

1 Like

The trigger listens on the transaction stream for transactions containing the templates you registered (AllInDar will register all templates in the DAR containing your trigger). Whenever, it receives a transaction it will trigger the rule. In addition to that, the rule will also be triggered if one of the commands your trigger submitted failed.
If needed, you can set the heartbeat field to add a time-based trigger as well. This is only useful if your trigger has some logic depending on the current time so even if the active contract set doesn’t change, it’s behavior might change.

1 Like

@cocreature @bernhard How to mark some other contract other than TestTrigger as pending while exercising choice on TestTrigger.

Scenario is: I query ACS of ContractA and based on one of the ContractA value, I will then exercise a choice on ContractB.
Here I want to mark ContractA as pending until choice on ContractB is completed.
How to achieve this?

Please open new threads for separate questions.

ok. Was facing same issue so thought of continuing here.