Skip to main content


This RFC defines the migration process of funds residing on WOTS addresses in the current legacy network to the post-Chrysalis Phase 2 network.


The IOTA protocol wants to move away from WOTS as it created a number of security, protocol and UX issues:

  • WOTS signatures are big and make up a disproportional amount of data of a transaction.
  • It is only safe to spend from an address once. Spending multiple times from the same address reveals random parts of the private key, making any subsequent transfers (other than the first) susceptible to thefts.
  • As a prevention mechanism to stop users from spending multiple times from the same address, nodes have to keep an ever growing list of those addresses.

In the beginning of the new Chrysalis Phase 2 network, only Ed25519 addresses are supported. The protocol will no longer support WOTS addresses. Therefore, there needs to be a migration process from WOTS addresses to Ed25519 address in the new network.

To make the migration as smooth as possible, the specified mechanism allows for users to migrate their funds at any time with only a small delay until they're available on the new network.

This RFC outlines the detailed architecture of how users will be able to migrate their funds and specifies the underlying components and their purposes.

Detailed Design

On a high-level the migration process works as follows:

  • Users create migration bundles in the legacy network which target their Ed25519 address in the new network.
  • The Coordinator then mints those migrated funds in receipts which are placed within milestones on the new network.
  • Nodes in the new network evaluate receipts and book the corresponding funds by creating new UTXOs in the ledger.

