"Illegal" CIS-2 transfer

I’d like to get some clarity on how to deal with the following case:

Transaction: https://ccdexplorer.io/mainnet/transaction/f07180e58564d8ed0d2864aad1911189f11811f5f5fc5937bb4cb535d91625d7

This mints a CIS-2 token to an account (account B) and transfers an amount from account A to account B.

My problems with this:

  1. Account A does not exist.
  2. This is the first time this account A has been involved with this CIS-2 token, hence it does not have any tokens to transfer…this leads to a negative balance.

According to the CIS-2 spec, token amount is an unsigned integer.

[Update: CCDScan also displays the same transfer event, but hides the (non-existing) account in the token holder overview. ]

All emitted events by a CIS2 smart contract (e.g. transfer, mint, and burn events) should enable the calculation off-chain which positive balance (or 0) all accounts have.

That account A has a negative balance can not happen based on the standard if the events are logged correctly by the smart contract.

This means that the smart contract should:

  • Log a mint event every time some tokens come into existence.
  • Log a burn event every time some tokens are destroyed.
  • Log a transfer event every time tokens are assigned to a new account (deducted from the sender’s balance and added to the receiver’s balance).

If account A does not exist on-chain, the CIS-2 specification does not require checking it, so you can send tokens to account A (non-existing) and then they are stuck there forever since nobody can move them away.


Be aware of transferring tokens to a non-existing account address. This specification by itself does not include a mechanism to recover these tokens. Checking the existence of an account address would ideally be done off-chain before the message is even sent to the token smart contract.

Given your example and depending on what the intention of the smart contract developer was:

  • Either mint the tokens to the 2wKB account (mint event) + transfer the tokens from 2wKB to 94152 (transfer event) in one transaction.
  • Or mint the tokens directly to 94152 (no transfer event).

Thank you. My problem is the reverse of what you are describing, however. Sending tokens to a non-existent account leads to the tokens being lost.

However, in the case above, the contract is sending tokens from a non-existing account.

I think I’m looking for a policy decision on how to handle this.

My token accounting module has processed these logged events, which resulted in account A having a negative balance. That’s easy to correct.

However, account B has received tokens that do not exist. My token accounting module has processed this as a regular transfer and has credited account B for these tokens.

My current token holders:

CCDScan token holders :

Hence both explorers have processed the logged events in the same way.

My question: what should be the correct procedure when tokens are transferred that do not exist?

From the spec:

A transfer MUST fail if:

  • The token balance of the from address is insufficient to do the transfer.

That is the case here is the conclusion then that the contract is not CIS-2 compliant?

I don’t think using a negative number would be a good solution, because it would disagree with the result of querying the contract for the balance. I would probably keep the account balance at 0 instead.

It might just be a bug in the smart contract, but since tokens appear out of nowhere, I would suggest to flag the contract and display some warning about the behavior.

The block explorer does not check the “actual” balance in the state of the smart contract, meaning we will not know if they divert or are equal (we don’t know if the bug is only in the event or in the “event+stateChanges”). Flagging the smart contract as “notAdheringToTheStandard” and displaying an e.g. “warning/infoNotice” is a good suggestion to make smart contract developers aware that something odd is going on here. Other than that “warning/infoNotice”, the block explorer should do its job and process every event based on the same rules and display “negativeBalances” if that is what the events are telling. This will make it easier for debugging in my opinion rather than having special rules that cause events to be processed/not-processed/changed.