Skip to content

ERC981: Partial Ownership Standard #981

Closed
@jdkanani

Description

@jdkanani

Preamble

EIP: <to be assigned>
Title: Partial Ownership Standard
Author: Brij Bhasin <[email protected]>, Jaynti Kanani <[email protected]>
Type: Standard
Category: ERC
Status: Draft
Created: 2018-04-07

Simple Summary

An interface for tokens that are unique, fungible and allow partial ownership and trade of digital and real assets.

Motivation

The current ERC Tokens while useful leave out key aspects of information needed to enable tokenized trade of real-world assets on distributed ledger technology. ERC20 fungible interface is great in its simplicity for issuing tokens that are fungible, but due to its open design and extensibility, can lead to manipulation by bad actors. ERC721 non-fungible interface is great in allowing uniqueness of token to represent an asset, and track its ownership, it restricts fungibility.

Rationale

This proposal serves to describe a new Ethereum Interface for issuing tokens for asset owners who come to a marketplace with a finite quantity of their asset to trade, representing 100% ownership of that particular issue. They then choose to divide the asset into divisible units that increase fungibility of that asset.

The initial price and type of the value of the entire asset or it divisible unit is determined by the owner. Once the owner puts it up for trade and proposes the value and mechanism for exchange. Once an agreement is reached on the type of value, the seller and buyer agree on the price of the exchange on the original asset with the value being offered by the buyer.

In theory, such a system would allow the owner of a fungible ERC 20 to exchange specific units with non-fungible ERC 721 tokens as long as the seller and buyer agree on a value assignment mechanism for each asset.

Part-Owner Interface

Part-Owner is a free, open standard that describes how to build fungible yet uniquely representative tokens of the underlying asset on the Ethereum blockchain. While most tokens are fungible (every token is the same as every other token), and some tokens are non-fungible (every token is unique), Part-Owner Tokens (POTs) uniquely represent a divisible unit ownership of an asset, which is tradeable for divisible unit ownership of another asset as long as there is agreement on the value mechanism.

For eg. A Sports League can issue POTs for each Team in the League. Fans of that team can put bids to buy POTs and collectively own their team.

Specification

This ERC defines a minimum part-owner interface that a smart contract must implement to allow tokens to be managed, owned, and traded. It does not mandate a standard for token metadata or restrict adding supplemental functions.

Design concepts to incorporate –

  • Mother asset ownership to be capped to 100%
  • Mother Asset to be divisible up to 18 decimal points of Child Assets
  • Collective ownership of Child Assets defines ownership of Mother Asset
  • Mother Asset have Types [Physical, Digital] (Physical – Land) (Digital – Sports Leagues, Private Partnerships, Venture Funds)
  • Child Assets have Units (Weight, Area, Number, Decimals)
  • Mother Asset has an Issuer
  • Child Assets have Owners
  • Mother Asset has Issue Value
  • Child Assets have Price

Token

Methods

name

Returns the name of the token - e.g. "CryptoPremierLeague", "VirtualSmartFund".

OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present.

function name() public view returns (string _name)

symbol

Returns the symbol of the token. E.g. "CPL", "VSF".

OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present.

function symbol() public view returns (string _symbol)

decimals

Returns the number of decimals the token uses - e.g. 8, means to divide the token amount by 100000000 to get its user representation.

OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present.

function decimals() public view returns (uint8 _decimals)

balanceOf

Returns the number of tokens (percentage of underlying mother asset) assigned to _owner.

function balanceOf(address _owner) public view returns (uint256 _balance)

divide

Divides token (underlying asset) into multiple tokens with specified amount, type and metadata - if and only if identity of msg.sender == ownerOf(_tokenId). A successful divide MUST fire the Transfer event for each new POT (defined below).

Owners are array of owner addresses. sigs are array of signatures which required to divide the token (underlying asset). Signature type can be approval or acknowledgement.

This method MUST divide token into multiple child tokens or throw, no other outcomes can be possible. Reasons for failure include (but are not limited to):

  • msg.sender is not the owner of _tokenId
  • invalid or insufficient signatures
function divide(uint256 _tokenId, address[] _owners, uint256[] amounts, string[] types, string[] metadata, bytes[] sigs) public payable

merge

Merges tokens (underlying assets) into single token with metadata - and msg.sender the owner of new token. A successful merge MUST fire the Transfer event for new POT (defined below).

This method MUST merge tokens into single token or throw, no other outcomes can be possible. Reasons for failure include (but are not limited to):

  • invalid token id in _tokenIds
  • invalid or insufficient signatures
function merge(uint256[] _tokenIds, uint256 amount, string type, string metadata, bytes[] sigs) public payable

transfer

Assigns the ownership of the POT with ID _tokenId to _to if and only if msg.sender == ownerOf(_tokenId). A successful transfer MUST fire the Transfer event (defined below).

