I’m developing an escrow-style contract that needs to both receive and later release coins using the documented methods

’m developing an escrow-style contract that needs to both receive and later release coins using the documented methods:

  • receive(coin: CoinInfo) – for receiving assets

  • send(input: QualifiedCoinInfo, recipient: Either<ZswapCoinPublicKey, ContractAddress>, value: Uint<128>) – for sending assets back out

The contract successfully receives coins, but I’m currently blocked on releasing them, because the send() method requires a QualifiedCoinInfo, which includes the coin’s mt_index (Merkle tree index).

The documentation confirms that:

  1. When the contract receives a coin, the runtime or indexer already knows the mt_index where the coin’s commitment was inserted.

  2. DApps are expected to save this information by calling queryContext.insertCommitment(commitment, index) while assembling or validating the transaction.

However, there is currently no documented or programmatic way to retrieve this mt_index (or the commitment) off-chain after the coin has been received.
Without access to that, it’s impossible to construct a valid QualifiedCoinInfo, and therefore the contract cannot spend or release assets it holds.

Could you please clarify:

  1. How can a DApp obtain the mt_index (Merkle tree index) for a coin commitment after it’s received by a contract?

  2. Is there a current or planned indexer or runtime API that exposes this information (e.g., something like getCommitmentIndex(commitment))?

  3. If the expectation is to use insertCommitment() in the QueryContext, can you provide a code example or guidance on how to access or compute the commitment and index during transaction assembly?

This functionality is critical for implementing escrow and other asset-holding contract patterns. Right now, it seems there’s no viable way to release assets once they are held by a contract.

Thank you for clarifying — I’d really appreciate any example or best practice you can share for constructing a valid QualifiedCoinInfo in this context.

If I understand your doubt correctly, I believe you are referring to something like the following for deposits:

// Contract's own treasury of native tokens.
export ledger treasury: QualifiedCoinInfo;

export circuit depositFunds(coinInfo: CoinInfo): [] {
  assert(coinInfo.color == nativeToken(), "Only native tokens can be deposited");
  receive(disclose(coinInfo));
  const me = right<ZswapCoinPublicKey, ContractAddress>(kernel.self());
  treasury.writeCoin(disclose(coinInfo), me);
  return [];
}

Then to send coin from the ledger’s QualifiedCoinInfo you should be able to do something like this:

  const sender = left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey());
  const sendResult = send(treasury, sender, disclose(amount));
  assert(sendResult.sent.value == amount, "Failed to send withdrawal payment");
  if (sendResult.change.is_some) {
     const me = right<ZswapCoinPublicKey, ContractAddress>(kernel.self());
     treasury.writeCoin(sendResult.change.value, me);
  } else {
     treasury.resetToDefault();
  }

Although, bear in mind what it’s being discussed in this issue related to circuits like the above: Cannot receive coins from a contract · Issue #63 · midnightntwrk/community-hub · GitHub.