Skip to content

feat(decimals): add token amount V2 RFC#115

Open
glevco wants to merge 5 commits into
masterfrom
feat/decimal/token-amount-v2
Open

feat(decimals): add token amount V2 RFC#115
glevco wants to merge 5 commits into
masterfrom
feat/decimal/token-amount-v2

Conversation

@glevco

@glevco glevco commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

@glevco glevco requested review from jansegre and msbrogli June 2, 2026 17:05
@glevco glevco self-assigned this Jun 2, 2026
@glevco glevco moved this from Todo to In Progress (WIP) in Hathor Network Jun 2, 2026
@glevco glevco force-pushed the feat/decimal/token-amount-v2 branch 3 times, most recently from 5eb5aea to f098aba Compare June 2, 2026 17:48
@glevco glevco force-pushed the feat/decimal/token-amount-v2 branch from 9684de1 to 29917aa Compare June 2, 2026 19:49

For fee-based tokens, the fee header will support V2 values (already covered in I5), and no further discussion is required. For deposit-based tokens, we must determine how the deposit and withdrawal of HTR will work. Currently, 1% of the minted value is required in HTR (rounded up) and the same 1% can be withdrawn when melting (rounded down). For example, minting 1.00 TOK requires 0.01 HTR, and minting 1.01 TOK requires 0.02 HTR. Melting 2.00 TOK yields 0.02 HTR, and melting 1.99 TOK yields 0.01 HTR. This means deposits and withdrawals always change in steps of 0.01 HTR, and therefore we decide:

- **I8:** We keep the same step of 0.01 HTR for minting and melting custom deposit-based tokens, regardless of the increased precision. This keeps the economic model unchanged. For example, minting 1.001 TOK will require 0.02 HTR, and melting 1.999 TOK will yield 0.01 HTR. In other words, we apply the same 1% calculation, then round to the nearest HTR cent (up for deposits, down for withdrawals).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@obiyankenobi do you agree with this? Same discussion as in the project document.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, that is ok. In practice, people should not mint 0.000xxx.

The only real discussion is about NFTs. Will we still consider 0.01 as a unit? Or should we migrate so people use fee-based tokens?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In practice, people should not mint 0.000xxx.

Why not? That's different from the PoC implementation. In it, the user can indeed mint 0.000xxx TOK, and he'll pay 0.01 HTR for it. If that's a rule and we'll forbid it, I have to add it.

The only real discussion is about NFTs. Will we still consider 0.01 as a unit? Or should we migrate so people use fee-based tokens?

We can easily keep 0.01 as a unit for both versions, or we can use 10^-18 for V2. Then it's a matter of display only, in both cases we would show 1 according to the transaction's version. The latter would save a few bytes because 1 NFT would use 2 bytes, vs 4 in the current state.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not? That's different from the PoC implementation. In it, the user can indeed mint 0.000xxx TOK, and he'll pay 0.01 HTR for it. If that's a rule and we'll forbid it, I have to add it.

I said "in practice". I don't think people will mint 32.89893278347324387 USDT or some other token. They usually mint "rounder" numbers. But I agree it should be accepted by the protocol.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Even in practice, I'm not so sure. People are probably not going to manually mint those numbers, but systems can easily do that, for example if they calculate amounts to mint based on some percentage.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we put a minimum value for mint and melt operations?

@glevco glevco Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I guess it's just a business decision. Currently, it allows any value but rounds the deposit/withdraw to 1 cent of HTR. I like it because it's the most flexible for users, without changing the economical model.

Comment thread projects/token-amount-v2/0002-token-amount-v2.md Outdated
- **I15:** V1 Blueprints can only be called by V1 transactions and other V1 Blueprints, and V2 Blueprints can only be called by V2 transactions and other V2 Blueprints. This ensures that all values in a single call chain are interpreted consistently, resulting in two separate Nano "worlds" that cannot interact directly. Runtime verification will be added to prevent cross-version calls (public, view, proxy) and balance retrieval.
- **I16:** Upgrading a contract's Blueprint should work normally, and can be used to upgrade a contract from a V1 Blueprint to a V2 Blueprint, enabling support for the new decimal precision. Since the balance is normalized internally, this requires only correctly normalizing (or not) the values at the boundaries, according to the Blueprint's version (and gated by I15).
- **I17:** We cannot drop support for V1 transactions while there are still V1 contracts in the network.
- **I18:** The serialization of values and amounts in the Nano state trie must be updated to support V2 values. This will break the current state root ID.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Is that acceptable? Should we introduce a new Nano type, keeping the existing Amount as-is?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe we should rename Amount to 2Amount and create a new type 18Amount. My name suggestions are horrible but I guess that's the best approach. This would prevent blueprint devs from making mistakes assuming one thing or the other.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe this depends on the discussion in #115 (comment), so let's decide there first

@glevco glevco moved this from In Progress (WIP) to In Progress (Done) in Hathor Network Jun 2, 2026
Existing vertices on the network form an immutable signed history with V1 token amounts that cannot be changed or migrated. We must add a versioning scheme that supports both V1 and V2 vertices concurrently, while still allowing them to interact with each other.