This method MUST transfer ownership to _to or throw, no other outcomes can be possible. Reasons for failure include (but are not limited to):

  • msg.sender is not the owner of _tokenId
  • invalid or insufficient signatures

_tokenId does not represent a POT currently tracked by this contract
_to is 0 (Conforming contracts MAY have other methods to destroy or burn POTs, which are conceptually "transfers to 0" and will emit Transfer events reflecting this. However, transfer(0, tokenId, sigs) MUST be treated as an error.)

A conforming contract MUST allow the current owner to "transfer" a token to themselves, as a way of affirming ownership in the event stream. (i.e. it is valid for _to == ownerOf(_tokenID).) This "no-op transfer" MUST be considered a successful transfer, and therefore MUST fire a Transfer event (with the same address for _from and _to).

Triggers Event: Transfer

function transfer(address _to, uint256 _tokenId, bytes[] sigs) public payable

Basic Ownership

owners

Returns addresses of all owners currently holding POTs.

function owners() public view returns (address[] _owners)

ownerOf

Returns the address currently marked as the owner of _tokenId. This method MUST throw if _tokenId does not represent a POT currently tracked by this contract. This method MUST NOT return 0 (POTs assigned to the zero identity are considered destroyed, and queries about them should throw).

function ownerOf(uint256 _tokenId) public view returns (address owner)

getToken

Returns token data of _tokenId, if _tokenId represents POT currently tracked by this contract.

function getToken(uint256 _tokenId) public view returns (address _owner, uint256 _amount, string _type, string _metadata)

totalTokens

Returns number of tokens owned by _owner

function totalTokens(address _owner) public view returns (uint256 _totalTokens)

tokenOfOwnerByIndex

OPTIONAL - It is recommended that this method is implemented for enhanced usability, but interfaces and other contracts MUST NOT depend on the existence of this method.

Returns the nth POT assigned to the _owner, with n specified by the _index argument. This method MUST throw if _index >= totalTokens(_owner).

function tokenOfOwnerByIndex(address _owner, uint256 _index) public view returns (uint256 _tokenId)

Recommended usage is as follows:

uint256 numberOfTokens = partOwnerTokenContract.totalTokens(owner);

uint256[] memory ownerTokens = new uint256[](numberOfTokens);

for (uint256 i = 0; i < numberOfTokens; i++) {
    ownerTokens[i] = partOwnerTokenContract.tokenOfOwnerByIndex(owner, i);
}

Identity

getIdentity

Returns identity hash if _owner is valid owner of any child assets.

function getIdentity(address _owner) public view returns (bytes32 _key);

POT metadata

tokenMetadata

OPTIONAL - It is recommended that this method is implemented for enhanced usability, but interfaces and other contracts MUST NOT depend on the existence of this method.

Returns a multiaddress string referencing an external resource bundle that contains (optionally localized) metadata about the POT associated with _tokenId. The string MUST be an IPFS or HTTP(S) base path (without a trailing slash) to which specific subpaths are obtained through concatenation. (IPFS is the preferred format due to better scalability, persistence, and immutability.) See ERC721

function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl)

Events

Transfer

This event MUST trigger when POT ownership is transferred via any mechanism.

Additionally, the creation of new asset MUST trigger a Transfer event for each newly created assets, with a _from to 0x0 and a _to key matching identity the owner of the new asset (possibly the smart contract itself). The deletion (or burn) of any asset MUST trigger a Transfer event with a _to as 0x0 and a _from address of the owner of the asset (now former owner).

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId)

Solidity interface

pragma solidity ^0.4.19;

contract ERC {
    // Events
    event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

    // Methods
    function divide(uint256 _tokenId, address[] _owners, uint256[] amounts, string[] types, string[] metadata, bytes[] sigs) public payable;
    function merge(uint256[] _tokenIds, uint256 amount, string type, string metadata, bytes[] sigs) public payable;
    function transfer(address _to, uint256 _tokenId, bytes[] sigs) public payable;
    
    function balanceOf(address _owner) public view returns (uint256 _balance);
    function owners() public view returns (address[] _owners);
    function ownerOf(uint256 _tokenId) public view returns (address owner);
    function getToken(uint256 _tokenId) public view returns (address _owner, uint256 _amount, string _type, string _metadata);
    function totalTokens(address _owner) public view returns (uint256 _totalTokens);
    function tokenOfOwnerByIndex(address _owner, uint256 _index) public view returns (uint256 _tokenId);
    
    // identity hash
    function getIdentity(address _owner) public view returns (bytes32 _key);
    
    // OPTIONAL
    function name() public view returns (string _name);
    function symbol() public view returns (string _symbol);
    function decimals() public view returns (uint8 _decimals);
    
    function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl);
}

Copyright

Copyright and related rights waived via CC0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions