ERC-7752: Equity Token

Overview

This topic of discussion is an ERC token standard representing shares in a corporation, or even membership unit interests (MIUs) in an alternative asset fund. This new interface standardizes equity management for issuers, investors, transfer agents, and financial intermediaries. Creating an equity token standard requires a large industry effort. That’s why this token standard adheres to the same design principles as the Open Cap Format (OCF), an industry-approved data schema for cap tables. Now everyone can adopt the same OCF-compliant token standard for Ethereum and beyond!

Building off of ERC-3643 enables features such as:

  • Transfer agent controls (account and asset freeze, account recovery, etc.)
  • Compatibility with onchain identity management
  • Customized compliance modules

Issuance Identifiers

To conform to OCF, mint has been renamed to issue and burn has been renamed to cancel. See here for more details on cancellation of equity issuances.

ERC-7752 brings breaking changes to ERC-3643 by emphasizing security issuances (each of which may have its own vesting conditions or other contractual requirements) rather than fungible token balances. Thus, methods like freezePartialTokens have been removed in favor of freezeSecurity to target a specific issuance. An additional securityId param is required for any method that modifies an account’s equity balance. The batch interfaces have been removed by the recommendation of @wjmelements to optimize contract size since calls can be batched with Multicall.

In addition to mint and burn ERC-20 breaking changes, this standard introduces:

  • the shareClass and uri parameters to the issue method
  • a securityId parameter to cancel, transfer, and transferFrom methods
  • a shareClass param to the approve, increaseAllowance, and decreaseAllowance methods
  • corresponding changes to the event signatures

Assigning a unique securityId to all issuances of equity tokens is necessary for the ease of understanding unique conditions of a stock grant (such as vesting), cost basis, and ease of auditing. Find more details on how the OCF handles stock transfers.

Organization Documents & Share Class Metadata

Support for storing governing documents of the stock such as a corporate charter, operating agreement, or stock plan has been introduced. A metadata JSON URI can also optionally be attached per share class and should conform to the OCF StockClass schema.

For organizations that don’t issue stock such as partnerships or LLCs, we recommend using the equity token with one share class named “membership unit interests”. Such entities should ensure that their operating agreement reflects this ownership structure.

Onchain Verifications

The concept of an on-chain identity from ERC-3643 may still be used, but this standard doesn’t force you into choosing the way described in ERC-3643. One reason for this is because on-chain identities can alternatively be attested to using the Ethereum Attestation Service. It would be beneficial to standardize the way things are attested to on-chain, so the door is open to adopt EAS for this ERC (and for other ERCs) to provide a common interface for verifiable credentials. EAS is being pushed by Coinbase and due to their sponsorship, I think EAS has a good chance of being the main standard.

Interface

library Types {
  struct Security {
    bytes32 id;
    address holder;
    bytes32 class;
    bytes32 balanceSecurityId;
    bytes32[] resultingSecurityIds;
    uint256 amount;
    uint256 issuedAt; // Block timestamp of issuance tx
    string uri; // Additional metadata apart from share type metadata
    bool isFrozen;
  }
}

interface IToken {

