BitCANN - Bitcoin Cash for Assigned Names and Numbers

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

Looking forward to feedback and discussions :slight_smile:

Additionally, I’d like to open a discussion on how we can audit contract systems like BitCANN and establish a process and how would that process look like? We did some discussion about the same at General Bull 43 “BitCANN and Smart Contract Audits” https://x.com/GeneralProtocol/status/1893361808355795254

9 Likes

On getting a contract system audited, this is my approach:

Unit Testing

Using the “mocknet” feature of CashScript is great to unit test isolated functionality.

In theory, it would probably be best practice to have unit test assertions hitting every condition of each contract; in practice, it would probably be excessive to retest similar conditions.

Currently the “mocknet” provides great debugging BitauthIDE links. But meep is also back online if you get the transaction hex.

Integration Testing

If all the contracts appear to work as intended, regression testing them on against a real node is next.

There can be automated tests against regtest, chipnet or mainnet. The advantage of regtest network is that features dependent on time can be simulated by mining new blocks.

Audit

Once the system is tested and somewhat “fixed”, they can be sent to audit.

An auditor is likely to suggest optimizations, enhancements and improvements, those changes could be easily made and retested with regression tests.

The simplest way to engage a good auditor is with money.


Just downloaded your GP talk, will circle back later.


This was the audit for Future Bticoin Cash, completed by the Bitcoin Cash Autist in July 2024.

BCA and Jason have a better understanding of what is possible in the VM beyond conventional application layer stuff.

BCA also has a broader understanding of cryptographic and on-the-wire attacks.

A markdown audit makes it easy to convey in many formats and machine readable.

4 Likes

Thanks! This is solid advice.

Along with this, I am preparing a checklist that I’ll keep on updating here:

  • Find and list the entry points
  • Find future upgrade vulnerabilities (for example relying on 40 bytes of commitment length, an attacker can update the nftCommitment in a way that creates a Denial of Service).
  • Find who can inject the input in the transaction, p2pkh or p2sh
    • For example: In cases where someone wants to store the pkh from an input, they’ll probably split the lockingbytecode but if the input is from a p2sh then the stored value in the commitment will be incorrect leading to a DoS. require(tx.inputs[x].lockingBytecode.length == 25);
  • Find potential utxo injections
  • Find unintentional burns
  • Find unintentional minting
  • Find category leaks (token going somewhere where it was not supposed to)
  • Find deadlocks due to state updates (nftCommitment getting updated in such a way that it can’t be used anymore)
  • Check Genesis configuration (Mis-configurations can lead to failed states later on)
  • Mathematical errors
  • Find input and output restrictions (If any)
  • Find version checks (timelocks, etc.)
  • Split errors without checking the length
1 Like

Auditing and security is the level “below”, but also we need the level “above” which is a nice UI interface, wallet integrations, a marketplace to easily trade the domains etc.

I’m thinking about that as I look into this, I’ll let you know if I have any ideas.

Good space with a lot of discussion about this idea: https://x.com/i/spaces/1mnxegbdNMNGX

(Might be worth /someone preserving it and uploading to Youtube)

Small update:

I recently started working on an npm package and a basic react frontend. I have been a bit busy so the progress is slow :slight_smile: I’ll speed up the development a bit now and also improve the documentation as I work on the package and app.

bitcann.js GitHub - BitCANN/bitcann.js at develop
frontend app: GitHub - BitCANN/app

2 Likes

Released the v1-draft for SORTS

The document defines the Simple Op Return Text Standard (SORTS) for use in on-chain identity and metadata systems. It is designed for append-only, immutable environments like BitCANN or similar smart contract systems, where data is in fixed-size or limited-size (e.g max 223 bytes in BCH, 2025). The SORTS enables flexible key-value data, namespacing, robust revocation, deduplication, and support for large values through fragmentation. The goal of this standard is to provide a human-readable, stateless, and portable format that supports extensibility and simple parsing.

1 Like

Thanks for sharing this project. I have a few questions that could be added in the FAQ:

1. Can the platform issuing the contract or any miner artificially bump up the price of an auction?

