Skip to content

.canton Naming CIP#209

Open
dave-axymos wants to merge 7 commits into
canton-foundation:mainfrom
Axymos:main
Open

.canton Naming CIP#209
dave-axymos wants to merge 7 commits into
canton-foundation:mainfrom
Axymos:main

Conversation

@dave-axymos
Copy link
Copy Markdown

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

  • multiple registrars could also offer names in the .canton namespace
  • but all registrars are backed by a single shared canonical registry, to prevent fragmentation
  • the service could outlive any individual company as needed

This is an early draft of the CIP and we'd welcome any questions/comments/suggestions.

Copy link
Copy Markdown
Contributor

@meiersi-da meiersi-da left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. the UX to be built: what problems are solved, and what UX flows are provided to solve them
  2. the economic model: who does what and why are they willing to do this?
  3. the technical implementation: split into
    1. component view: dApp(s), backends running at registrars, .dar packages vetted by users, .dar packages vetted by registrars only
    2. information flows: how does the information flow across the components to for the different UX flows described in (1)
    3. key design decisions: e.g., how to ensure uniqueness of names
    4. link to PoC implementation (where available)

Comment thread cip-XXXX-canton-naming/cip-XXXX-canton-naming.md
Comment thread cip-XXXX-canton-naming/cip-XXXX-canton-naming.md

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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 3.6, that is not yet out. Did you mean 3.5, which is what provides contract keys?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember hitting an issue when trying to compile against 3.5 — I'll compile against that target again and double-check!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, if 3.5 provides contract keys does that mean that they're available now on devnet @meiersi-da ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract keys require connecting to a synchronizer running Canton Protocol Version 35 (PV=35), which in turn requires a Canton 3.5.x participant node that understands PV=35. For local development that's easy, just spin up a synchronizer with with PV=35 and your done.

For DevNet this requires a logical synchronizer migration to change the synchchronizers protocol from its current PV=34 to PV=35. This is coming soon. Here's the CIP proposing the upgrade: https://github.com/canton-foundation/cips/pull/208/changes#diff-2fd1b7daef2c45396290d28ec49523ef503f8af706f2308ab1932751ffe1aea2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@meiersi-da thanks, when I saw 0.6.4 drop yesterday I was hopefully it was on devnet already! 😅

I've started to deploy a "scratchnet" super validator stack to EC2 just so I can have a play around with it from our own boxes as multiple validators. Will be great when it's on devnet! thanks for sharing the schedule, that's really helpful.

-> assertMsg "Payment >= minPriceFloor"
-> lookupByKey @NameRecord -> assertMsg "Name not already registered"
-> Fee split via chained TransferFactory calls:
1. Treasury transfer (paymentHoldingCids -> DRO); capture senderChangeCids
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally avoid having the DSO receiver transfers. We want it to only facilitate the flows, but not partake in the flows.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I never replied to this — so this is our "DRO" here rather than the DSO proper. Did you mean that that's a pattern to avoid here too?

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me who pays whom and how much.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And they can be traded.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What defines an "agreed reserved name"?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like it might be quite a bit of work. What's the motivation for registrars to partake in these votes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

  1. Companies agree to stake X to become a registrar
  2. In the event of a dispute, a contesting registrar can trigger a vote
  3. 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"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Registrar e.g. Axymos does a look-up on chain to see if a name is available
  2. 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
  3. So then using len(results) == 0 as a proxy check for uniqueness.
  4. Since the DRO is the key maintainer and 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 hit createCmd on 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:

  1. registrar calls NameRegistry.RegisterName()
  2. 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

I'd have expected for the NameRecord contracts to implement both the interfaces from the token standard and the ones from the credential standard. They server different purposes and enable different kinds of usages.

It's totally fine for one contract to implement multiple interfaces.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

I'd have expected for the NameRecord contracts to implement both the interfaces from the token standard and the ones from the credential standard. They server different purposes and enable different kinds of usages.

It's totally fine for one contract to implement multiple interfaces.

Thanks @meiersi-da, yeah I've updated our latest approach to implement both CIP-56 and the in-flight credential standard. I've also looked at moving NameRegistry itself to implement the credential factory (mapping the "renew" flow into UpdateCredential, but then keeping other flows like sales outside of the credential interface and leaning on the CIP-56 transferallocation and DvP flows).

@dave-axymos
Copy link
Copy Markdown
Author

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:

1. the UX to be built: what problems are solved, and what UX flows are provided to solve them

2. the economic model: who does what and why are they willing to do this?

3. the technical implementation: split into
   
   1. component view: dApp(s), backends running at registrars, .dar packages vetted by users, .dar packages vetted by registrars only
   2. information flows: how does the information flow across the components to for the different UX flows described in (1)
   3. key design decisions: e.g., how to ensure uniqueness of names
   4. link to PoC implementation (where available)

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.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