  /// Events
  event Approval(address indexed _owner, address indexed _spender, uint256 _amount);
  event Transfer(address indexed from, address indexed to, bytes32 securityId, uint256 amount, bytes32[] newIds);
  event ShareClassAdded(bytes32 indexed _class, string indexed _uri);
  event ShareClassUpdated(bytes32 indexed _class, string indexed _uri);
  event ShareClassRemoved(bytes32 indexed _class);
  event UpdatedTokenInformation(string indexed _newName, string indexed _newSymbol, uint8 _newDecimals, string _newVersion, address indexed _newOnchainID);
  event IdentityRegistryAdded(address indexed _identityRegistry);
  event ComplianceAdded(address indexed _compliance);
  event RecoverySuccess(address indexed _lostWallet, address indexed _newWallet, address indexed _investorOnchainID);
  event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner);
  event SecurityFrozen(address indexed _userAddress, bytes32 _securityId);
  event SecurityUnfrozen(address indexed _userAddress, bytes32 _securityId);
  event Paused(address _userAddress);
  event Unpaused(address _userAddress);

  /// Functions
  function securities(bytes32 securityId) external view returns (Types.Security memory);
  function getShareClasses() external view returns (bytes32[] calldata);
  function addShareClass(bytes32 className, string memory metadataURI) external;
  function removeShareClass(bytes32 className) external;
  function getMetadataURIs() external view returns (string[] memory);
  function name() external view returns (string memory);
  function decimals() external view returns (uint8);
  function symbol() external view returns (string memory);
  function version() external view returns (string memory);
  function allowance(address owner, bytes32 shareClass, address spender) external view returns (uint256);
  function approve(address spender,  bytes32 shareClass, uint256 amount) external returns (bool);
  function increaseAllowance(address spender,  bytes32 shareClass, uint256 addedValue) external returns (bool);
  function decreaseAllowance(address spender,  bytes32 shareClass, uint256 subtractedValue) external returns (bool);
  function totalSupply() external view returns (uint256);
  function totalSupply(bytes32 shareClass) external view returns (uint256);
  function balanceOf(address account) external view returns (uint256);
  function balanceOf(address account, bytes32 shareClass) external view returns (uint256);
  function transferFrom(address from, address to, bytes32 securityId, uint256 amount) external returns (bool);
  function transfer(address to, bytes32 securityId, uint256 amount) external returns (bool);
  function setName(string calldata name) external;
  function setSymbol(string calldata symbol) external;
  function setOnchainID(address onchainID) external;
  function pause() external;
  function unpause() external;
  function setAddressFrozen(address userAddress, bool isFrozen) external;
  function freezeSecurity(address userAddress, bytes32 securityId) external;
  function unfreezeSecurity(address userAddress, bytes32 securityId) external;
  function setIdentityRegistry(address identityRegistry) external;
  function setCompliance(address compliance) external;
  function forcedTransfer(address from, address to, bytes32 securityId, uint256 amount) external returns (bool);
  function issue(address to, bytes32 shareClass, uint256 amount, string memory uri, bytes calldata vestingData) external;
  function cancel(address userAddress, bytes32 securityId, uint256 amount) external;
  function recoveryAddress(address lostWallet, address newWallet, address _investorOnchainID) external returns (bool);
  function onchainID() external view returns (address);
  function identityRegistry() external view returns (IIdentityRegistry);
  function compliance() external view returns (IModularCompliance);
  function paused() external view returns (bool);
  function isFrozen(address userAddress) external view returns (bool);
}

Comparing ERC-20 vs. ERC-7752

The Open Cap Format (OCF) method of handling stock transfers and the ERC-20 standard share some similarities, but they also differ in several key aspects.

Similarities

  1. Immutability and Traceability:
  • ERC-20: ERC-20 tokens operate on a blockchain, where every transaction is recorded in an immutable ledger. This ensures that every transfer of tokens is permanently recorded, providing a clear history of token ownership.
  • OCF: The event-driven architecture in OCF also ensures that every action (issuance, transfer, etc.) is recorded immutably. The OCF does not allow for altering past events.
  1. Event-Driven Nature:
  • ERC-20: Token transfers in the ERC-20 standard are event-driven. Every transfer emits a Transfer event, which can be tracked by applications and users to monitor token movements.
  • OCF: Similarly, OCF uses events like StockTransfer to record the movement of securities. Each event is a discrete record in the history of the security.
  1. Standardized Format:
  • ERC-20: The ERC-20 standard defines a consistent format for managing and transferring tokens.
  • OCF: The OCF also defines a standardized format for recording corporate actions, including transfers.

Differences

  1. Asset Nature:
  • ERC-20: ERC-20 is a standard for fungible tokens, where each token is identical and interchangeable with any other token. ERC-20 tokens typically represent assets like cryptocurrencies or utility tokens.
  • OCF: OCF deals with securities, which are often non-fungible or semi-fungible. For example, shares of stock might come with different rights, vesting conditions, restrictions, or legends, making them distinct from one another even if they belong to the same stock class.
  1. Transfer Mechanism:
  • ERC-20: In the ERC-20 standard, a transfer simply moves tokens from one address to another. The balance of the sender is decreased, and the balance of the recipient is increased in a single operation.
  • OCF: OCF requires a more complex process for transfers. It involves creating a new issuance event for the recipient and linking the old and new securities through a transfer event. This reflects the fact that securities are not merely balances to be adjusted, but distinct legal instruments that may have unique attributes.
  1. Handling Partial Transfers:
  • ERC-20: Partial transfers are straightforward in ERC-20, where a specified number of tokens can be transferred from one address to another, and the balance is adjusted accordingly.
  • OCF: In OCF, partial transfers require creating a new security issuance for the remaining balance after the transfer. The original security is effectively split into two, with one part transferred to the new owner and the other part remaining with the original owner.
  1. Complexity of Events:
  • ERC-20: The ERC-20 standard has relatively simple events (Transfer, Approval), as it is designed to manage fungible tokens that do not have complex attributes or varying legal implications.
  • OCF: OCF events are more complex, reflecting the multifaceted nature of securities. For example, stock transfers may involve additional considerations like compliance with legal restrictions, tax implications, or changes in the rights associated with the stock.
  1. Legal and Compliance Focus:
  • ERC-20: While ERC-20 tokens can represent assets with real-world value, they are often not tied to the same legal and regulatory frameworks as securities. ERC-20 tokens are primarily used for blockchain-based applications, and while they can be subject to regulation, they do not inherently carry the same legal obligations as securities.
  • OCF: The OCF is specifically designed to manage and track securities, which are heavily regulated. It accounts for legal requirements, such as stock legends, compliance with securities laws, and proper documentation of transfers, making it suitable for managing equity in real companies.

