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
There are a few crucial things to keep in mind when writing triggers:
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.
The only way to change the ledger is by calling emitCommands (or one of the wrappers like dedupCreate).
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.
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.
@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?