Let’s say I want the name “apple”, and I start an auction. The platform issuing the contract sees this and automatically makes a bid for 1 USD (in BCH), using a different address to hide themselves.
When I see the 1 USD bid, I bid let’s say 5 USD. Then the platform makes another bid of +10%, that is 5.50 USD, and I increase again to 6 USD. The platform does not bid any more, I finally get the name, and the platform makes x% of 6 USD.
What happened here is that I paid 6 USD for a name when the name had no real demand behind: the platform did not really want to use the name, they only wanted the registration money.
If the platform tunes their algorithm correctly, they can automatically get some bucks for names they do not wish to use (same for a miner, but with the probability of mining a block).
Is this possible in BitCANN?

2. Do all names belong to the same namespace?

In BitCANN there can be multiple platforms issuing registration contracts. Do the names registered by different platforms belong to the same namespace?

3. (In case all names belong to the same namespace) Can a user just become a platform and always get 100% of the registration money?

Let’s say I want to register “apple”. I can take a look at the contract that would be created using an existing platform, and then decide to build my own contract putting one of my addresses as the recepient of the registration money.
In that case I would get all the names I want for free. Doesn’t that give free range to squatters, and incentivizes users to bypass platforms?

4. The fact that name registrations are visible from the start doesn’t it make it easier for squatters to squat?

In naming systems, squatters try to purchase the names that will have more demand so that later they can resell them at a higher price. However, since auctions are fully public from the start in BitCANN, that signals squatters where the demand is.

Why is this prefered to hiding the name during registration as Namecoin does?

2 Likes

Thank you for looking into it, I’ll add these as part of the QnAs on Bitcann Protocol

Platform: The fee is collected on an auction-by-auction basis. It’s possible for anyone to bypass any or all steps of the platform. Any technically advanced user, another platform, or a third-party software can be used as a legitimate competitor to facilitate users claiming their domain and potentially offering a cashback, reducing the platform’s revenue. Hence, the platform is disincentivized to engage in such activity, as new competitors will emerge and disrupt their revenue model. Platforms are incentivized to give the best user experience and maintain a network effect for their own benefit.

Miner: No miner can be certain they will mine a specific block a given number of blocks in the future. Hence, if they try to artificially pump up the price, they must rely on other participants to place a higher bid and hope that they themselves mine the block containing the winning bid.

Yes, they belong to the same namespace/TLD.

No, it’s not possible for anyone to claim a domain solely by knowing the contract that will be created. Each domain contract includes restrictions that make self-claiming impossible. Ownership must be obtained through the auction process.

Since it’s not possible to hide the domain being auctioned directly, an alternative approach is to use a hash-based auction.

  • Creating a registry of hashes is easy, which undermines the perceived privacy of a hash-based auction.

  • We can probably agree that satoshi.bch has far more value than a3n-ns210.bch, so a hash-based system is only making it harder for a select few squatters. Any dedicated squatter would still be easily able to know the name that is hashed by looking up into a private/public registry.

  • Since it’s an auction and not a fixed price, it gives interested parties a fair chance to compete for ownership of a name.

  • Squatters must initiate or place a bid in an auction and hope that no one else participates. If another bidder enters, the squatter faces increased financial burden, as each new bid must be higher by a certain percentage (e.g., 5%), discouraging speculative hoarding.

  • At the contract level, if the revealed name is greater than 20 bytes, it’s impossible to claim it, essentially locking money forever. This adds more to the already weak case for using a hash-based auction.

Furthermore, BitCANN allows people to do a lot with their domains. This means someone might buy up many and develop a leasing platform, renting them out to others.

Quite a bit of discussion of BitCANN on the latest Podcast episode.

1 Like

Thank you for the answers!

The fee is collected on an auction-by-auction basis. It’s possible for anyone to bypass any or all steps of the platform. Any technically advanced user, another platform, or a third-party software can be used as a legitimate competitor to facilitate users claiming their domain and potentially offering a cashback, reducing the platform’s revenue. Hence, the platform is disincentivized to engage in such activity, as new competitors will emerge and disrupt their revenue model. Platforms are incentivized to give the best user experience and maintain a network effect for their own benefit.

Makes sense, although it would be difficult for a user to come to a conclusion that the platform is increasing prices, since the address will be different. So the user would need to compare the average final auction price of all name registrations between platforms, and come to the conclusion that another platform is “less rigged”.

