.canton Naming CIP#209
Conversation
meiersi-da
left a comment
There was a problem hiding this comment.
Thanks @dave-axymos . This looks like a good first step. The current write-up is rather technical, and requires quite a few things to "reverse engineered".
What do you think about structuring the document to clearly separate:
- the UX to be built: what problems are solved, and what UX flows are provided to solve them
- the economic model: who does what and why are they willing to do this?
- the technical implementation: split into
- component view: dApp(s), backends running at registrars, .dar packages vetted by users, .dar packages vetted by registrars only
- information flows: how does the information flow across the components to for the different UX flows described in (1)
- key design decisions: e.g., how to ensure uniqueness of names
- link to PoC implementation (where available)
|
|
||
| Governance of the service is managed by consensus among the approved registrars. Parameters of the service (like min pricing, vote thresholds etc) can be agreed upon by the governance layer and then are stored in the Name Registry contract itself. Registrars compete to provide name sales, renewals, and support to end users, but every name they sell is recorded in the same canonical `NameRegistry` contract. | ||
|
|
||
| The reference implementation (to follow) is a DAML contract package targeting Canton SDK 3.6.0. All authorisation flows through DAML's signatory model; the DAR would be vetted, so holders can exercise their own choices directly via the JSON Ledger API. |
There was a problem hiding this comment.
Why 3.6, that is not yet out. Did you mean 3.5, which is what provides contract keys?
There was a problem hiding this comment.
I remember hitting an issue when trying to compile against 3.5 — I'll compile against that target again and double-check!
There was a problem hiding this comment.
btw, if 3.5 provides contract keys does that mean that they're available now on devnet @meiersi-da ?
| -> assertMsg "Payment >= minPriceFloor" | ||
| -> lookupByKey @NameRecord -> assertMsg "Name not already registered" | ||
| -> Fee split via chained TransferFactory calls: | ||
| 1. Treasury transfer (paymentHoldingCids -> DRO); capture senderChangeCids |
There was a problem hiding this comment.
We generally avoid having the DSO receiver transfers. We want it to only facilitate the flows, but not partake in the flows.
| 3. Sibling[1] transfer (senderChangeCids from step 2); ... | ||
| Each transfer consumes its inputHoldingCids and returns new change holdings. | ||
| Registrar retains their fee as the final change holdings (no transfer needed). | ||
| -> create NameRecord (live immediately; usable from this point) |
There was a problem hiding this comment.
It's not clear to me who pays whom and how much.
There was a problem hiding this comment.
Yeah this needs to be nailed down and better explaiend.
Our thinking at the moment was that if there's some off-chain work.co-ordination needed between registrars then there would be fee split going on to account for that level of effort.
Then we'd also on-chain enforce things like a floor for prices
|
|
||
| #### Transfer | ||
|
|
||
| Names can be transferred either in the case of: |
There was a problem hiding this comment.
Interesting idea. In that case, I'd suggest to represent names as CIP-56 assets by making each name a holding with InstrumentId with admin = dso; id = 'name' and amount = 1.0.
This way these names can be held and managed using a token standard wallet.
There was a problem hiding this comment.
And they can be traded.
There was a problem hiding this comment.
that sounds really good - I was toying with this before and I can't remember why we moved away from it at the time. I'll revisit it in the reference impl to trial. thanks!
|
|
||
| #### Dispute lifecycle | ||
|
|
||
| Disputes occur in the case that a disputing registrar claims the registration should not stand — e.g. a rogue registrar registering an agreed reserved name, or a holder using the name in a way that violates registrar conduct policy. |
There was a problem hiding this comment.
What defines an "agreed reserved name"?
There was a problem hiding this comment.
What defines an "agreed reserved name"?
At the moment for v1 live on chain, we've a reserve list that we don't let people register out of the gate — mainly profanity and clear phishing attempts e.g. stopping end users signing up as hsbc.canton, just to allow a decision to be made on that approach.
I think there's multiple options we could explore here:
- free rein — any name goes
- literally register the names that we think may want to be held (e.g. the names of each validator/supervalidator say) with a long expiry
- an off-chain list (which we have here at the moment)
I think we need to go away and strengthen up here the "what" and the "why" both and a path to someone claiming a reservered name etc if that's the why we go down.
| -> CounterStake (takes registryCid) -> fetches live registry -> sets voteDeadline (now + registry.voteWindow) | ||
| -> continue to step 4 | ||
| | | ||
| 4. Registrars vote via AddVote (True = for dispute, False = against) |
There was a problem hiding this comment.
This sounds like it might be quite a bit of work. What's the motivation for registrars to partake in these votes?
There was a problem hiding this comment.
Appreciate the challenge to reframe here! :)
Again this is probably something that we're porting over from a trustless model. We're thinking of a flow similar to a Token Curated Registry where e.g.
- Companies agree to stake X to become a registrar
- In the event of a dispute, a contesting registrar can trigger a vote
- There's a fee split between registrars voting
a. each voting registrar receives financial incentive to vote
b. winning registrar (disputer or challenger) receives a fee
In fact, based on the "do we need a reserve list" above, maybe this piece could naturally fall away - if there are no reserved names then there's no need to dispute on chain
| -> assertMsg "Registrar not in allowlist" | ||
| -> assertMsg "Invalid name format" (isValidName — lowercase, .canton suffix, no leading/trailing hyphens, 1–63 chars) | ||
| -> assertMsg "Payment >= minPriceFloor" | ||
| -> lookupByKey @NameRecord -> assertMsg "Name not already registered" |
There was a problem hiding this comment.
There is no uniqueness guarantee for contract keys in Canton 3.x
I suspect that name allocation will have to be funnelled through an on-ledger representation of the map of all names (e.g. as a prefix tree) to ensure uniqueness even when there are many different nodes submitting name allocation transactions.
There was a problem hiding this comment.
The way we were looking to leverage contract keys was to do make sure that we gate our look-up on the fact that the multi-hosted party (the 'DRO') is the maintainer of the key and then it's choosing not to create duplicates.
So having a flow of:
- Registrar e.g. Axymos does a look-up on chain to see if a name is available
- If it is, calls
NameRegistry.RegisterName()- which does a look-up of all active contracts owned collectively by the registrar pool and checks that it doesn't exist - So then using
len(results) == 0as a proxy check for uniqueness. - Since the DRO is the key
maintainerand since all registrars are registering as the DRO then our thoughts were that were safe enough to gate it
Couple of thoughts:
- we were leaning on the atomicity and sequencing of transactions on the network to make sure that race conditions prevented the second registration
- in the case of a "rogue registrar" (e.g. someone offboarded from the allowlist but still a host of the DRO) could bypass the path of driving all registrations through
RegisterName()and instead just hitcreateCmdon NameRecord directly.
One thing we had that I seem to have accidentally dropped from NameRecord was the concept of activation. So thinking that the flow would be:
- registrar calls
NameRegistry.RegisterName() - registrats calls
NameRecord.Activate()
Then resolving a name requires that it has been activated. I'm coming from a trustless EVM world so maybe that's overkill and we've better practices in Canton Network around stuff like this (e.g. literal contractual sign-up, say)
| 4. **On-chain integrity** — Names are guaranteed unique via on-chain checks. We rely on the underlying infrastructure to solve for the race conditions/atomicity of these registrations. All authorisation flows through DAML's signatory model. | ||
|
|
||
|
|
||
| ### Open Questions |
There was a problem hiding this comment.
I'm also missing clarity on the end-to-end flow for end-user actions: how will users build and submit their transactions (e.g., will they use a ".canton dApp" built as part of this CIP, or will they use their token standard wallet)? what transactions are built and submitted by end-users, which ones by registrars?
There was a problem hiding this comment.
So existing in production for us, Axymos as registrar are submitting all TXs on the user behalf (as our DAR is running on our node, solely) - we were trying to move to a position where users would be able to do more with their own assets, so were thinking if this becomes a CIP standard, that
- end-users would be able to have the DAR vetted on their node, so could interact directly, signing TXs as themselves
- hosting nodes would be able to query the
NameRegistry"natively" from their own node (if added as an observer to the registry, or maybe if the DSO is a singleton observer? Note sure if that puts a burden on the DSO though) - non-hosting nodes or external companies could still use APIs provided by any registrar to query the records.
At the moment, we're thinking that end users would co-sign TXs for things that they particular are actioning like if they choose to release a name (Credential_ArchiveAsHolder()) or for TransferWithApproval (to support transfer/sales/etc)
You're right that we've more focused the CIP on the on-chain elements that would allow registrars to build a full end-to-end product on top, rather than including elements like what a full dApp would look like.
We can definitely add more colour here to give an example, though we imagine that elements of UX etc will be driven by individual registrars building on top of this. But definitely will try to make the "bones" more obvious of who is signing what when, from where etc.
And I think if we do implement elements like CIP-56 then maybe we get more "for free" from the core standards (we're currently modelling it based on the credential inferface but maybe Token is more of a fit)
that sounds good, thanks @meiersi-da will restructure along those lines! |
|
|
||
| ## Reference Implementation | ||
|
|
||
| Reference implementation to follow. Currently targeting Canton SDK 3.6.0 (LF target 2.3-staging), which is still in an alpha phase. |
There was a problem hiding this comment.
We're working against contract keys, still in alpha, so are only on Sandbox at the moment, and we're also aiming to align with standards coming down the track like the RegistryV1.Credential, so have that stubbed out.
Other than that though, everything is based on working DAML code (though harder to test some aspects on a single node so sure there's more edge cases to find!)
But I'll try to make time to tidy our reference implementation and get into a shareable state soon.
Co-authored-by: Simon Meier <simon@digitalasset.com> Signed-off-by: dave-axymos <dave@axymos.com>
Co-authored-by: Simon Meier <simon@digitalasset.com> Signed-off-by: dave-axymos <dave@axymos.com>
Axymos launched xNS as a naming service on Canton Network in late 2025.
Through the 'Identity and Metadata' SIG, we were invited to provide a CIP for how we could decentralise this as we move forward so that
.cantonnamespaceThis is an early draft of the CIP and we'd welcome any questions/comments/suggestions.