- **I3:** Blocks do not need 18 decimal places for their rewards, so they will remain V1-only. This avoids the need to upgrade block templates and mining rigs.
- **I4:** Transactions can be either V1 or V2, with the version indicated by a signal bit on the transaction (detailed in the [Reference-level section](#i4---versioning-transactions)). Support for V2 transactions will be gated by a Feature Activation process.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would people still be able to send new V1 transactions? I think we should forbid it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because V1 transactions will be necessary to interact with contracts that use V1 Blueprints. We can deprecate them after all contracts are migrated, and we invalidate those older Blueprints.


### Vertex versioning

Existing vertices on the network form an immutable signed history with V1 token amounts that cannot be changed or migrated. We must add a versioning scheme that supports both V1 and V2 vertices concurrently, while still allowing them to interact with each other.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would that also mean a V2 for token creation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. And for OCBs. But as explained, if we use the proposed approach which is using a signal bit, the token amount versioning is orthogonal to the transaction version, so it's a single change enabling support for all subtypes of transactions.

@glevco glevco moved this from In Progress (Done) to In Review (WIP) in Hathor Network Jun 3, 2026
msbrogli
msbrogli previously approved these changes Jun 15, 2026

Existing vertices on the network form an immutable signed history with V1 token amounts that cannot be changed or migrated. We must add a versioning scheme that supports both V1 and V2 vertices concurrently, while still allowing them to interact with each other.

- **I3:** Blocks do not need 18 decimal places for their rewards, so they will remain V1-only. This avoids the need to upgrade block templates and mining rigs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But miners would have to upgrade anyways. I guess we can migrate blocks too so everything is equal (but old vertices).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't see the advantage, only downsides. We do have to keep the V1 code forever, to support old vertices, so we don't have the advantage of removing code. And we also do have to support V1 transactions even after V2 is activated for interaction with existing Blueprints, so it's not like every new vertex in the network would be V2.

And the downsides:

  • The current block reward of 2 HTR is encoded as 4 bytes in V1, and 9 bytes in V2.
  • We would have to change the reward code / template generation to be dependent on Feature Activation. It currently uses V1 only. We've never used Feature Activation for changing block templates — although It should work seamlessly in theory, it's more code to change, review, and therefore more surface for bugs.
  • What if external partners handle blocks using their own tooling? For example, miners generate output aggregators transactions. They wouldn't have to change anything if blocks keep being V1-only.

It's increased block sizes and risks for no real gain.


For fee-based tokens, the fee header will support V2 values (already covered in I5), and no further discussion is required. For deposit-based tokens, we must determine how the deposit and withdrawal of HTR will work. Currently, 1% of the minted value is required in HTR (rounded up) and the same 1% can be withdrawn when melting (rounded down). For example, minting 1.00 TOK requires 0.01 HTR, and minting 1.01 TOK requires 0.02 HTR. Melting 2.00 TOK yields 0.02 HTR, and melting 1.99 TOK yields 0.01 HTR. This means deposits and withdrawals always change in steps of 0.01 HTR, and therefore we decide:

- **I8:** We keep the same step of 0.01 HTR for minting and melting custom deposit-based tokens, regardless of the increased precision. This keeps the economic model unchanged. For example, minting 1.001 TOK will require 0.02 HTR, and melting 1.999 TOK will yield 0.01 HTR. In other words, we apply the same 1% calculation, then round to the nearest HTR cent (up for deposits, down for withdrawals).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we put a minimum value for mint and melt operations?


For fee-based tokens, the fee header will support V2 values (already covered in I5), and no further discussion is required. For deposit-based tokens, we must determine how the deposit and withdrawal of HTR will work. Currently, 1% of the minted value is required in HTR (rounded up) and the same 1% can be withdrawn when melting (rounded down). For example, minting 1.00 TOK requires 0.01 HTR, and minting 1.01 TOK requires 0.02 HTR. Melting 2.00 TOK yields 0.02 HTR, and melting 1.99 TOK yields 0.01 HTR. This means deposits and withdrawals always change in steps of 0.01 HTR, and therefore we decide:

- **I8:** We keep the same step of 0.01 HTR for minting and melting custom deposit-based tokens, regardless of the increased precision. This keeps the economic model unchanged. For example, minting 1.001 TOK will require 0.02 HTR, and melting 1.999 TOK will yield 0.01 HTR. In other words, we apply the same 1% calculation, then round to the nearest HTR cent (up for deposits, down for withdrawals).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was wondering why not allow one to mint 0.0001 TKA depositing 0.000001 HTR. Any technical reason? Or just a business rule?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just a business rule, see #115 (comment)

Comment thread projects/token-amount-v2/0002-token-amount-v2.md Outdated
Comment thread projects/token-amount-v2/0002-token-amount-v2.md Outdated

Clients can therefore update their API calls incrementally while the full node is already using V2 accounting internally, at least until V2 transactions are activated (per I4). At that point, clients must be fully updated.

### Nano contracts runtime

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

After Blueprint v2 is activated, will we allow the creation of v1 blueprints?

@glevco glevco Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we should, at least while there are contracts using existing V1 blueprints in the network, because it would be possible for an user to want to update his Blueprint to fix a bug for example, without wanting to change the logic for handling decimals in token amounts. If we forbid V1 blueprints, we would force them to change this logic which may not be easy.

After we're sure all existing contracts are migrated to V2 Blueprints, we can forbid V1 blueprints from being created, and more than that, all V1 transactions.

Comment thread projects/token-amount-v2/0002-token-amount-v2.md Outdated
- **I15:** V1 Blueprints can only be called by V1 transactions and other V1 Blueprints, and V2 Blueprints can only be called by V2 transactions and other V2 Blueprints. This ensures that all values in a single call chain are interpreted consistently, resulting in two separate Nano "worlds" that cannot interact directly. Runtime verification will be added to prevent cross-version calls (public, view, proxy) and balance retrieval.
- **I16:** Upgrading a contract's Blueprint should work normally, and can be used to upgrade a contract from a V1 Blueprint to a V2 Blueprint, enabling support for the new decimal precision. Since the balance is normalized internally, this requires only correctly normalizing (or not) the values at the boundaries, according to the Blueprint's version (and gated by I15).
- **I17:** We cannot drop support for V1 transactions while there are still V1 contracts in the network.
- **I18:** The serialization of values and amounts in the Nano state trie must be updated to support V2 values. This will break the current state root ID.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We cannot break the root ID. I'd say v1 contracts will keep storing with 2 decimals and v2 contracts will store with 18 decimals. This guarantees that all previous root ids remain the same.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Then we have a problem of upgrading contracts from V1 to V2. By making the internal serialization the same across versions, upgrading would be seamless, just a matter of changing how the values are converted to and from the nano runtime.

If we can't do that, upgrading one contract would mean migrating what's stored at the moment of upgrade. That opens a new set of questions.

  • Should we allow upgrades at all? If not, that's an important business decision. Contracts like Dozer would be locked in V1 forever.
  • If we do, how do we migrate balances?

We cannot break the root ID.

I guess an important question to make is why this is true. Would that be the case even if we had tooling to make sure the migration was correct?

That relates to: what would be the plan if we ever found a determinism bug? It would mean different root IDs between nodes, so it would be a precedent for changing root IDs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually, I guess we can. This value is not part of the block mining and it is used only to compare states between nodes. I guess all we need is a plan to prevent unnecessary warnings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually, I guess we can. This value is not part of the block mining and it is used only to compare states between nodes.

Exactly. Good, then!

I guess all we need is a plan to prevent unnecessary warnings.

I don't think we currently have any automatic warnings triggered by this, only manual checks on releases.

- **I18:** The serialization of values and amounts in the Nano state trie must be updated to support V2 values. This will break the current state root ID.

### Shielded outputs
[shielded-outputs]: #shielded-outputs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must ship this feature before shipping shielded outputs and enforce all shielded outputs to have 18 decimals. Otherwise, I guess we can't balance inputs and outputs with different versions.

@glevco glevco Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in ea92718

What if a shielded tx uses non-shielded inputs from both V1 and V2? Would that be allowed?

@msbrogli msbrogli Jun 15, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, but all shielded outputs must have 18 decimals. So v1 amounts in inputs or outputs would be scaled for balance check. The issue happens only on shielded amounts because the blinding factors make it impossible to multiply shielded amount by a constant (e.g., 10**16). But we can easily do it on transparent amounts. It would be great to get this confirmed by @LFRezende .

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So v1 amounts in inputs or outputs would be scaled for balance check

Perfect, that's already how it works.


Option #2 is viable, but unnecessarily wasteful. Adding a new flag-only header would require 1 byte for the header version (with an empty body). All V2 transactions would have to carry that extra 1 byte, on top of the already increased encoded token amount sizes.

Therefore, option #1 is the proposed approach: repurpose a signal bit in the transaction's first byte to encode the token amount version. The first byte of all vertices is a reserved signal byte intended precisely for this kind of upgrade. On mainnet/testnet, the least significant nibble of the signal byte of blocks is used for voting on Feature Activation processes. For transactions, the entire signal byte remains unused.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess the best way to put it is: Blocks and transactions share this byte during serialization. For blocks, this byte maps to signal bit field; for transactions, this byte should map to a separate flag field. I like it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this is just a matter of naming, "signal" is generic in this case and works as a synonym for "flag". All vertices have one signal byte (or one flag byte), and blocks use 4 bits for voting. 4 signal bits, 4 flag bits, reads the same for me.

So what do you mean by "for transactions, this byte should map to a separate flag field"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

After deserialization, I'd like to have it in another field (not tx.signals).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, got it! Added the name flags_byte to the reference-level on 433573d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review (WIP)

Development

Successfully merging this pull request may close these issues.

3 participants