Migration timeline

  1. Users issue migration bundles which effectively burn their funds. During this period, normal value bundles and zero-value transactions are allowed to become part of a milestone cone.
  2. The Coordinator is stopped, and a new global snapshot is created which as its only solid entry point contains the last issued milestone (and put on This global snapshot is used to create the genesis snapshot containing the already migrated funds for the new network. The remainder of the total supply which has not been migrated is allocated on the TreasuryOutput. Users are instructed to check the validity of these two snapshots.
  3. A new Hornet version is released which only allows migration bundles to be broadcasted or be part of milestone cones. Users must update their node software as otherwise they will no longer peer.
  4. The legacy network is restarted with the global snapshot, and the new network bootstraps with the genesis snapshot.
  5. Further funds migrated in the legacy network are transferred to the new network using the receipt mechanism.

Changes to the legacy network

In order to facilitate the migration process, the node software making up the legacy network needs to be updated. This update will be deployed by stopping the Coordinator and forcing all nodes to upgrade to this new version.

Migration bundle

The node software will no longer book ledger mutations to non-migration addresses. This means that users are incentivized to migrate their funds as they want to use their tokens. See this document on what migration addresses are.

A migration bundle is defined as follows:

  • It contains exactly one output transaction of which the destination address is a valid migration address and is positioned as the tail transaction within the bundle. The output transaction value is at least 1'000'000 tokens.
  • It does not contain any zero-value transactions which do not hold signature fragments. This means that transactions other than the tail transaction must always be part of an input.
  • Input transactions must not use migration addresses.

The node will only use tail transactions of migration or milestone bundles for the tip-pool. This means that past cones referenced by a milestone will only include such bundles.

The legacy node software is updated with an additional HTTP API command called getWhiteFlagConfirmation which given request data in the following form:

"command": "getWhiteFlagConfirmation",
"milestoneIndex": 1434593

returns data for the given milestone white-flag confirmation:

"milestoneBundle": [
"includedBundles": [

where milestoneBundle contains the milestone bundle trytes and includedBundles is an array of tryte arrays of included bundles in the same DFS order as the white-flag confirmation. Trytes within a bundle "array" are sorted from currentIndex = 0 ascending to the lastIndex.

This HTTP API command allows interested parties to verify which migration bundles were confirmed by a given milestone.

Milestone inclusion Merkle proof

The Coordinator will only include migration bundles (respectively the tails of those bundles) in its inclusion Merkle proof. Nodes which do not run with the updated code will crash once the updated confirmation is in place.

Preventing non-migration bundles

As an additional measure to prevent users from submitting never confirming non-migration bundles (which would lead to key-reuse), nodes will no longer accept non-migration bundles in the HTTP API.

HTTP API level checks:

  • The user must submit an entire migration bundle. No more single zero-value transactions, value-spam bundles etc. are allowed.
  • Input transactions are spending the entirety of the funds residing on the corresponding address. There must be more than 0 tokens on the given address.

Wallet software must be updated to no longer support non-migration bundles.

There are no restrictions put in place on the gossip level, as it is too complex to prevent non-migration transactions to be filtered out, however, these transactions will never become part of a milestone cone.

Treasury Transaction

A TreasuryTransaction is a payload which contains a reference to the current TreasuryOutput (in form of a TreasuryInput object) and an output TreasuryOutput which deposits the remainder.

Serialized form:

Typeuint32Set to value 4 to denote a TreasuryTransaction.
Input TypebyteSet to value 1 to denote an TreasuryInput.
Milestone HashArray<byte>[32]>The hash of the milestone which created the referenced TreasuryOutput.
Output TypebyteSet to value 2 to denote an TreasuryOutput.
Amountuint64The amount of funds residing in the treasury.

Treasury Input

The TreasuryInput is equivalent to a normal UTXOInput but instead of referencing a transaction, it references a milestone. This input can only be used within TreasuryTransaction payloads.

Serialized form:

Input TypebyteSet to value 1 to denote an TreasuryInput.
Milestone HashArray<byte>[32]>The hash of the milestone which created the referenced TreasuryOutput.

Treasury Output

The TreasuryOutput is a special output type which represents the treasury of the network, respectively the non yet migrated funds. At any given moment in time, there is only one TreasuryOutput.

Serialized form:

Output TypebyteSet to value 2 to denote an TreasuryInput.
Amountuint64The amount of funds residing in the treasury.

The TreasuryOutput can not be referenced or spent by transactions, it can only be referenced by receipts.

The TreasuryOutput can be queried from the HTTP API and needs to be included within snapshots in order to keep the total supply intact.


Receipts allow for fast migration of funds from the legacy into the new network by representing entries of funds which were migrated in the old network.


Receipts are listings of funds for which nodes must generate UTXOs in the form of SigLockedSingleOutputs targeting the given address. Receipts are embedded within milestone payloads and therefore signed by the Coordinator. A milestone may contain up to one receipt as a payload. The Coordinator chooses whether to embed a receipt payload or not.

Serialized form:

Payload Typeuint32Set to value 3 to denote a Receipt.
Migrated Atuint32The index of the legacy milestone in which the listed funds were migrated at.
FinalboolFlags whether this receipt is the last receipt for the given Migrated At index.
Funds Countuint16Denotes how many migrated fund entries are within the receipt.
Migrated Funds Entry
Tail Transaction HashArray<byte>[49]The tail transaction hash of the bundle in which these funds were migrated.
Address oneOf
Ed25519 Address
Address Typeuint8Set to value 0 to denote an Ed25519 Address.
AddressArray<byte>[32]The raw bytes of the Ed25519 address which is a BLAKE2b-256 hash of the Ed25519 public key.
Amountuint64The amount which was migrated
Payload Lengthuint32The length in bytes of the payload.
TreasuryTransaction Payload



  • funds_array_count can be max 127 and must be > 0.
  • funds array must be in lexical sort order (by their serialized form).
  • any tail_transaction_hash must be unique over the entire receipt.
  • deposit must be ≥ 1'000'000 IOTA tokens.
  • payload_length can not be zero.
  • treasury_transaction must be a Treasury Transaction payload.


  • migrated_at can not decrease between subsequent receipts.
  • There must not be any subsequent receipts with the same migrated_at index after the one with the final flag set to true.
  • The amount field of the previous TreasuryOutput minus the sum of all the newly migrated funds must equal the amount of the new TreasuryOutput within the TreasuryTransaction payload.

Legitimacy of migrated funds

While the syntactical and semantic validation ensures that the receipt's integrity is correct, it doesn't actually tell whether the given funds were really migrated in the legacy network.

In order validate this criteria, the node software performs following operation:

  1. The HTTP API of a legacy node is queried for all the tail_transaction_hashes, the addresses and their corresponding migrated funds.
  2. The node checks whether the funds within the receipt matches the response from the legacy node.
  3. Additionally, if the receipt's final flag was set to true, it is validated whether all funds for the given legacy milestone were migrated by looking at all the receipts with the same migrated_at index.

If the operation fails, the node software must gracefully terminate with an appropriate error message.

In an optimal setting, node operators choose to only ask their own deployed nodes in the legacy network.

Booking receipts

After successful receipt validation, the node software generates UTXOs in the following form: A SigLockedSingleOutput is allocated with the given ed25519_address and the funds as the deposit. As there is no actual transaction which generates the UTXO and normally a UTXO ID consists of transaction hash | the output index, the milestone hash of the milestone which included the receipt with the given funds is used as the transaction hash. The output index equals the index of the funds within the receipt (this is also why the receipt is limited to 127 entries). This allows to easily look up in which milestone these funds were generated in.

If one wants to audit the UTXO path of an input, it means that milestones need to be kept forever as they're needed to recognize that a certain output was generated by it. However, this can be offloaded to a 2nd level service.

All the generated SigLockedSingleOutputs from the migrated funds are then booked into the ledger and the new TreasuryOutput is persisted as a UTXO using the milestone hash of the receipt which included the Treasury Transaction payload.


For transparency reasons, the IF offers software which renders a dashboard showing details throughout the entire migration process:

  • A list of outstanding funds residing on migration addresses to be migrated with the milestone index at which they were created.
  • Migrated funds.
  • Generated receipts.


At current legacy network ledger size of 261446 entries (addresses with ≥ 1'000'000 tokens), it would take min. ~2058 receipts to migrate all funds. While theoretically the max message size allows for more entries to be within one receipt, it is limited by the fact that the index of the migrated address within the receipt is used to generate the output_index of the generated SigLockedSingleDeposit (further explained below).

Assuming the best case scenario in which all 261446 entries were sent to migration addresses in the legacy network, these funds could therefore be migrated into the new network within ~5.7h (at a 10 second milestone interval). Of course, in practice users will migrate over time and the receipt mechanism will need to be in place as long as the new network runs.

If looked at the receipt validation from a higher-level, it becomes quite apparent that this is analogous to previous global snapshots where users would post comments on a PR on GitHub saying that they computed the same ledger state, just that it is more granular and automatic while still leveraging the same source of truth: the ledger state/database of nodes.


  • The local snapshot file format needs to also include the to be applied receipts and supply information.


Copyright and related rights waived via CC0.