Issue with Fetching Inactive Contract During Tokenized Instrument Purchase in DAML

Hello DAML Community,

I am currently working on a DAML-based project involving tokenized instruments, and I have encountered an issue while attempting to buy units of a tokenized instrument. The error message I am receiving indicates that the contract is not active, and it appears that the fetch operation is being performed on an inactive contract.

Problem Description:

I am able to successfully create and tokenize instruments, but when attempting to execute the Buy command for a tokenized instrument, I get the following error:

{
“errors”: [
“NOT_FOUND: CONTRACT_NOT_ACTIVE(11,8226547d): Interpretation error: Error: Update failed due to fetch of an inactive contract …”,
],
“ledgerApiError”: {
“code”: 5,
“details”: [
{
“errorCodeId”: “CONTRACT_NOT_ACTIVE”,
“metadata”: {
“participant”: “‘participant1’”,
“category”: “11”,
“tid”: “c6f89defd498877856735777bd9fb5bc”,
“definite_answer”: “false”,
“commands”: “{readAs: , deduplicationPeriod: {duration: ‘PT168H’}, submittedAt: ‘2025-05-08T06:21:43.571735Z’, ledgerId: ‘participant1’, applicationId: ‘app’, submissionId: ‘8226547d-3f68-4326-a260-dd448f3a4580’, actAs: [‘Admin::12207c188703ce0486c8259f06cbbc6969b828d4c567015de3d540112fca46ae669d’, ‘Investor::1220b37fc68d69ab0bb1904e2b702c6126b890efc4ef30d0320cd720d4343f48e80f’], commandId: ‘19098422-a108-47ef-a58c-f81c1c5ff534’, workflowId: }”
},
“type”: “ErrorInfoDetail”
},

],
“message”: “CONTRACT_NOT_ACTIVE(11,8226547d): Interpretation error: Error: Update failed due to fetch of an inactive contract …”,
“status”: 404
}
}

Context:

  • Ledger Setup: I am using separate configuration files for the Admin and Investor parties, running on different ledger instances:
    • participant1 (Admin’s ledger): Configured to run on port 8093
    • participant2 (Investor’s ledger): Configured to run on port 8094
  • Workflow:
    1. Admin creates a new instrument (Instrument template).
    2. Admin tokenizes the instrument using the CreateTokenized choice and adds the Investor as a viewer.
    3. The Investor attempts to buy units from the tokenized instrument using the Buy choice, but this results in the error.

Possible Cause:

Based on the error, it seems that:

  1. The Buy command is being issued from participant2 (Investor’s ledger), but the contract was created on participant1 (Admin’s ledger).
  2. The contract may not be shared or visible between the two participants.
  3. The Admin and Investor parties might not have the correct actAs or readAs rights on the contract, or the contract may have been archived or consumed in a sub-transaction before the Buy command could be executed.

Question:

  1. How can I ensure that the Investor on participant2 can correctly fetch and interact with the contract created on participant1?
  2. Do I need to explicitly share or divulge the contract between participants? If so, how can I achieve that in DAML?
  3. Are there any other common pitfalls when interacting with contracts across different participants that I should be aware of?

Relevant DAML Code:

Here is a simplified version of the code for context:

module Instrument where

template Instrument
with
issuer: Party
assetName: Text
instrumentId: Text
where
signatory issuer

template TokenizedInstrument
with
issuer: Party
assetName: Text
instrumentId: Text
where
signatory issuer
observer issuer

choice Buy : ContractId TokenizedInstrument
  with
    investor: Party
    units: Decimal
  controller investor, issuer
  do
    create TokenizedInstrument with
      issuer = issuer
      assetName = assetName
      instrumentId = instrumentId

Configuration Files:

The configuration for Admin and Investor is as follows:

Admin:
server {
address = “0.0.0.0”
port = 8093
}
ledger-api {
address = “localhost”
port = 5111
}
ledger-id = “participant1”

Investor:
server {
address = “0.0.0.0”
port = 8094
}
ledger-api {
address = “localhost”
port = 5121
}
ledger-id = “participant2”

I suspect the problem might be related to how the contracts are shared or fetched across different ledger participants, or possibly missing rights for actAs/readAs. Any guidance or suggestions to resolve this issue would be greatly appreciated.

Thank you in advance for your help!

You have a couple options to make the contracts visible to the investor:

  • You can add the investor party as an observer on the contracts. See the docs here.
  • You can use a more advanced mechanism called “Explicit Disclosure,” in which a copy of the contract is exported on the Admin side, shared with the investor through a separate service that you create specifically for sharing contracts, then imported into the Investor’s participant. See the docs here.

You may also find DA’s free training helpful. Both of the above topics are covered specifically, though I would recommend going through the training in order. It’s an investment that pays off.

Welcome to the forums!

1 Like

Thank you for your analysis of the cross-participant visibility issue. I’d like to clarify that I have indeed properly implemented observers in my TokenizedInstrument template as you recommended. Here’s the relevant portion of my code:

