Hi all,
We're building
otcDigital, a multi-participant marketplace for
RWAs on Canton. The
marketplace connects buy-side institutions, sell-side firms, qualified custodians, transfer agents, and FMIs
- covering primary market workflows (tokenization, subscription, allocation, DvP settlement) and secondary market trading and instant settlement across multiple asset classes.
We've been implementing
CIP-0112 as our standard throughout, and hit a gap I'd like to raise for discussion.
For reference: we're using Splice 0.6.12, Canton 3.5.8, Daml SDK 3.5.2, splice-api-token-holding-v2-1.0.0, -allocation-v2-1.0.0, and -allocation-instruction-v2-1.0.0 DARs.The situationIn institutional markets, most assets aren't held directly by end investors
- they're held by a
qualified custodian (a regulated bank or broker-dealer) on behalf of the client. The HoldingV2.HoldingView data model seems to cover this: Account.owner for the beneficial owner (investor) and Account.provider for the custodian, with a spec requirement that both MUST have visibility. So we set up accounts that way.
In our first implementation, the investor was correctly named as Account.owner in the view, but still could not query the holding unless the concrete template also declared them as an observer.
The reason is a gap between the view semantics and Canton's routing mechanism. HoldingV2.HoldingView is data that a party reads after they have access to a contract
- it plays no role in determining who receives the contract. Canton routes contracts based solely on the signatory and observer clauses of the concrete Daml template. An investor named in Account.owner who isn't also declared as observer in the underlying template receives nothing, regardless of what the view says.
Please correct us if our observations are wrong.
The practical issue is that these account fields are interface view data. They describe the account after a party already has access to the contract; they do not themselves route the contract to that party. Each implementor must still decide how to connect the account parties, especially Account.owner, to concrete Daml stakeholder or observer declarations.
What we didWe built a concrete holding template that explicitly declares investor parties as Canton observers, routing the contract to their participant node. We also caught that our initial view had Account.owner = custodian
- semantically backwards from the spec's intent. The fix is straightforward: set Account.owner = investor in the view AND declare them as observer in the template body. But these are two separate steps, and the spec currently provides no guidance connecting them. For allocation-based settlement, we now propagate this through the typed
CIP-0112 field AllocationSpecification.authorizer
.
Note that this solution is not interoperable: a third-party wallet implementing only HoldingV2.Holding cannot determine from the interface whether Account.owner actually has ledger access.
Questions for the group
1) Can the Foundation confirm that Account.owner = beneficial owner (investor) and Account.provider = custodian is the intended mapping for the qualified custody pattern? Or is the intended mapping different?
2) Is there a normative reference implementation of the qualified custody pattern that shows how Account.owner is wired to a Canton observer declaration? If not, would the Foundation consider publishing one?
3) Should
CIP-0112 add clearer
conformance guidance or reference tests for qualified custody, specifying when a compliant holding should route to the Account.owner party's participant node, and how that should be represented in concrete Daml templates?
4
) How does the standard envision handling multi-tier custody chains (investor → custodian → prime broker → CSD)? Account has two tiers; real custody chains often have more.
Keen to hear how others are handling this, particularly anyone integrating custodian or asset manager workflows.
Thanks,