ICNS has a decoupled architecture, the system is separated into 4 principal components, and we integrated CAP for history record storage. Main canister IDs are listed below:

Name Canister ID CAP Canister
registry e5kvl-zyaaa-aaaan-qabaq-cai ebop2-oyaaa-aaaan-qabcq-cai
registrar e2lt7-uaaaa-aaaan-qabaa-cai eineg-yqaaa-aaaan-qabda-cai
default resolver euj6x-pqaaa-aaaan-qabba-cai -
reverse registrar etiyd-ciaaa-aaaan-qabbq-cai -

1. Registry

The ICNS registry stores the list of all domains and subdomains, information about each domain is stored in a Record, a Record has the following fields:

public type Record = {
    var name : Text; // the ICNS name, e.g. icns.icp
    var owner : Principal; // the owner of this name
    var resolver : Principal; // the resolver canister id
    var ttl : Nat64; // caching time-to-live
    var expiry : Time.Time; // name expiry
    var controller: Principal; // controller, able to set records for this domain
    var operator : Principal; // operator, able to transfer the name
};

Main methods:

1.1 set record for a name, only domain owner can do this

// if success, return CAP tx record id, else return error msg
public type TxReceipt = Result.Result<Nat, Text>;
public shared(msg) func setRecord(
	name: Text, 
	owner: Principal, 
	resolver: Principal, 
	ttl: Nat64, 
	expiry: Time.Time
) : async TxReceipt

1.2 set subdomain record, only the owner/controller of node name can do this

public shared(msg) func setSubnodeRecord(
	node: Text, 
	sublabel: Text, 
	owner: Principal, 
	resolver: Principal, 
	ttl: Nat64, 
	expiry: Time.Time
) : async TxReceipt 

Note: to prevent attacks, only certain names can add subdomain: registrar canister can set subdomain of "icp", and reverse registrar canister can set subdomain of "principal.reverse" for reverse resolution. This limitation will be removed in the future.

1.3 queries

// get record of a name, returns null if name not exist
public query func getRecord(name: Text) : async ?Record
// get names of given user
public query func getUserDomains(key: Principal) : async ?[Record]
// get names the given user controls
public query func getControllerDomains(key: Principal) : async ?[Record]

1.4 the registry also implemented ERC721 style interfaces

public query func balanceOf(user: Principal): async Nat
public query func isApproved(name: Text, who: Principal) : async Bool
public query func getApproved(name: Text): async ?Principal
public query func isApprovedForAll(owner: Principal, op: Principal) : async Bool
public shared(msg) func setApprovalForAll(op: Principal, approved: Bool) : async TxReceipt
public shared(msg) func approve(name: Text, op: Principal): async TxReceipt
public shared(msg) func transfer(to: Principal, name: Text): async TxReceipt
public shared(msg) func transferFrom(from: Principal, to: Principal, name: Text): async TxReceipt

2. Registrar

We only support “.icp” top-level domains now, the default registrar is in charge of the “.icp” domain registration. Currently, users can only get ICNS names through auction.

The payment is made with WICP, if a user is outbid in an auction, the WICP payment is credited to the user’s account and can be withdrawn anytime, or use for payment in later auctions.

public type Auction = {
    name: Text; // the name in auction
    var endTime: Int; // auction end time
    var currentBidder: Principal; // current top bidder
    var currentBid: Bid; // current top bid
};
public type BidState = {
    #lost; // outbid by others
    #processing; // temporarily the top bidder
    #win; // won the auction
};
public type Bid = {
    name: Text; // name for bid
    price: Nat; // bid price
    timestamp: Time.Time; // bid time
    var state: BidState; // bid state
};