Miner: No miner can be certain they will mine a specific block a given number of blocks in the future. Hence, if they try to artificially pump up the price, they must rely on other participants to place a higher bid and hope that they themselves mine the block containing the winning bid.

Agreed, miners have it more difficult to extract money from the system, but still there could be a certain range that could be beneficial (according to their probability to mine a block). Nonetheless, it seems the reward would be too small to compensate such a setup.

No, it’s not possible for anyone to claim a domain solely by knowing the contract that will be created. Each domain contract includes restrictions that make self-claiming impossible. Ownership must be obtained through the auction process.

Sorry, by “knowing the contract” I meant it makes it easier for anyone to “reverse engineer” and become a platform in their own right. They would still need the necessary libraries/SDK to build a new contract just like any regular platform.

The thing is, if anyone can potentially be a platform, then users have the possibility to:

  1. Create contracts using a platform address they control.
  2. Start auctions using a user address they control.
  3. No matter the final auction price, all the money will go to the platform’s address controlled by them.
  4. The user gets the name and recovers the money.

I suppose that the question is:

5. Can the platform owner use a regular user address to acquire names and recover the bidded money (since they control the platform’s address too), meaning the names are free for a platform owner?

Besides acquiring free names, the platform would also gain fake activity and appear more popular than it is.

  • Creating a registry of hashes is easy, which undermines the perceived privacy of a hash-based auction.
  • We can probably agree that satoshi.bch has far more value than a3n-ns210.bch , so a hash-based system is only making it harder for a select few squatters. Any dedicated squatter would still be easily able to know the name that is hashed by looking up into a private/public registry.

I see, yes, this makes sense. Still, a rotating salt could be used, though it would mean you’d need 2 salted hashes (current and next) for each name (otherwise you cannot compare hashes that have the rotation point between them but are separated by less than an hour, or the time range elligible for comparison).

  • Since it’s an auction and not a fixed price, it gives interested parties a fair chance to compete for ownership of a name.

This would make sense if the interested parties happened to decide to acquire the name at similar times. But this is a rare occurrence unless the auction lasts a long time, which gives a bad UX. This means that most likely, the users acquiring a name will be bidding against squatters (which want to resell or lease) or cheaters (platform owner, miners).

By hiding the name behind salted hashes, you ensure that only someone that happened to want the same name within the same time window can bid for it.

  • Squatters must initiate or place a bid in an auction and hope that no one else participates. If another bidder enters, the squatter faces increased financial burden, as each new bid must be higher by a certain percentage (e.g., 5%), discouraging speculative hoarding.

Still, squatters will acquire names as long as the cost does not exceed future expected gains (reselling, leasing), so I don’t think this compensates for not having salted hashes.

  • At the contract level, if the revealed name is greater than 20 bytes, it’s impossible to claim it, essentially locking money forever. This adds more to the already weak case for using a hash-based auction.

Interesting, that means the client app hashing the name would need to make sure to disallow long names.

Since each auction is unique and likely involves a different individual or address, it’s unclear how platforms will predict whether a bidder will follow through with a higher bid after the platform has already placed one. That said, I think there may be cases where the platform or other actors will attempt to randomly test the waters to see if they can inflate the price without ultimately winning the name themselves.

Once the bitcann’s static 8 contracts are set in place, no one can change them. It’s not possible to create contracts using any platform address. Infact, there isn’t a concept of platform address in the contracts.

I see, to tackle this issue, the ‘factory’ contract that ends up creating the name has a parameter called maxPlatformFeePercentage and that is set only once at the genesis phase, let’s say that is set to 50% then will only be able to get 50% of their money back. So the platform has a lot to loose by generating fake revenue.
Also, for a single instance of bitcann an auction on a completely different platform will be visible on all the other platforms as well, allowing bidding and any other interaction.

Hmm, I am not able to imagine/understand it correctly. Who will add the salt and then create the hash?

  • If the party starting the auction adds it then it’s not known by the next bidder so that’s not going to but it.
  • If the salts are already known, that circles back to the same issue we have but more complicated

I am sure I am missing the point, can you give an example?

Right, from what I have researched, there is no clear solution to squatting so disincentivising is a great way to find a balance.

I think I can imagine a way to ‘refund’ this at the contract level as well but I am still not convinced that hashes provide any real value.

Once the bitcann’s static 8 contracts are set in place, no one can change them. It’s not possible to create contracts using any platform address. Infact, there isn’t a concept of platform address in the contracts.