ERC Pull Request

2 Likes

I don’ think the batch interfaces are useful or necessary.

ok I see they are part of erc-3643. I don’t like them there either.

I think your specification can be simplified by explicitly requiring erc-3643 compliance. You can remove all of the erc-3643 spec thereby.

What do you see as the issue with the batch interfaces?

The original post didn’t mention that there are ERC-3643 breaking changes. I’ve updated the post with the details.

1 Like

What do you see as the issue with the batch interfaces?

Concatenation is the superior batch ABI. It even works for batching different methods. All of the top trading bots are doing it.

1 Like

i don’t agree on that, most users still use EOAs and as long as ERC-3074 is not integrated (should be part of Pectra upgrade) they cannot batch transactions natively, therefore it is good to have batch transactions defined on the ABIs, not everyone uses trading bots or smart accounts. Also, having an external contract implementing the batching logic and calling the token contract repeatedly is going to cost additional gas compared to the batch implemented on the token contract directly.

We disagree on the meaning of ABI. You are confusing this because the non-standard Solidity 4byte ABI is the most popular ABI, and because Solidity’s json output calls its list of methods “abi”. However, ABI is a more general term referring to the binary input format itself, not the list of methods.

You would like there to be separate methods for batching. I would like for all methods to be batchable.

Both ABIs work for EOAs.

Yes they can, if we adopt the concatenation batch ABI.

Irrelevant; this demonstrates you do not understand. My theory about why you do not understand heads this reply.

Thank you for the clarification. I appreciate the insight into using ABI concatenation as a method for batching transactions. It’s definitely an interesting approach and I can see how it could be powerful in certain contexts, e.g. for advanced users like trading bots.

That said, in my experience developing in Solidity over the past six years, and working closely with a team of developers, this method of batching through ABI concatenation is not something we’ve commonly encountered or used. Most of the development community, from what I’ve observed, tends to favor more explicit and user-friendly interfaces, especially when it comes to contract interactions. The standard practice involves defining specific batch methods in the ABI to accommodate the needs of the majority of users and developers, particularly those using EOAs or relying on standard wallets and interfaces.

While ABI concatenation might offer more flexibility in certain scenarios, it also introduces a level of complexity that might not be necessary or practical for most use cases. The typical user interacting with smart contracts is not using raw ABI concatenation but rather calling clearly defined methods through familiar interfaces.

I agree that in a more abstract sense, ABI refers to the binary input format and not just the list of methods in a Solidity-generated JSON. However, for most real-world applications, especially those targeting a broad user base, the practical advantages of explicit batch methods in the ABI outweigh the theoretical flexibility provided by concatenation.

It’s certainly a technique worth being aware of, but in terms of practicality and adoption within the community, the current standard approach of defining batch methods seems to best serve the majority of users and use cases.

The fact that concatenation isn’t currently supported by solidity is not a reason to oppose it.

This EIP concerns the future. Past tendencies are gravitationally bound by the current limitations of solidity.

The concatenated methods are also clearly defined.

Obviously not, because the batching deficiency is cited in the rationale of several EIPS like 3074.

@wjmelements @Joachim-Lebrun thank you for the discussion. I think this thread will be helpful for others in consideration of batch interfaces in other specs.

That being said, I am updating the spec to remove partial freeze/unfreeze in favor of freezing a specified securityId. This simplifies the implementation and cuts down on the contract size.

@wjmelements, I suppose the concatenation you mention would further reduce compiled contract size (please correct me if I’m wrong) because the batch methods would not need to be explicitly implemented.

Edit: Are you referring to something like Multicall3?

2 Likes