template TokenizedInstrument
with
issuer: Party
assetName: Text
instrumentType: Text
instrumentId: Text
totalSupplyToken: Decimal
description: Text
complianceDisclaimers: Text
availableUnits: Decimal
tokenFaceValue: Decimal
tokenPurchasePrice: Decimal
tokenIssueDate: Text
tokenMaturityDate: Text
tokenInterestRate: Decimal
viewers: [Party]
where
signatory issuer
observer issuer
observer viewers – Explicitly adding viewers as observers

key (issuer, instrumentId) : (Party, Text)
maintainer key._1

choice Buy : (ContractId TokenizedInstrument, ContractId Investment)
  with
    investor: Party
    units: Decimal
  controller investor, issuer
  do
    assertMsg "Investor must be a viewer" (elem investor viewers)
    -- rest of implementation...

Additionally, the tokenization process explicitly adds investors as viewers when creating the instrument:

choice CreateTokenized : ContractId TokenizedInstrument
with
quantity: Decimal
initialViewers: [Party] – Investors passed here
controller issuer
do
create TokenizedInstrument with
viewers = initialViewers – Set as observers
– other fields…

Given that the observer pattern is correctly implemented according to DAML’s documentation, I believe the issue must lie elsewhere in the cross-participant setup. Some possibilities I’m considering:

  1. The participants may not be properly interconnected in the ledger topology
  2. There might be an issue with how parties are allocated across participants
  3. The authentication tokens might not be properly scoped

Would you be able to provide more specific guidance about:

  1. Required participant interconnection configurations?
  2. Any additional debugging steps to verify cross-participant visibility?
  3. Common pitfalls in multi-participant setups that could cause this behavior?

Thank you again for your assistance - the observer explanation was very helpful in verifying this aspect of my implementation.

  1. Required participant interconnection configurations?

The key is that they are connected to the same domain (a.k.a, synchronizer).

  1. Any additional debugging steps to verify cross-participant visibility?

Have you learned about the Canton Console and Remote Administration yet? That’s a good way. I would start daml canton-console. And then run participant1.health.ping(participant2) to see if they are connected.

  1. Common pitfalls in multi-participant setups that could cause this behavior?

Are you only allocating one admin party and one investor party? Sometimes newbies will allocate both parties on both participants. I don’t see evidence of that here, but it is worth checking.

What version of Canton are you using?

  • Both participant1 (Admin) and participant2 (Investor) are connected to the same domain (mydomain).
  • Confirmed via:
    participant1.health.ping(participant2)
    res41: Duration = 6991 milliseconds

@ participant1.domains.list_connected()
res42: Seq[ListConnectedDomainsResult] = Vector(ListConnectedDomainsResult(domainAlias = Domain ‘mydomain’, domainId = mydomain::12207e7480a1…, healthy = true))

@ participant2.domains.list_connected()
res43: Seq[ListConnectedDomainsResult] = Vector(ListConnectedDomainsResult(domainAlias = Domain ‘mydomain’, domainId = mydomain::12207e7480a1…, healthy = true))

Particiapnt allocation * participant1 (Admin’s node): Hosts only the Admin party

  • participant2 (Investor’s node): Hosts only the Investor party

I’m using canton-open-source-2.9.7

Also I’ve noticed that the TokenizedInstrument contract is being consumed before the Buy command is executed, resulting in the CONTRACT_NOT_ACTIVE error. I’m unsure why this is happening.

  • Workflow:
    1. Admin creates a new instrument (Instrument template).
    2. Admin tokenizes the instrument using the CreateTokenized choice and adds the Investor as a viewer.
    3. The Investor attempts to buy units from the tokenized instrument using the Buy choice, but this results in the error.

I can create and tokenize assets, and tested with postman. creation and tokenize asset is working but the Investor can’t buy them across participants.

Working Steps:
:one: Create Instrument (Works)

  • Admin on participant1 creates instrument successfully

:two: Tokenize (Works)

  • Admin tokenizes with Investor as viewer
  • Contract ID 0014e75... created

Broken Steps:
:three: Investor Visibility (Fails)

  • Investor on participant2 cannot see the tokenized contract
  • Fetch returns 404 NOT_FOUND despite being listed as observer

:four: Buy Attempt (Fails)

  • When trying to buy via participant2:
    {“errors”: [“CONTRACT_NOT_ACTIVE”], “status”: 404}
    Both participants connected to same domain and participant1.health.ping(participant2) succeeds. Also Investor party correctly assigned to participant2
    Why can’t Investor see contracts they’re observers on?

And you are using the HTTP JSON Server? Do you have one-per-participant? Are you querying the correct HTTP JSON Server?

Yes, I am using the HTTP JSON Server, and I have one JSON server per participant. Below are the configurations I’m using:
Participant 1 – Admin role (port 8093):
server {
address = “0.0.0.0”
port = 8093
}
allow-insecure-tokens = true
ledger-api {
address = “localhost”
port = 5111
}
ledger-id = “participant1”
auth {
type = “jwt-hs256”
secret = “secret”
issuer = “daml”
}