I was under the impression that:

  • Anyone can become a platform, with the necessary libraries/SDKs
  • At genesis phase, the platform creator can choose any parameters they desire.

I see, to tackle this issue, the ‘factory’ contract that ends up creating the name has a parameter called maxPlatformFeePercentage and that is set only once at the genesis phase, let’s say that is set to 50% then will only be able to get 50% of their money back. So the platform has a lot to loose by generating fake revenue.
Also, for a single instance of bitcann an auction on a completely different platform will be visible on all the other platforms as well, allowing bidding and any other interaction.

Can I create a platform that sets 100% payout to the platform ( maxPlatformFeePercentage := 100) at genesis phase?

If so, then I can start auctions, and no matter the final price, all the auction money will go to me, since I created the platform with 100% payout.

Who will add the salt and then create the hash?

You can use (part of) the last block hash; this would require the cheater to build the hashes each hour (or whatever the max duration of an auction is).

Also, the important part of hashes is that they protect non-obvious names. If a squatter has a list of obvious names, they won’t wait until a user makes an auction, they’ll just start the auction as soon as possible to get them the cheapest.

But non-obvious names are not on any list. By hashing them, you hide them from squatters/cheaters.

The bottom line is: squatters won’t waste time hashing popular/known names, they’ll just acquire them. What you are protecting with hashes is more unpopular names that noone has considered yet.

If your auctions have names in clear text, you are just providing the squatters/cheaters a full list of all popular and unpopular names, and they can counter-bid any name they wish.

1 Like

Hey, thank you for your patience! I have been doing a lot more research and brainstorming over the past few days. The concept of “anyone can be a platform” is broken and doesn’t work as I originally thought it would. In the next message, I’ll share a ‘dual decay mechanism’ designed to make BitCANN a public good.

  • Salted hashes make more sense for Vickrey auctions, but they have poor UX and aren’t useful for English auctions, since a bidder doesn’t know what the hash resolves to.
  • BitCANN includes a penalisation contract that rewards those who prove a given name contains an invalid character. Anyone who can demonstrate the presence of an invalid character can claim the funds from the running auction, ensuring that only a set of allowed characters are used. The allowed characters are ICANN compatible just like ENS and Unstoppable Domains. This opens the door for BitCANN to be natively included in browsers like Brave, and potentially be recognized as a global TLD in the future.
  • The only space available in the NFT commitment for a hash right now is 20 bytes, as the first 20 bytes are used for the bidder’s PKH. If hashed name commitments are used, it introduces a vulnerability in a post-quantum world where an attacker could potentially find a different, invalid name that hashes to the same value as a valid one. This would make it impossible to register new names and to take away the funds from potentially all the running auctions.
  • Either people can own hashes or names. If the name has to be revealed at some point, many applications would then rely on a trusted external indexer to resolve the hash to a name. There would need to be a cooldown period after the name is revealed to allow for penalisation, ensuring the name follows the guidelines. Applications would need to store the name as hash for the user, and if that name is lost during the auction process, the funds could be locked forever. Bad UX.
  • The disclosed name must be stored in an on-chain NFT, which can be verified against the hash after the cooldown period ends to ensure the same name is used to claim it. In a post-quantum world, it’s possible to present a valid name but then claim an invalid one during claim. The NFT commitment is limited to 40 bytes, so any name longer than that would lock up funds. Furthermore, it would also be possible for someone to reveal a name that resolves to the hash of a target name, creating a temporary situation of dual ownership.

Dual Decay Mechanism

Parameter Value Notes
Initial auction price 0.01 BCH from 1st registration
Auction price decay rate 0.0003% 326667 registrations to reach 0.0002 BCH
End auction price 0.0002 BCH from 326667 registrations onwards
Genesis incentive decay rate 0.001% 99858 names to reach 0 payout

1. Decaying Auction Price:

Every name is claimed via an English-style auction with a base price. This starting price begins at 0.01 BCH (~$5, as of 1st July 2025) and decreases at a fixed rate of 0.0003% per name claimed, reaching a floor of 0.0002 BCH after roughly 326,667 registrations.

