Trigger on Daml Hub / Trigger per Party?

I have an app deployed on Daml hub, trying to find out how to deploy triggers to daml hub. Most documentation deals with triggers being activated locally against the sandbox ledger, and to have a trigger run as a certain party.

What would I need to do if I were to deploy onto Daml hub?

What I’m trying to do:

Assume I am the admin, currently my ledger ID is
ledger-party-68815041-ad16-4d9a-8177-9f9b20d8fb3f,

As an admin, I would like to issue an ExampleToken to each new user

(but there is a propose and accept workflow required, which I’d like to abstract away using triggers.)

The intention is that when they login, they already have some ‘play assets’ issued by me.

First I create an AssetHoldingAccount template, with a ticker / symbol called ExampleToken. This is the account that I want each new user by default to have.

On this template, I have a choice which creates a proposal which the receiver needs to accept.

nonconsuming choice Invite_New_Asset_Holder : ContractId AssetHoldingAccountProposal
      with
        recipient : Party
      controller if resharable then owner else assetType.issuer
      do
        create AssetHoldingAccountProposal with
          account = this
          recipient

How can I achieve the following workflow:

  1. For every new party that signs in, the admin party should automatically exercise the Invite_new_asset_holder choice for the new user.

  2. For the non-admin party, upon receiving the invitation, the trigger should automatically accept for them.

Question 1 of 2
Regarding the first point, the trigger needs to react to the creation of a new contract right?

So upon login of a new user, I can create a user contract, but I can also, for example create an assetHoldingAccountRequest which is specifically used to trigger the trigger.

To tackle the automatic creation of the user template or assetHoldingAccountRequest template, the frontend React code can do the following, (and this is taken from the create-daml-app template)

const login = useCallback(async (credentials: Credentials) => {
   console.log('login Called')
   try {
     const ledger = new Ledger({ token: credentials.token, httpBaseUrl });

     // THIS PORTION BELOW COMES WITH CREAT-DAML-APP

     let userContract = await ledger.fetchByKey(User.User, credentials.party);
     if (userContract === null) {
       const user = { username: credentials.party, following: [] };
       // anyone can create this contract
       userContract = await ledger.create(User.User, user);

     // THIS IS MY 'AUTOMATION' for a new user to create an assetHoldingAccountRequest
       await ledger.create(Account.AssetHoldingAccountRequest, {recipient: credentials.party, owner: 'a'})
       console.log('created')
     }
     onLogin(credentials);
   } catch (error) {
     setError(true)
   }
 }, [onLogin]);

Upon the creation of AssetHoldingAccountRequest below

template AssetHoldingAccountRequest with
    recipient: Party
    owner: Party
  where 
    signatory recipient
    choice Accept: ()
      controller owner
      do
        return ()

from the non-admin party, to the admin, I (admin) would like to automatically exercise the Invite_New_asset_Holder choice, with this new party’s ledger ID.

Once this user receives the invitation in the ledger, the trigger should automatically accept it on his behalf.

Question 2 of 2
Do I need triggers specific to the party? and hence I would need to hardcode the ledger ID?
as an example,

autoSendRequest, do if p === hardcodedAdminLedgerID

and

autoAcceptRequest:  do if p !== hardCodedAdminLedgerID
1 Like

Hey @Max,

The good news is that it looks like you’re over-complicating your workflow a bit, so you may not need as many triggers as you think you do.

Assuming the owner in your AssetHoldingAccountRequest is the admin party and the recipient is the user, you’ll already have authorization from both the admin and the user together (the recipient coming from being a signatory on the contract, and owner from being the party exercising the choice), so you shouldn’t need to have a separate Proposal contract that the user then needs to accept.

Since anything in the body of that Accept choice has both authorizations, you can do whatever you need to do to onboard the user there - creating your account contracts, test tokens, etc. If you want to reuse your AssetHoldingAccountProposal (since it may be useful elsewhere), you can even create and accept that proposal in the choice body at the same time, something to the effect of:

template AssetHoldingAccountRequest with
    recipient: Party
    owner: Party
  where 
    signatory recipient
    observer owner
    choice Accept: ()
      controller owner
      do
        proposalCid <- exerciseByKey @AssetHoldingAccount owner Invite_New_Asset_Holder with ..
        exercise proposalCid Proposal_Accept
        return ()

In that case, you will only need one trigger - running as the owner - to accept all AssetHoldingAccountRequests (which can definitely be created by the user in the login callback).

Also note that you will need to add the owner as an observer on the AssetHoldingAccountRequest template if you haven’t - having a party as a controller on a choice does not add it automatically the way controller ... can had.

Definitely let me know if you have any questions regarding the above.

2 Likes

Hi Max,

Regarding the first point: yes, kicking off an automated propose/accept workflow starting by automatically creating contracts via frontend/React code when the user logs in is a decent pattern to employ. Triggers are not able to react to the existence of parties directly, so you’ll indeed need to create a User template and have it get created for each party when they log in to the UI.

The nice thing about this is it provides a natural spot for a more specific new user onboarding experience for your application. Suppose you want the User template to have fields for name, email, phone number, favorite color, or anything else. The UI could expose a form displayed to new users to collect this information, which the user can provide when logging in the first time. Then of course automations have a convenient way of listening for new signups by streaming & listening for User contracts - it’s a nice “win-win”

To the second question:

As @alex.graham explained, you might not need per-party triggers. However, if you really needed them…

It shouldn’t be necessary to hardcode party IDs in the trigger code itself. The trigger gets launched with the authorization of a specific party at runtime, so you’d have one trigger instance running as admin, and other instances of the same trigger running as end-user parties. The recommended approach on Daml Hub is to utilize the UserAdmin party as a global admin/operator, as its ID is well-known and publicly discoverable.

To instantiate a trigger deployment as a different party, you can use the same idea of “auto” deploying it from the UI codebase on the end-user’s behalf, by making an HTTP request to a Daml Hub API endpoint.

You could achieve the same thing with the @daml/hub-react library, which now ships with the newest create-daml-app templates. See the documentation for that here

3 Likes

Darn, I can only mark one as the accepted answer, but both are super helpful, thanks @alex.graham , @Alex_Matson

1 Like