Participant 2 – Investor role (port 8094):
server {
address = “0.0.0.0”
port = 8094
}
allow-insecure-tokens = true
ledger-api {
address = “localhost”
port = 5121
}
ledger-id = “participant2”
auth {
type = “jwt-hs256”
secret = “secret”
issuer = “daml”
}

I have enabled the parties as follows:
val admin = participant1.parties.enable(“Admin”)
val investor = participant2.parties.enable(“Investor”, waitForDomain = DomainChoice.All)

  • The asset creation and tokenization are done using the Admin via http://localhost:8093/v1/create and .../v1/exercise.
  • The buy operation is triggered via the Investor on http://localhost:8094/v1/exercise.

However, I’m getting the following error during the buy operation:
CONTRACT_NOT_ACTIVE: Update failed due to fetch of an inactive contract …TokenizedInstrument…
This likely means the contract has already been consumed before the Investor tried to act on it.

The solution involving adding the Investor as an observer to the TokenizedInstrument contract did not resolve the issue.
Even after successfully creating and fetching the new instrument contract with the correct instrumentId, attempting to exercise the Buy choice still results in a CONTRACT_NOT_ACTIVE error.

@Neethu_M
I see a bunch of inconsistencies on this thread, which make it hard to accurately diagnose the problem. E.g. you say that the transaction to exercise the Buy choice on the TokenizedInstrument contract is submitted to participant2, but the original error message posted at the start of the thread points to participant1:

“errorCodeId”: “CONTRACT_NOT_ACTIVE”,
“metadata”: {
“participant”: “‘participant1’”,

This error suggests that fetching the contract on participant1 failed, while you’re saying you have no problem fetching the contract on participant1, and only trying to fetch it on participant2 fails.

Rather than diagnosing the problem, I would propose to first address the design issue I see in your code. You define both the issuer and the investor as controllers on the Buy choice. This means the authority of both of these parties is required to exercise the choice. This also means that under normal circumstances a transaction, where the top level action is to exercise the Buy choice, must be submitted by a participant user with ActAs privileges to both parties. However, you want to host the parties exclusively on separate participants. This is where the design breaks, as a participant user can only carry the ActAs/ReadAs rights to the parties hosted on this participant. In other words you cannot have a participant user with ActAs rights to both the issuer and the investor parties that are exclusively hosted on different participants.
The necessity to submit a transaction with the authority of multiple parties usually indicates a flaw in the design of Daml contracts. In your example, I don’t quite see the reason to include the issuer as a controller on the Buy choice. If you just leave the investor as the sole controller, the consequences of the choice will still be authorized by both the investor (as the choice controller) and the issuer (as a signatory on the TokenizedInstrument contract).

What I suggest is that, unless you have a compelling reason to have multiple controllers on the Buy choice, you remove the investor as a controller, and then run a clean test:

  1. Submit the transactions that create the Instrument and TokenizedInstrument contracts to participant1 using a participant1 user that carries the ActAs rights only for the issuer party.
  2. Submit the transaction that fetches the TokenizedInstrument contract on participant1 using a participant1 user that carries the ActAs rights only for the issuer party.
  3. Submit the transaction that fetches the TokenizedInstrument contract on participant2 using a participant2 user that carries the ActAs rights only for the investor party.
  4. Assuming the steps above are successful, submit the transaction that exercises the Buy choice on the TokenizedInstrument contract on participant2 using a participant2 user that carries the ActAs rights only for the investor party.

Please, let us know the results.

2 Likes

Hi everyone,

Thank you for the helpful insights!

I was able to resolve the issue. Initially, I was using two separate participants—one for the Admin and one for the Investor. However, the contract created by the Admin wasn’t accessible to the Investor, as they were operating on different participants and the contract visibility wasn’t properly established.

To fix this:

  • I moved both Admin and Investor to a single participant. This allowed both parties to act within the same ledger context, ensuring visibility of contracts.
  • Additionally, I found that the contract I was trying to fetch during the purchase had already been archived before the Buy command was executed. I updated the logic to avoid prematurely archiving the contract, which resolved the fetch issue.

Everything is working as expected now. Thank You!

@Neethu_M
From what you’re saying, the problem must have been addressed by fixing the logic that was archiving the TokenizedInstrument contract before the Investor could fetch it or exercise a choice on it. Moving both parties to the same participant is actually unnecessary for the parties to be able to view a contract that they’re stakeholders on. If you host the Investor party on its own participant, the Investor should still be able to fetch the TokenizedInstrument contract, where the Investor party is an observer. Hosting the Admin and the Investor party exclusively on their own participant would, however, be a problem if both parties are the controllers on the Buy choice, as I explained previously. To be able to exercise the choice, you would need to create a participant user with ActAs claims to both parties, which means both parties would need to be hosted on the same participant.

1 Like