This has the following advantages:

  • If the BCH price appreciates, the auction price may become too high, pricing out users and resulting in fewer names being claimed. If it’s too low, a few actors could dominate early name registrations, hoard them, or even lease them out. The decay mechanism balances these extremes.
  • If the BCH price does not appreciate, bidding can serve as the price discovery mechanism.
  • Reduces early hoarding and squatting.

2. Decaying Genesis Incentive:

The Decaying Genesis Incentive serves a dual purpose: it funds the creator’s efforts to launch and grow the system. It also acts as a critical check against early miner monopolization, preventing a few miners who may discover the system early from recycling rewards to accumulate names before the broader network is aware.

  • Incentivises the creator to continuously promote and improve the system, fund the development, integrations, community building, etc. No work = no payout.
  • Creates a natural sunset for economic power, avoiding long-term rent seeking behavior.
  • Ensures the system eventually becomes public good governed by market forces.

Known limitations:

  • [Early phase]: In the initial stages, the creator may be tempted to bid on names to inflate prices and secure either a higher payout or acquire the name at little to no cost if others don’t outbid them.
    However, as the incentive decays to zero by around ~99,858 registrations, the opportunity for such behavior diminishes. Compared to global domain statistics, this risk window represents a very small fraction of total registrations.

Global Domain Registration Statistics

System Total Domains Registered
Global (all domains) >362 Million
ENS (Ethereum Name Service) >2.8 Million
Unstoppable Domains >4 Million
  • [Early phase]: If the creator also operates as a miner during the early phase, they are in the strongest position to game the system.
import matplotlib.pyplot as plt

INITIAL_AUCTION_PRICE_BCH = 0.01  # Starting auction price in BCH (10,000,000 satoshis)
AUCTION_END_PRICE_BCH = 0.0002  # Ending auction price in BCH (200,000 satoshis)
DECAYING_GENESIS_INCENTIVE_DECAY_RATE_PERCENT = 0.001  # Decaying genesis incentive fee decay rate per step
DECAYING_AUCTION_PRICE_DECAY_RATE_PERCENT = 0.0003  # Decaying auction price decay rate per step
DUST_THRESHOLD_BCH = 0.00001  # Minimum amount before considering as dust (10,000 satoshis)

MAX_SIMULATION_STEPS = 500000  # Maximum number of steps to simulate
SHOW_TICKS_EVERY_STEPS = 30000  # Show x-axis ticks every N steps for readability

# Global variables to track when thresholds are first reached
first_dust_threshold_step = None
first_auction_price_threshold_step = None

def calculate_sequences():
    """
    Calculate decaying genesis incentive fee decay and miner fee accumulation based on auction price sequence.
    
    For each step, the decaying genesis incentive fee decays by a percentage of the current auction price,
    and the miner fee accumulates the decayed amount. When decaying genesis incentive fee reaches dust threshold,
    all remaining auction price goes to miner fee.
    
    The function uses global configuration parameters and updates global tracking variables
    for when thresholds are first reached.
    
    Returns:
        tuple: A tuple containing three lists:
            - decaying_genesis_incentive_fee_sequence: List of remaining genesis incentive fees for each step
            - miner_fee_sequence: List of miner fees collected for each step  
            - auction_price_sequence: List of auction prices for each step
    """
    global first_dust_threshold_step
    global first_auction_price_threshold_step

    # Initialize fee sequences - miner fee starts at 0, decaying genesis incentive fee starts at first auction price
    auction_price_sequence = [INITIAL_AUCTION_PRICE_BCH]
    miner_fee_sequence = [0]  # Miner fee starts at 0 BCH
    decaying_genesis_incentive_fee_sequence = [INITIAL_AUCTION_PRICE_BCH]  # Decaying genesis incentive fee starts at initial auction price
    current_step = 1

    while current_step <= MAX_SIMULATION_STEPS:

        # Calculate cumulative decay percentage based on current step
        decay_percentage_to_the_step = current_step * DECAYING_AUCTION_PRICE_DECAY_RATE_PERCENT / 100

        # Get auction price for current step with linear decay
        current_auction_price = INITIAL_AUCTION_PRICE_BCH * (1 - decay_percentage_to_the_step)

        # Check if auction price has reached minimum threshold
        if(current_auction_price <= AUCTION_END_PRICE_BCH):
            if(first_auction_price_threshold_step is None):
                first_auction_price_threshold_step = current_step
            current_auction_price = AUCTION_END_PRICE_BCH
        auction_price_sequence.append(current_auction_price)
        
        # Calculate base decay amount as percentage of current auction price
        base_decay_amount = current_auction_price * (DECAYING_GENESIS_INCENTIVE_DECAY_RATE_PERCENT / 100)
        
        # Decay amount increases with step number (step * base_decay)
        total_decay_amount = base_decay_amount * current_step

        # Calculate remaining amounts after decay
        decaying_genesis_incentive_fee_remaining = current_auction_price - total_decay_amount
        miner_fee_for_step = total_decay_amount

        # Check if decaying genesis incentive fee has reached dust threshold
        if decaying_genesis_incentive_fee_remaining <= DUST_THRESHOLD_BCH:
            # Record the first step when dust threshold is reached
            if first_dust_threshold_step is None:
                first_dust_threshold_step = current_step
            
            # When dust threshold is reached, all auction price goes to miner fee
            decaying_genesis_incentive_fee_remaining = 0
            miner_fee_for_step = current_auction_price

        # Store calculated values for current step
        decaying_genesis_incentive_fee_sequence.append(decaying_genesis_incentive_fee_remaining)
        miner_fee_sequence.append(miner_fee_for_step)
        current_step += 1

    return decaying_genesis_incentive_fee_sequence, miner_fee_sequence, auction_price_sequence


