BitCANN - Bitcoin Cash for Assigned Names and Numbers – is a decentralized domain name and identity system built on the Bitcoin Cash Blockchain.
- Decentralized Domain Names like
.sat
and.bch
and more. - Add Records, RPA Pay Codes, Add Currency Addresses, Text Records, Custom Records, Social, Email, and more.
- No Renewals or Expiry*
- NFT Domain ownership, enabling secondary market trading.
- Easy lookups
- Sign-In using your Identity
- Plugin for other contract systems
- Earn by protecting the system by:
- Burning illegal registration attempts
- Identifying and burning registration conflicts
- Proving domain violations
- Contracts
- Cashtokens
- TLDs
- Genesis
- FAQs
- How are domains sold?
- Who earns from the auction sales?
- How is the correctness of the name verified?
- How are auctions created?
- What if two auctions for the same name are running?
- I won the bidding contest, how do I claim the domain?
- Why Internal Auth NFT and External Auth NFT in each Domain Contract?
- What if the tokenAmount in the CounterNFT runs out?
- An illegal registration auction has started for a domain that is owned by someone, will there be two owners?
- Can a bid be cancelled?
- Can anyone renounce ownership of a domain?
- What happens to the ownershipNFT when the ownership is renounced or the domain is abandoned?
- How do domain or record lookups work?
- How does ownership transfer work?
- How to add records?
- How to remove records?
- No Renewal or Expiry?
The architecture is built around a series of smart contracts, categorized into these main types:
-
Registry Contract: Registry.cash
-
Operational Contracts: Auction.cash, Bid.cash, DomainFactory.cash
-
Guard Contracts: AuctionNameEnforcer.cash, DomainOwnershipGuard.cash, AuctionConflictResolver.cash
-
Domain Contract: Domain.cash
-
Accumulator Contract: Accumulator.cash
The Registry contract functions as the control and storage hub. Operational, Guard, and Accumulator contracts must execute their transactions in conjunction with the Registry contract. This contract holds RegistrationNFTs, AuctionNFTs, and AuthorizedThreadNFTs.
Constructor:
domainCategory
: The category of the domain. All the NFTs in the system belong to this category.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from self | AuthorizedThreadNFT back to self + (optionally tokenAmount from auctionNFT input) |
1 | Any UTXO from Authorized contract | UTXO back to Authorized contract |
Note: The actual number and structure of inputs/outputs and covenants beyond this pair is controlled by the authorized contract being used.
The Auction contract lets anyone start a new auction. Each auction requires:
- A minimum starting bid of at least
minStartingBid
BCH. - It runs for at least
minWaitTime
(check DomainFactory contract). The timer resets with a new bid.
Constructor:
minStartingBid
: The minimum starting bid of the auction.
Transaction Structure:
Parameters:
name
: The name of the domain. This does not include the TLD. TLDs
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from Registry Contract | AuthorizedThreadNFT back to Registry Contract |
1 | Any UTXO from self | Back to self |
2 | RegistrationNFTs Counter NFT | RegistrationNFTs Counter NFT, with nftCommitment incremented by 1 and tokenAmount decreased by NewRegistrationID |
3 | Funding UTXO from bidder | AuctionNFT |
4 | OP_RETURN revealing the name | |
5 | Optional change in BCH |
The Bid contract allows anyone to bid on an active auction by allowing restricted manipulation of auctionNFT. It updates the satoshisValue
and the pkh
in the nftCommitment
. The only condition is that the new Bid amount must be at least minBidIncreasePercentage
higher. Even if the auction is passed the minWaitTime
and the winning bid has not claimed the domain's ownership, it's still possible to continue bidding which will reset the timer to atleast minWaitTime
.
Constructor:
minBidIncreasePercentage
: The minimum percentage increase in the new bid amount.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from Registry Contract | AuthorizedThreadNFT back to Registry Contract |
1 | Any UTXO from Authorized contract | UTXO back to Authorized contract |
2 | AuctionNFT | AuctionNFT with increased amount and updated commitment |
3 | Funding UTXO from new bidder | Previous bid amount returned to previous bidder |
4 | Optional change to new bidder |
The DomainFactory burns the auctionNFT and issues 3 new NFTs DomainNFTs. It verifies that the actionNFT input is at least minWaitTime
old. It also attaches the tokenAmount from auctionNFT to the authorized contract's thread.
Constructor:
domainContractBytecode
: The partial bytecode of the domain contract.minWaitTime
: The minimum wait time after which the domain can be claimed by the bidder.maxPlatformFeePercentage
: The maximum fee percentage that can be charged by the platform.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from Registry Contract | AuthorizedThreadNFT back to Registry Contract + tokenAmount from auctionNFT input |
1 | Any UTXO from self | Back to self |
2 | RegistrationNFT Domain Minting NFT | RegistrationNFT Domain Minting NFT back to registry contract |
3 | AuctionNFT | DomainNFT External Auth NFT |
4 | DomainNFT Internal Auth NFT | |
5 | DomainNFT Ownership NFT | |
6 | Platform fee and rest to miners |
These contracts serve the purpose of incentivizing the enforcement of the rules. For example, if someone were to start an auction for a domain that is already owned then the DomainOwnershipGuard contract will allow anyone to provide proof of ownership of the domain using External Auth DomainNFT and penalize the illegal auction by burning the auctionNFT and giving the funds to the proof provider.
Similarly, other contracts also provide a way to penalize anyone who attempts to break the rules of the system.
The AuctionNameEnforcer contract allows anyone to prove that the running auction has an invalid domain name. By providing proof (index of the invalid character) they burn the auctionNFT, taking away the entire amount as a reward.
INFO: The nature of this architecture is that it allows for more types of restrictions. These rules can be modified to allow for more or fewer restrictions.
Rules:
- The name must consist of only these characters
- Letters (a-z or A-Z)
- Numbers (0-9)
- Hyphens (-)
Transaction Structure:
Parameters:
characterNumber
: The index of the character in the name that is invalid (starting from 1)
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from Registry Contract | AuthorizedThreadNFT back to Registry Contract + tokenAmount from auctionNFT input |
1 | Any UTXO from self | Back to self |
2 | AuctionNFT | Reward output |
Important: Applications must verify that domain name follows the rules before starting an auction. Failing to do so will result in the user losing their bid amount.
This prevents registrations for domains that have already been registered and have owners. Anyone can provide proof of valid ownership(External Auth DomainNFT) and burn the auctionNFT and claim the funds as a reward.
Constructor:
domainContractBytecode
: The partial bytecode of the domain contract.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from Registry Contract | AuthorizedThreadNFT back to Registry Contract + tokenAmount from auctionNFT input |
1 | Any UTXO from self | Back to self |
2 | DomainNFT External Auth NFT | DomainNFT External Auth NFT back to the Domain Contract |
3 | AuctionNFT | Reward output |
Important: Applications must verify the presence of External Auth NFT in the Domain Contract before creating a new auction. Failing to do so will result in the user losing their bid amount.
If two registration auctions exist for the same domain name, the one with the higher registrationID i.e the tokenAmount is invalid. (Since registration is a single-threaded operation such scenarios are unlikely to occur willingly.)
This contract allows anyone to prove that an auction is invalid and burn the invalid auctionNFT in the process and taking away the funds as a reward for keeping the system in check.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from Registry Contract | AuthorizedThreadNFT back to Registry Contract + tokenAmount from auctionNFT input |
1 | Any UTXO from self | Back to self |
2 | Valid AuctionNFT | Valid AuctionNFT back to Registry Contract |
3 | Invalid AuctionNFT | Reward output |
Important: Applications must verify that an auctionNFT with the same name doesn't already exist in the registry contract before creating a new auction. Failing to do so will result in the user losing their bid amount. BCH's UTXO-based system has no concept of 'Contract Storage' to confirm the existence of an ongoing auction.
The Domain contract allows the owner to perform a few operations after DomainNFTs are issued from DomainFactory. There exists a unique domain contract for each unique domain name.
Constructor:
inactivityExpiryTime
: The time after which the domain is considered abandoned.name
: The name of the domain.domainCategory
: The category of the domain.
There are 3 functions in each Domain Contract:
- useAuth: This can be used to perform a variety of actions.
For example:
- Prove the the ownership of the domain by other contracts.
- Perform any actions in conjunction with other contracts. (A Lease Contract)
- Add records and invalidate multiple records in a single transaction.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
x | DomainNFTs Internal/External Auth NFT from self | Back to self |
x+1 (optional) | OwnershipNFT from owner | OwnershipNFT as output |
x+2 | OP_RETURN containing record data or removal hash |
- burn: This allows the owner of the domain to renounce ownership OR if the domain has been inactive for >
inactivityExpiryTime
then anyone can burn the domain allowing for a new auction.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | DomainNFTs Internal Auth NFT | BCH change output |
1 | DomainNFTs External Auth NFT | |
2 | Pure BCH or DomainNFTs Domain ownership NFT from owner |
- resolveOwnerConflict: Ideally, this function will never be triggered as no one would want to keep the free money on the table by not triggering the transaction that earns them money. Having said that, it's important to have a safeguard for such an unforceable future where these incentive system are unable to catch a registration conflict or burn two competing auctionNFTs for the same name at the same time period resulting in more than 1 owner for a domain. The owner with the lowest registrationID must be the only owner for a domain. To help enforce this rule, this function will allow anyone to burn both the Auth NFTs of the NEW invalid owner.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | Valid External Auth DomainNFT | Valid External Auth DomainNFT back to self |
1 | Valid Internal Auth DomainNFT | Valid Internal Auth DomainNFT back to self |
2 | Invalid External Auth DomainNFT | BCH change output |
3 | Invalid Internal Auth DomainNFT | |
4 | BCH input from anyone |
Once enough auctions have happened, there might come a time when the counterNFT's tokenAmount is not enough to create new Auction NFT. Since the amount would be accumulating in the thread NFTs, this contract can be used to transfer them back to the CounterNFT to keep the system functioning smoothly.
Transaction Structure:
# | Inputs | Outputs |
---|---|---|
0 | AuthorizedThreadNFT NFT with authorized contract's locking bytecode as commitment from Registry Contract | AuthorizedThreadNFT back to Registry Contract |
1 | Any UTXO from self | Back to self |
2 | RegistrationNFTs Counter NFT | RegistrationNFTs Counter NFT with tokenAmount from input3 |
2 | AuthorizedThreadNFT Authorized contract's UTXO with tokenAmount | AuthorizedThreadNFT without tokenAmount back to Registry Contract |
4 | Pure BCH | Change BCH |
The contracts talk to each other through cashtokens. There are 4 types in this system:
A pair of minting NFTs that exist as UTXOs within the Registry.cash contract, consisting of:
- CounterNFT: This minting hybrid NFT has nftCommitment that starts from 0 and increments by 1 with each new registration. It is also initialized with the maximum possible token amount of
9223372036854775807
that interacts with Auction.cash to facilitate the creation of new auction NFTs. Based on the value of the new registrationID from it's own commitment, the new minted AuctionNFT gets the exact tokenAmount. FAQcategory
: domainCategorycommitment
: registrationID < 8 bytes >tokenAmount
: Keeps reducing with each new registration.
- DomainMintingNFT: A minting NFT that works with DomainFactory.cash to issue new Domain NFTs. This has no nftCommitment or tokenAmount.
category
: domainCategory
A mutable hybrid NFT created for each new auction that remains within Registry.cash, containing comprehensive auction information through the following attributes:
nftCommitment
: A combination ofbidderPKH< 20 bytes > + name < bytes >
tokenAmount
: This represents the registrationIDcapability
: Mutablesatoshis
: The latest bid amountcategory
: The designated domainCategory A new bid simply updates thepkh
in thenftCommitment
and updates thesatoshisValue
to the new amount.
Each authorized contract's lockingbytecode(Excluding Domain.cash) is added to an immutable NFT commitment and sent to the Registry.cash at the time of genesis. These immutable NFTs stay with Registry.cash
forever. Any interaction with the registry must include one of these thread NFTs to create a transaction.
Structure:
category
: domainCategorycommitment
: lockingbytecode of authorized contract <35 bytes>
The Registry Contract has a designated number of threads for authorized contracts:
x = number of threads [The exact value can be anything. It must be decided at the time of genesis as these cannot be created later]
- Auction: ~x threads
- Bid: ~x threads
- DomainFactory: ~x threads
- AuctionNameEnforcer: ~x threads
- DomainOwnershipGuard: ~x threads
- AuctionConflictResolver: ~x threads
- Accumulator: ~x threads
A set of 3 immutable NFTs minted when an auction ends:
-
OwnershipNFT: This NFT proves ownership of a specific domain.
category
: domainCategorycommitment
: registrationID < 8 bytes > + name < bytes >
-
InternalAuthNFT: A specialized authorization NFT that resides within the Domain contract and must be used together with the OwnershipNFT to enable the owner's interaction with Domain.cash.
category
: domainCategorycommitment
: registrationID < 8 bytes >
-
ExternalAuthNFT: A specialized authorization NFT that resides within the Domain Contract but can be attached to any transaction, particularly utilized by DomainOwnershipGuard.cash to prove existing domain ownership and enforce penalties on illegal auction attempts.
category
: domainCategory
If the domain has been inactive for > inactivityExpiryTime
then the domain is considered abandoned and anyone can prove the inactivity and burn the Internal and External Auth NFTs to make the domain available for auction.
Top Level Domains (TLDs) like .bch
and .sat
do not exist within the contract system directly as a value
. The names in the all the NFTs in the system do not have the TLD in them. Instead, it exists in the AuthChain. This is done to allow bigger names and reduce the contract size and complexity.
During the genesis phase, the Registry.cash contract is initialized with the domainCategory
. The authHead
for this category must include the symbol and name as the TLD, making it accessible to all applications. This entry will be the first and only one in the authChain
. After this step, the authHead
must be permanently removed by creating an OP_RETURN output as the first output.
To ensure the system operates as expected, the following steps must be followed :
- Mint a new hybrid token with an NFT commitment set to 0 (8 bytes) and the maximum possible token amount of
9223372036854775807
, the tokenCategory of this NFT will bedomainCategory
. - Using the
tokenCategory
i.e domainCategory, create the locking bytecode forRegistry.cash
. - Mint a mintingNFT i.e
DomainMintingNFT
and send it to theRegistry.cash
- Determine the following parameters and generate the locking bytecode of all the other authorized contracts:
inactivityExpiryTime
minWaitTime
maxPlatformFeePercentage
minBidIncreasePercentage
minStartingBid
domainContractBytecode
- Create multiple threadNFTs for each authorized contract, commitment of each threadNFT must be the lockingbytecode of the authorized contract and the capability must be immutable.
- Send the threadNFTs to the
Registry.cash
- Remove the authhead after adding information(Name and Symbol) about the domain in the authchain.
Domains are sold through an auction. The auction starts using the Auction Contract and is open for new Bids from anyone using the Bid contract. Once no new bids have been made for a minWaitTime
period, the bidder can claim the domain by using the DomainFactory contract.
No, Once a bid is made, it's locked in.
Since this is an open protocol, the platform facilitating the interaction can attach their own address to get a percentage of the fee. The percentage of the fee is set in the contract parameters of the DomainFactory contract. The can choose to get any percentage less than maxPlatformFeePercentage
. Remaining funds are sent to the miners.
Let's assume there exists a library called bitcann
. There is how it might look like:
import { getDomain, getRecords } from 'bitcann';
const domain = getDomain('example.bch');
const records = getRecords(domain);
getDomain()
will return the address of the domain contract.
function getDomain(fullName) {
const name, tld = fullName.split('.')
const domainCategory = getCategoryForTLD(tld)
const domainCategoryReversed = binToHex(hexToBin(domainCategory).reverse())
const scriptHash = buildLockScriptP2SH32(20 + domainCategoryReversed + pushDataHex(name) + domainContractBytecode)
const address = lockScriptToAddress(scriptHash)
return address
}
getRecords()
will return the records of the domain. Getting the records is as easy as fetching the transaction history of the domain contract and checking the OP_RETURN outputs.
Yes, The owner must call the burn
function of their respective Domain contract. The function will burn the Internal Auth NFT and the External Auth NFT allowing anyone to initiate a new auction for the domain.
Since the ownershipNFT's first 8 bytes are registrationID, they cannot influence the domain contract as the new internal Auth NFT will have a different registrationID. The existing ownershipNFT renders useless.
Ownership transfer is simply transferring the ownershipDomainNFT to the new owner.
The owner can add new records using the addRecord
function of the DomainContract. Records are added as OP_RETURN outputs. Records can be found by checking transaction history.
Since the records are OP_RETURN, it's not possible to remove them. However, it's possible to provide a way that can act as a standard for applications to understand that a certain record in invalidated. To 'Invalidate' a record, the owner can create a new transaction from the Domain Contract using the addRecord
function and provide RMV + hash
in the OP_RETURN.
To prevent domains from being lost indefinitely, the owner must perform at least one activity (such as adding or invalidating records) within the inactivityExpiryTime
period. Each activity resets the inactivity timer. If the owner does not interact with the domain within the inactivityExpiryTime
, anyone can burn the Internal and External Auth NFTs to make the domain available for auction.
Check AuctionNameEnforcer
Check Auction
Check AuctionConflictResolver
Check DomainFactory
Check DomainNFTs
Check Accumulator
Check DomainOwnershipGuard