Problem
TransferPreapproval requires signatory admin, receiver. Creating it via create_contract needs both parties in act_as. In a multi-participant Canton deployment - where admin and receiver are hosted on separate participants - neither participant can authorize both parties. A submission from the admin's participant cannot include the receiver's authorization, and vice versa.
This means the direct transfer path in SimpleTokenRules (which requires a preapproval CID in extraArgs.context) is unusable in production Canton topologies. Only the two-step transfer path works out of the box.
The existing tests use submitMulti [admin, receiver] (a Daml Script convenience that bypasses participant boundaries), which works in a single-ledger test environment but doesn't reflect real multi-participant deployments.
Proposed Solution
Add a nonconsuming choice CreatePreapproval to SimpleTokenRules. Since SimpleTokenRules already has signatory admin, the receiver can exercise this choice via disclosed contracts to create a TransferPreapproval with both signatures - each party signing on their own participant.
nonconsuming choice CreatePreapproval : ContractId TransferPreapproval
with
receiver : Party
instrumentId : InstrumentId
expiresAt : Optional Time
meta : Metadata
controller receiver
do
assertMsg "Instrument not supported by this factory"
(instrumentId.id `elem` supportedInstruments)
assertMsg "instrumentId.admin must match factory admin"
(instrumentId.admin == admin)
create TransferPreapproval with admin; receiver; instrumentId; expiresAt; meta
How it works
Admin creates SimpleTokenRules on their participant (already exists)
Receiver obtains the SimpleTokenRules disclosed contract blob
Receiver exercises CreatePreapproval on their participant using the disclosed contract
Admin's authorization flows from SimpleTokenRules signatory; receiver's authorization comes from being the choice controller
The resulting TransferPreapproval has signatory admin, receiver - both signatures obtained without multi-party submission
Multi-participant flow (with disclosed contracts)
Receiver's participant Admin's participant
| |
| ← obtains SimpleTokenRules blob → |
| |
|-- exercise CreatePreapproval ----------->|
| (disclosed SimpleTokenRules) |
| |
| TransferPreapproval created |
| (signatory: admin + receiver) |
After the preapproval exists, any sender can do a direct (one-step) transfer to the receiver by passing the preapproval CID in extraArgs.context.values["transfer-preapproval"] - exactly as the existing directTransfer path in Rules.daml expects.
Problem
TransferPreapprovalrequires signatory admin, receiver. Creating it viacreate_contractneeds both parties inact_as. In a multi-participant Canton deployment - where admin and receiver are hosted on separate participants - neither participant can authorize both parties. A submission from the admin's participant cannot include the receiver's authorization, and vice versa.This means the direct transfer path in
SimpleTokenRules(which requires a preapproval CID in extraArgs.context) is unusable in production Canton topologies. Only the two-step transfer path works out of the box.The existing tests use submitMulti [admin, receiver] (a Daml Script convenience that bypasses participant boundaries), which works in a single-ledger test environment but doesn't reflect real multi-participant deployments.
Proposed Solution
Add a
nonconsuming choice CreatePreapprovaltoSimpleTokenRules. SinceSimpleTokenRulesalready hassignatory admin, the receiver can exercise this choice via disclosed contracts to create aTransferPreapprovalwith both signatures - each party signing on their own participant.How it works
Admin creates
SimpleTokenRuleson their participant (already exists)Receiver obtains the SimpleTokenRules disclosed contract blob
Receiver exercises
CreatePreapprovalon their participant using the disclosed contractAdmin's authorization flows from SimpleTokenRules signatory; receiver's authorization comes from being the choice controller
The resulting
TransferPreapprovalhas signatory admin, receiver - both signatures obtained without multi-party submissionMulti-participant flow (with disclosed contracts)
After the preapproval exists, any sender can do a direct (one-step) transfer to the receiver by passing the preapproval CID in extraArgs.context.values["transfer-preapproval"] - exactly as the existing directTransfer path in Rules.daml expects.