if __name__ == "__main__":
    # Calculate decaying genesis incentive and miner fee sequences based on auction price decay
    decaying_genesis_incentive_fee_sequence, miner_fee_sequence, auction_price_sequence = calculate_sequences()

    # Display simulation results
    print(f"First step reaching dust threshold: {first_dust_threshold_step}")
    print(f"Total decaying genesis incentive fees collected: {sum(decaying_genesis_incentive_fee_sequence):.8f} BCH")
    print(f"First step reaching auction price threshold: {first_auction_price_threshold_step}")

    # Create visualization of the fee decay simulation
    plt.figure(figsize=(12, 6))

    # Plot decaying genesis incentive (red line)
    plt.plot(
        range(len(decaying_genesis_incentive_fee_sequence)), 
        decaying_genesis_incentive_fee_sequence, 
        label=f"Decaying Genesis Incentive ({DECAYING_GENESIS_INCENTIVE_DECAY_RATE_PERCENT}%/step)", 
        color="red", 
        linewidth=1
    )

    # Plot miner fee (blue line)
    plt.plot(
        range(len(miner_fee_sequence)), 
        miner_fee_sequence, 
        label=f"Miner Fee ({DECAYING_GENESIS_INCENTIVE_DECAY_RATE_PERCENT}%/step)", 
        color="blue", 
        linewidth=1
    )

    # Plot auction price decay (green line)
    plt.plot(
        range(len(auction_price_sequence)), 
        auction_price_sequence, 
        label=f"Auction Price Decay ({INITIAL_AUCTION_PRICE_BCH} to {AUCTION_END_PRICE_BCH} BCH over {DECAYING_AUCTION_PRICE_DECAY_RATE_PERCENT}%/step)", 
        color="green", 
        linewidth=1
    )

    # Configure plot appearance
    plt.title(f"Decaying Genesis Incentive vs Miner Fee vs Auction Price Decay\n"
              f"Decay Rate: {DECAYING_GENESIS_INCENTIVE_DECAY_RATE_PERCENT}%/step, Dust Threshold: {DUST_THRESHOLD_BCH:.8f} BCH, "
              f"Max Steps: {MAX_SIMULATION_STEPS:,}")
    plt.xlabel("Simulation Step")
    plt.ylabel("Amount (BCH)")
    plt.legend()
    plt.grid(True, alpha=0.3)

    # Format y-axis for better readability of small values
    plt.yscale('linear')
    plt.gca().yaxis.set_major_formatter(plt.ScalarFormatter(useMathText=True))
    plt.gca().ticklabel_format(style='sci', axis='y', scilimits=(0,0))

    # Set x-axis tick intervals for better readability
    plt.xticks(range(0, len(decaying_genesis_incentive_fee_sequence), SHOW_TICKS_EVERY_STEPS))

    plt.tight_layout()
    plt.show()

Telegram link updated: Telegram: View @bitcann_protocol