dave-axymos and others added 2 commits May 15, 2026 21:17
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>
@dave-axymos
Copy link
Copy Markdown
Author

@meiersi-da (and everyone!) since your feedback, have been working to:

  • move the DRO to a proper externally hosted party — this simplifies a lot of flows, like e.g. the dispute flow is now:
    • one registrar raises a concern. Others vote on it via choosing to sign as the DRO and then the k-of-n threshold itself is what executes
  • aligned to both CIP-56 and RegistryV1.Credential standards. I've created a little stub DAR of the credential interfaces to test out.
    • reworked our transfer flows for sales around the CIP-56 standard on DvP.
    • for the CIP-56, I don't think I can test this on localnet though, because the OOTB wallet UI doesn't seem to support custom tokens, is that right??
  • implemented the RegistryShard concept so that
    • honest path race conditions are avoided by the contract archiving
    • rogue paths are avoided because
      • single host of the DRO can't act with its authority solo as it's now properly an external party so needs k-of-n quorum
      • honest RegisterName() calls don't pay the "penalty" of counter-signing, since the authority flows through the canonical NameRegistry contract, which is bootstrapped at the start of the network

I'm going to try and update this draft to align with these changes now, but also trying to get a scratchnet set-up working so that can test more of these corner cases with real code before locking in on shapes

@meiersi-da
Copy link
Copy Markdown
Contributor

@meiersi-da (and everyone!) since your feedback, have been working to:

  • move the DRO to a proper externally hosted party — this simplifies a lot of flows, like e.g. the dispute flow is now:

Note: we usually distinguish between decentralized parties that cannot submit transactions on their own and external parties whose tx signing keys are held externally to the validator nodes. The DSO party used for CC is setup as a decentralized party using a decentralized namespace.

  • one registrar raises a concern. Others vote on it via choosing to sign as the DRO and then the k-of-n threshold itself is what executes

  • aligned to both CIP-56 and RegistryV1.Credential standards. I've created a little stub DAR of the credential interfaces to test out.

    • reworked our transfer flows for sales around the CIP-56 standard on DvP.
    • for the CIP-56, I don't think I can test this on localnet though, because the OOTB wallet UI doesn't seem to support custom tokens, is that right??

The Splice Amulet UI does not. However you should be able to run https://github.com/canton-network/wallet/tree/main/examples/portfolio to get a CIP-56 wallet UI. Haven't tried it myself yet, but heard good things.

@dave-axymos
Copy link
Copy Markdown
Author

Note: we usually distinguish between decentralized parties that cannot submit transactions on their own and external parties whose tx signing keys are held externally to the validator nodes. The DSO party used for CC is setup as a decentralized party using a decentralized namespace.

thanks Simon, I think I understand the split as: 



  • decentralised namespace definition - control over the DRO party itself (e.g. onboarding a host of the DRO, key rotation etc)

  • then k-of-n signing - evaluating and signing individual TXs.



Is that accurate?

If so, we’re looking to split at both layers, but initially thinking of modelling them 1-to-1 (so an onboarded registrar would become a quorum party to the DND and would hold a key for signing authority as the DRO). I believe this matches the DSO model. 



We’re also assuming (for simplicity) that each host of the DRO are themselves a validator so the “party to participant” is straightforward. (And we don’t have, say, a registrar who has their own external key but who isn’t running a validator and are hosted on elsewhere. Though guess in theory we could open this up, too if desired!)



Happy to remodel that if you think a different take on this would be better for our use-case? (Or maybe in the future we decide that e.g. not every registrar needs to be part of the DND itself) 

But wanted to start with a structure that felt open to collaboration.







I'm also going to try and put more of a breakdown in next draft of where authority:

  • a) flows from a pre-signed contract
  • b) is a direct quorum of governance
  • c) is something else e.g. 2/3s majority supported by a contract

The Splice Amulet UI does not. However you should be able to run https://github.com/canton-network/wallet/tree/main/examples/portfolio to get a CIP-56 wallet UI. Haven't tried it myself yet, but heard good things.

Ah cool thanks! Will look at getting wallet bridge set-up on localnet and see if I can hook that in too.

@meiersi-da
Copy link
Copy Markdown
Contributor

Signed-off-by: dave-axymos <dave@axymos.com>
Signed-off-by: dave-axymos <dave@axymos.com>
Comment thread cip-XXXX-canton-naming/cip-XXXX-canton-naming.md Outdated
Co-authored-by: leonidr-c7 <leo@c7.digital>
Signed-off-by: dave-axymos <dave@axymos.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants