Alert Source Discuss
⚠️ Draft Standards Track: SRC

SRC-998: Composable Non-Fungible Token

Extends a SRC-721 to own other SRC-721 and SRC-20 tokens.

Authors Matt Lockyer <mattdlockyer@gmail.com>, Nick Mudge <nick@perfectabstractions.com>, Jordan Schalm <jordan.schalm@gmail.com>, sebastian echeverry <sebastian.echeverry@robotouniverse.com>, Zainan Victor Zhou (@xinbenlv)
Created 2018-07-07
Discussion Link https://sila-magicians.org/t/src-998-composable-non-fungible-tokens-cnfts/387
Requires SIP-20, SIP-165, SIP-721

Abstract

An extension of the SRC-721 standard to enable SRC-721 tokens to own other SRC-721 tokens and SRC-20 tokens.

An extension of the SRC-20 and SRC-223 https://github.com/sila-chain/SIPs/issues/223 standards to enable SRC-20 and SRC-223 tokens to be owned by SRC-721 tokens.

This specification covers four different kinds of composable tokens:

  1. SRC998SRC721 top-down composable tokens that receive, hold and transfer SRC-721 tokens
  2. SRC998SRC20 top-down composable tokens that receive, hold and transfer SRC-20 tokens
  3. SRC998SRC721 bottom-up composable tokens that attach themselves to other SRC-721 tokens.
  4. SRC998SRC20 bottom-up composable tokens that attach themselves to SRC-721 tokens.

which map to

  1. An SRC998SRC721 top-down composable is an SRC-721 token with additional functionality for owning other SRC-721 tokens.
  2. An SRC998SRC20 top-down composable is an SRC-721 token with additional functionality for owning SRC-20 tokens.
  3. An SRC998SRC721 bottom-up composable is an SRC-721 token with additional functionality for being owned by an SRC-721 token.
  4. An SRC998SRC20 bottom-up composable is an SRC-20 token with additional functionality for being owned by an SRC-721 token.

A top-down composable contract stores and keeps track of child tokens for each of its tokens.

A bottom-up composable contract stores and keeps track of a parent token for each its tokens.

With composable tokens it is possible to compose lists or trees of SRC-721 and SRC-20 tokens connected by ownership. Any such structure will have a single owner address at the root of the structure that is the owner of the entire composition. The entire composition can be transferred with one transaction by changing the root owner.

Different composables, top-down and bottom-up, have their advantages and disadvantages which are explained in the Rational section. It is possible for a token to be one or more kinds of composable token.

A non-fungible token is compliant and Composable of this SIP if it implements one or more of the following interfaces:

  • SRC998SRC721TopDown
  • SRC998SRC20TopDown
  • SRC998SRC721BottomUp
  • SRC998SRC20BottomUp

Specification

SRC-721

SRC998SRC721 top-down, SRC998SRC20 top-down, and SRC998SRC721 bottom-up composable contracts must implement the SRC-721 interface.

SRC-20

SRC998SRC20 bottom-up composable contracts must implement the SRC-20 interface.

SRC-165

The SRC-165 standard must be applied to each SRC-998 interface that is used.

Authentication

Authenticating whether a user or contract can execute some action works the same for both SRC998SRC721 top-down and SRC998SRC721 bottom-up composables.

A rootOwner refers to the owner address at the top of a tree of composables and SRC-721 tokens.

Authentication within any composable is done by finding the rootOwner and comparing it to msg.sender, the return result of getApproved(tokenId) and the return result of isApprovedForAll(rootOwner, msg.sender). If a match is found then authentication passes, otherwise authentication fails and the contract throws.

Here is an example of authentication code:

address rootOwner = address(rootOwnerOf(_tokenId));
require(rootOwner == msg.sender || 
  isApprovedForAll(rootOwner,msg.sender) ||
  getApproved(tokenId) == msg.sender;

The approve(address _approved, uint256 _tokenId) and getApproved(uint256 _tokenId) SRC-721 functions are implemented specifically for the rootOwner. This enables a tree of composables to be transferred to a new rootOwner without worrying about which addresses have been approved in child composables, because any prior approves can only be used by the prior rootOwner.

Here are example implementations:

function approve(address _approved, uint256 _tokenId) external {
  address rootOwner = address(rootOwnerOf(_tokenId));	
  require(rootOwner == msg.sender || isApprovedForAll(rootOwner,msg.sender));

  rootOwnerAndTokenIdToApprovedAddress[rootOwner][_tokenId] = _approved;
  emit Approval(rootOwner, _approved, _tokenId);
}

function getApproved(uint256 _tokenId) public view returns (address)  {
  address rootOwner = address(rootOwnerOf(_tokenId));
  return rootOwnerAndTokenIdToApprovedAddress[rootOwner][_tokenId];
}

Traversal

The rootOwner of a composable is gotten by calling rootOwnerOf(uint256 _tokenId) or rootOwnerOfChild(address _childContract, uint256 _childTokenId). These functions are used by top-down and bottom-up composables to traverse up the tree of composables and SRC-721 tokens to find the rootOwner.

SRC998SRC721 top-down and bottom-up composables are interoperable with each other. It is possible for a top-down composable to own a bottom-up composable or for a top-down composable to own an SRC-721 token that owns a bottom-up token. In any configuration calling rootOwnerOf(uint256 _tokenID) on a composable will return the root owner address at the top of the ownership tree.

It is important to get the traversal logic of rootOwnerOf right. The logic for rootOwnerOf is the same whether or not a composable is bottom-up or top-down or both. Here is the logic:

Logic for rootOwnerOf(uint256 _tokenId)

If the token is a bottom-up composable and has a parent token then call rootOwnerOf for the parent token.
    If the call was successful then the returned address is the rootOwner.
    Otherwise call rootOwnerOfChild for the parent token.
        If the call was successful then the returned address is the rootOwner.
        Otherwise get the owner address of the token and that is the rootOwner.
Otherwise call rootOwnerOfChild for the token
    If the call was successful then the returned address is the rootOwner.
    Otherwise get the owner address of the token and that is the rootOwner.

Calling rootOwnerOfChild for a token means the following logic:

// Logic for calling rootOwnerOfChild for a tokenId
address tokenOwner = ownerOf(tokenId);
address childContract = address(this);
bytes32 rootOwner = SRC998SRC721(tokenOwner).rootOwnerOfChild(childContract, tokenId);

But understand that the real call to rootOwnerOfChild should be made with assembly so that the code can check if the call failed and so that the staticcall opcode is used to ensure that no state is modified.

Tokens/contracts that implement the above authentication and traversal functionality are “composable aware”.

Composable Transfer Function Parameter Format

Composable functions that make transfers follow the same parameter format: from:to:what.

For example the getChild(address _from, uint256 _tokenId, address _childContract, uint256 _childTokenId) composable function transfers an SRC-721 token from an address to a top-down composable. The _from parameter is the from, the _tokenId parameter is the to and the address _childContract, uint256 _childTokenId parameters are the what.

Another example is the safeTransferChild(uint256 _fromTokenId, address _to, address _childContract, uint256 _childTokenId) function. The _fromTokenId is the from, the _to is the to and the address _childContract, address _childTokenId parameters are the what.

transferFrom/safeTransferFrom Functions Do Not Transfer Tokens Owned By Tokens

In bottom-up and top-down composable contracts the transferFrom and safeTransferFrom functions must throw if they are called directly to transfer a token that is owned by another token.

The reason for this is that these functions do not explicitly specify which token owns a token to be transferred. See the rational section for more information about this.

transferFrom/safeTransferFrom functions must be used to transfer tokens that are owned by an address.

SRC-721 Top-Down Composable

SRC-721 top-down composables act as containers for SRC-721 tokens.

SRC-721 top-down composables are SRC-721 tokens that can receive, hold and transfer SRC-721 tokens.

There are two ways to transfer a SRC-721 token to a top-down composable:

  1. Use the function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) function. The _to argument is the top-down composable contract address. The bytes data argument holds the integer value of the top-down composable tokenId that the SRC-721 token is transferred to.
  2. Call approve in the SRC-721 token contract for the top-down composable contract. Then call getChild in the composable contract.

The first ways is for SRC-721 contracts that have a safeTransferFrom function. The second way is for contracts that do not have this function such as cryptokitties.

Here is an example of transferring SRC-721 token 3 from an address to top-down composable token 6:

uint256 tokenId = 6;
bytes memory tokenIdBytes = new bytes(32);
assembly { mstore(add(tokenIdBytes, 32), tokenId) }
SRC721(contractAddress).safeTransferFrom(userAddress, composableAddress, 3, tokenIdBytes);

Every SRC-721 top-down composable compliant contract must implement the SRC998SRC721TopDown interface.

The SRC998SRC721TopDownEnumerable and SRC998SRC20TopDownEnumerable interfaces are optional.

pragma solidity ^0.4.24;

/// @title `SRC998SRC721` Top-Down Composable Non-Fungible Token
/// @dev See https://github.com/sila-chain/SIPs/blob/master/SIPS/sip-998.md
///  Note: the SRC-165 identifier for this interface is 0xcde244d9
interface SRC998SRC721TopDown {

  /// @dev This emits when a token receives a child token.
  /// @param _from The prior owner of the token.
  /// @param _toTokenId The token that receives the child token.
  event ReceivedChild(
    address indexed _from, 
    uint256 indexed _toTokenId, 
    address indexed _childContract, 
    uint256 _childTokenId
  );
  
  /// @dev This emits when a child token is transferred from a token to an address.
  /// @param _fromTokenId The parent token that the child token is being transferred from.
  /// @param _to The new owner address of the child token.
  event TransferChild(
    uint256 indexed _fromTokenId, 
    address indexed _to, 
    address indexed _childContract, 
    uint256 _childTokenId
  );

  /// @notice Get the root owner of tokenId.
  /// @param _tokenId The token to query for a root owner address
  /// @return rootOwner The root owner at the top of tree of tokens and SRC-998 magic value.
  function rootOwnerOf(uint256 _tokenId) public view returns (bytes32 rootOwner);
  
  /// @notice Get the root owner of a child token.
  /// @param _childContract The contract address of the child token.
  /// @param _childTokenId The tokenId of the child.
  /// @return rootOwner The root owner at the top of tree of tokens and SRC-998 magic value.
  function rootOwnerOfChild(
    address _childContract, 
    uint256 _childTokenId
  ) 
    public 
    view
    returns (bytes32 rootOwner);
  
  /// @notice Get the parent tokenId of a child token.
  /// @param _childContract The contract address of the child token.
  /// @param _childTokenId The tokenId of the child.
  /// @return parentTokenOwner The parent address of the parent token and SRC-998 magic value
  /// @return parentTokenId The parent tokenId of _tokenId
  function ownerOfChild(
    address _childContract, 
    uint256 _childTokenId
  ) 
    external 
    view 
    returns (
      bytes32 parentTokenOwner, 
      uint256 parentTokenId
    );
  
  /// @notice A token receives a child token
  /// @param _operator The address that caused the transfer.
  /// @param _from The owner of the child token.
  /// @param _childTokenId The token that is being transferred to the parent.
  /// @param _data Up to the first 32 bytes contains an integer which is the receiving parent tokenId.  
  function onSRC721Received(
    address _operator, 
    address _from, 
    uint256 _childTokenId, 
    bytes _data
  ) 
    external 
    returns(bytes4);
    
  /// @notice Transfer child token from top-down composable to address.
  /// @param _fromTokenId The owning token to transfer from.
  /// @param _to The address that receives the child token
  /// @param _childContract The SRC-721 contract of the child token.
  /// @param _childTokenId The tokenId of the token that is being transferred.
  function transferChild(
    uint256 _fromTokenId,
    address _to, 
    address _childContract, 
    uint256 _childTokenId
  ) 
    external;
  
  /// @notice Transfer child token from top-down composable to address.
  /// @param _fromTokenId The owning token to transfer from.
  /// @param _to The address that receives the child token
  /// @param _childContract The SRC-721 contract of the child token.
  /// @param _childTokenId The tokenId of the token that is being transferred.
  function safeTransferChild(
    uint256 _fromTokenId,
    address _to, 
    address _childContract, 
    uint256 _childTokenId
  ) 
    external;
  
  /// @notice Transfer child token from top-down composable to address.
  /// @param _fromTokenId The owning token to transfer from.
  /// @param _to The address that receives the child token
  /// @param _childContract The SRC-721 contract of the child token.
  /// @param _childTokenId The tokenId of the token that is being transferred.
  /// @param _data Additional data with no specified format
  function safeTransferChild(
    uint256 _fromTokenId,
    address _to, 
    address _childContract, 
    uint256 _childTokenId, 
    bytes _data
  ) 
    external;
  
  /// @notice Transfer bottom-up composable child token from top-down composable to other SRC-721 token.
  /// @param _fromTokenId The owning token to transfer from.
  /// @param _toContract The SRC-721 contract of the receiving token
  /// @param _toTokenId The receiving token  
  /// @param _childContract The bottom-up composable contract of the child token.
  /// @param _childTokenId The token that is being transferred.
  /// @param _data Additional data with no specified format
  function transferChildToParent(
    uint256 _fromTokenId, 
    address _toContract, 
    uint256 _toTokenId, 
    address _childContract, 
    uint256 _childTokenId, 
    bytes _data
  ) 
    external;
  
  /// @notice Get a child token from an SRC-721 contract.
  /// @param _from The address that owns the child token.
  /// @param _tokenId The token that becomes the parent owner
  /// @param _childContract The SRC-721 contract of the child token
  /// @param _childTokenId The tokenId of the child token
  function getChild(
    address _from, 
    uint256 _tokenId, 
    address _childContract, 
    uint256 _childTokenId
  ) 
    external;
}

rootOwnerOf 1

/// @notice Get the root owner of tokenId.
/// @param _tokenId The token to query for a root owner address
/// @return rootOwner The root owner at the top of tree of tokens and SRC-998 magic value.
function rootOwnerOf(uint256 _tokenId) public view returns (bytes32 rootOwner);

This function traverses token owners until the root owner address of _tokenId is found.

The first 4 bytes of rootOwner contain the SRC-998 magic value 0xcd740db5. The last 20 bytes contain the root owner address.

The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a rootOwnerOf function. The magic value is used in such calls to ensure a valid return value is received.

If it is unknown whether a contract has the rootOwnerOf function then the first four bytes of the rootOwner return value must be compared to 0xcd740db5.

0xcd740db5 is equal to:

this.rootOwnerOf.selector ^ this.rootOwnerOfChild.selector ^ 
this.tokenOwnerOf.selector ^ this.ownerOfChild.selector;

Here is an example of a value returned by rootOwnerOf. 0xcd740db50000000000000000e5240103e1ff986a2c8ae6b6728ffe0d9a395c59

rootOwnerOfChild

/// @notice Get the root owner of a child token.
/// @param _childContract The contract address of the child token.
/// @param _childTokenId The tokenId of the child.
/// @return rootOwner The root owner at the top of tree of tokens and SRC-998 magic value.
function rootOwnerOfChild(
  address _childContract, 
  uint256 _childTokenId
) 
  public 
  view 
  returns (bytes32 rootOwner);

This function traverses token owners until the root owner address of the supplied child token is found.

The first 4 bytes of rootOwner contain the SRC-998 magic value 0xcd740db5. The last 20 bytes contain the root owner address.

The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a rootOwnerOf function. The magic value is used in such calls to ensure a valid return value is received.

If it is unknown whether a contract has the rootOwnerOfChild function then the first four bytes of the rootOwner return value must be compared to 0xcd740db5.

ownerOfChild

/// @notice Get the parent tokenId of a child token.
/// @param _childContract The contract address of the child token.
/// @param _childTokenId The tokenId of the child.
/// @return parentTokenOwner The parent address of the parent token and SRC-998 magic value
/// @return parentTokenId The parent tokenId of _tokenId
function ownerOfChild(
  address _childContract, 
  uint256 _childTokenId
) 
  external 
  view 
  returns (
    address parentTokenOwner, 
    uint256 parentTokenId
  );

This function is used to get the parent tokenId of a child token and get the owner address of the parent token.

The first 4 bytes of parentTokenOwner contain the SRC-998 magic value 0xcd740db5. The last 20 bytes contain the parent token owner address.

The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a ownerOfChild function. The magic value is used in such calls to ensure a valid return value is received.

If it is unknown whether a contract has the ownerOfChild function then the first four bytes of the parentTokenOwner return value must be compared to 0xcd740db5.

onSRC721Received

/// @notice A token receives a child token
/// @param _operator The address that caused the transfer.
/// @param _from The prior owner of the child token.
/// @param _childTokenId The token that is being transferred to the parent.
/// @param _data Up to the first 32 bytes contains an integer which is the receiving parent tokenId.  
function onSRC721Received(
  address _operator, 
  address _from, 
  uint256 _childTokenId, 
  bytes _data
) 
  external 
  returns(bytes4);

This is a function defined in the SRC-721 standard. This function is called in an SRC-721 contract when safeTransferFrom is called. The bytes _data argument contains an integer value from 1 to 32 bytes long that is the parent tokenId that an SRC-721 token is transferred to.

The onSRC721Received function is how a top-down composable contract is notified that an SRC-721 token has been transferred to it and what tokenId in the top-down composable is the parent tokenId.

The return value for onSRC721Received is the magic value 0x150b7a02 which is equal to bytes4(keccak256(abi.encodePacked("onSRC721Received(address,address,uint256,bytes)"))).

transferChild

/// @notice Transfer child token from top-down composable to address.
/// @param _fromTokenId The owning token to transfer from.
/// @param _to The address that receives the child token
/// @param _childContract The SRC-721 contract of the child token.
/// @param _childTokenId The tokenId of the token that is being transferred.
function transferChild(
  uint256 _fromTokenId,
  address _to, 
  address _childContract, 
  uint256 _childTokenId
) 
  external;

This function authenticates msg.sender and transfers a child token from a top-down composable to a different address.

This function makes this call within it:

SRC721(_childContract).transferFrom(this, _to, _childTokenId);

safeTransferChild 1

/// @notice Transfer child token from top-down composable to address.
/// @param _fromTokenId The owning token to transfer from.
/// @param _to The address that receives the child token
/// @param _childContract The SRC-721 contract of the child token.
/// @param _childTokenId The tokenId of the token that is being transferred.
function safeTransferChild(
  uint256 _fromTokenId,
  address _to, 
  address _childContract, 
  uint256 _childTokenId
) 
  external;

This function authenticates msg.sender and transfers a child token from a top-down composable to a different address.

This function makes this call within it:

SRC721(_childContract).safeTransferFrom(this, _to, _childTokenId);

safeTransferChild 2

/// @notice Transfer child token from top-down composable to address or other top-down composable.
/// @param _fromTokenId The owning token to transfer from.
/// @param _to The address that receives the child token
/// @param _childContract The SRC721 contract of the child token.
/// @param _childTokenId The tokenId of the token that is being transferred.
/// @param _data Additional data with no specified format, can be used to specify tokenId to transfer to
function safeTransferChild(
  uint256 _fromTokenId,
  address _to, 
  address _childContract, 
  uint256 _childTokenId, 
  bytes _data
) 
  external;

This function authenticates msg.sender and transfers a child token from a top-down composable to a different address or to a different top-down composable.

A child token is transferred to a different top-down composable if the _to address is a top-down composable contract and bytes _data is supplied an integer representing the parent tokenId.

This function makes this call within it:

SRC721(_childContract).safeTransferFrom(this, _to, _childTokenId, _data);

transferChildToParent

/// @notice Transfer bottom-up composable child token from top-down composable to other SRC-721 token.
/// @param _fromTokenId The owning token to transfer from.
/// @param _toContract The SRC-721 contract of the receiving token
/// @param _toToken The receiving token  
/// @param _childContract The bottom-up composable contract of the child token.
/// @param _childTokenId The token that is being transferred.
/// @param _data Additional data with no specified format
function transferChildToParent(
  uint256 _fromTokenId, 
  address _toContract, 
  uint256 _toTokenId, 
  address _childContract, 
  uint256 _childTokenId, 
  bytes _data
) 
  external

This function authenticates msg.sender and transfers a child bottom-up composable token from a top-down composable to a different SRC-721 token. This function can only be used when the child token is a bottom-up composable token. It is designed to transfer a bottom-up composable token from a top-down composable to an SRC-721 token (bottom-up style) in one transaction.

This function makes this call within it:

SRC998SRC721BottomUp(_childContract).transferToParent(
  address(this), 
  _toContract, 
  _toTokenId, 
  _childTokenId, 
  _data
);

getChild

/// @notice Get a child token from an SRC-721 contract.
/// @param _from The address that owns the child token.
/// @param _tokenId The token that becomes the parent owner
/// @param _childContract The SRC-721 contract of the child token
/// @param _childTokenId The tokenId of the child token
function getChild(
  address _from, 
  uint256 _tokenId, 
  address _childContract, 
  uint256 _childTokenId
) 
  external;

This function is used to transfer an SRC-721 token when its contract does not have a safeTransferChild(uint256 _fromTokenId, address _to, address _childContract, uint256 _childTokenId, bytes _data) function.

A transfer with this function is done in two steps:

  1. The owner of the SRC-721 token calls approve or setApprovalForAll in the SRC-721 contract for the top-down composable contract.
  2. The owner of the SRC-721 token calls getChild in the top-down composable contract for the SRC-721 token.

The getChild function must authenticate that msg.sender is the owner of the SRC-721 token in the SRC-721 contract or is approved or an operator of the SRC-721 token in the SRC-721 contract.

SRC-721 Top-Down Composable Enumeration

Optional interface for top-down composable enumeration:

///  @dev The SRC-165 identifier for this interface is 0xa344afe4
interface SRC998SRC721TopDownEnumerable {

  /// @notice Get the total number of child contracts with tokens that are owned by tokenId.
  /// @param _tokenId The parent token of child tokens in child contracts
  /// @return uint256 The total number of child contracts with tokens owned by tokenId.
  function totalChildContracts(uint256 _tokenId) external view returns(uint256);
  
  /// @notice Get child contract by tokenId and index
  /// @param _tokenId The parent token of child tokens in child contract
  /// @param _index The index position of the child contract
  /// @return childContract The contract found at the tokenId and index.
  function childContractByIndex(
    uint256 _tokenId, 
    uint256 _index
  ) 
    external 
    view 
    returns (address childContract);
  
  /// @notice Get the total number of child tokens owned by tokenId that exist in a child contract.
  /// @param _tokenId The parent token of child tokens
  /// @param _childContract The child contract containing the child tokens
  /// @return uint256 The total number of child tokens found in child contract that are owned by tokenId.
  function totalChildTokens(
    uint256 _tokenId, 
    address _childContract
  ) 
    external 
    view 
    returns(uint256);
  
  /// @notice Get child token owned by tokenId, in child contract, at index position
  /// @param _tokenId The parent token of the child token
  /// @param _childContract The child contract of the child token
  /// @param _index The index position of the child token.
  /// @return childTokenId The child tokenId for the parent token, child token and index
  function childTokenByIndex(
    uint256 _tokenId, 
    address _childContract, 
    uint256 _index
  )
    external 
    view 
    returns (uint256 childTokenId);
}

SRC-20 Top-Down Composable

SRC-20 top-down composables act as containers for SRC-20 tokens.

SRC-20 top-down composables are SRC-721 tokens that can receive, hold and transfer SRC-20 tokens.

There are two ways to transfer SRC-20 tokens to an SRC-20 Top-Down Composable:

  1. Use the transfer(address _to, uint256 _value, bytes _data); function from the SRC-223 contract. The _to argument is the SRC-20 top-down composable contract address. The _value argument is how many SRC-20 tokens to transfer. The bytes argument holds the integer value of the top-down composable tokenId that receives the SRC-20 tokens.
  2. Call approve in the SRC-20 contract for the SRC-20 top-down composable contract. Then call getSRC20(address _from, uint256 _tokenId, address _src20Contract, uint256 _value) from the SRC-20 top-down composable contract.

The first way is for SRC-20 contracts that support the SRC-223 standard. The second way is for contracts that do not.

SRC-20 top-down composables implement the following interface:

/// @title `SRC998SRC20` Top-Down Composable Non-Fungible Token
/// @dev See https://github.com/sila-chain/SIPs/blob/master/SIPS/sip-998.md
///  Note: the SRC-165 identifier for this interface is 0x7294ffed
interface SRC998SRC20TopDown {

  /// @dev This emits when a token receives SRC-20 tokens.
  /// @param _from The prior owner of the token.
  /// @param _toTokenId The token that receives the SRC-20 tokens.
  /// @param _src20Contract The SRC-20 contract.
  /// @param _value The number of SRC-20 tokens received.
  event ReceivedSRC20(
    address indexed _from, 
    uint256 indexed _toTokenId, 
    address indexed _src20Contract, 
    uint256 _value
  );
  
  /// @dev This emits when a token transfers SRC-20 tokens.
  /// @param _tokenId The token that owned the SRC-20 tokens.
  /// @param _to The address that receives the SRC-20 tokens.
  /// @param _src20Contract The SRC-20 contract.
  /// @param _value The number of SRC-20 tokens transferred.
  event TransferSRC20(
    uint256 indexed _fromTokenId, 
    address indexed _to, 
    address indexed _src20Contract, 
    uint256 _value
  );

  /// @notice A token receives SRC-20 tokens
  /// @param _from The prior owner of the SRC-20 tokens
  /// @param _value The number of SRC-20 tokens received
  /// @param _data Up to the first 32 bytes contains an integer which is the receiving tokenId.  
  function tokenFallback(address _from, uint256 _value, bytes _data) external;
  
  /// @notice Look up the balance of SRC-20 tokens for a specific token and SRC-20 contract
  /// @param _tokenId The token that owns the SRC-20 tokens
  /// @param _src20Contract The SRC-20 contract
  /// @return The number of SRC-20 tokens owned by a token from an SRC-20 contract
  function balanceOfSRC20(
    uint256 _tokenId, 
    address _src20Contract
  ) 
    external 
    view 
    returns(uint256);
  
  /// @notice Transfer SRC-20 tokens to address
  /// @param _tokenId The token to transfer from
  /// @param _value The address to send the SRC-20 tokens to
  /// @param _src20Contract The SRC-20 contract
  /// @param _value The number of SRC-20 tokens to transfer
  function transferSRC20(
    uint256 _tokenId, 
    address _to, 
    address _src20Contract, 
    uint256 _value
  ) 
    external;
  
  /// @notice Transfer SRC-20 tokens to address or SRC-20 top-down composable
  /// @param _tokenId The token to transfer from
  /// @param _value The address to send the SRC-20 tokens to
  /// @param _src223Contract The `SRC-223` token contract
  /// @param _value The number of SRC-20 tokens to transfer
  /// @param _data Additional data with no specified format, can be used to specify tokenId to transfer to
  function transferSRC223(
    uint256 _tokenId, 
    address _to, 
    address _src223Contract, 
    uint256 _value, 
    bytes _data
  )
    external;
  
  /// @notice Get SRC-20 tokens from SRC-20 contract.
  /// @param _from The current owner address of the SRC-20 tokens that are being transferred.
  /// @param _tokenId The token to transfer the SRC-20 tokens to.
  /// @param _src20Contract The SRC-20 token contract
  /// @param _value The number of SRC-20 tokens to transfer  
  function getSRC20(
    address _from, 
    uint256 _tokenId, 
    address _src20Contract, 
    uint256 _value
  ) 
    external;
}

tokenFallback

/// @notice A token receives SRC-20 tokens
/// @param _from The prior owner of the SRC-20 tokens
/// @param _value The number of SRC-20 tokens received
/// @param _data Up to the first 32 bytes contains an integer which is the receiving tokenId.  
function tokenFallback(address _from, uint256 _value, bytes _data) external;  

This function comes from the SRC-223 which is an extension of the SRC-20 standard. This function is called on the receiving contract from the sending contract when SRC-20 tokens are transferred. This function is how the SRC-20 top-down composable contract gets notified that one of its tokens received SRC-20 tokens. Which token received SRC-20 tokens is specified in the _data parameter.

balanceOfSRC20

/// @notice Look up the balance of SRC-20 tokens for a specific token and SRC-20 contract
/// @param _tokenId The token that owns the SRC-20 tokens
/// @param _src20Contract The SRC-20 contract
/// @return The number of SRC-20 tokens owned by a token from an SRC-20 contract
function balanceOfSRC20(
  uint256 _tokenId, 
  address _src20Contract
) 
  external 
  view 
  returns(uint256);

Gets the balance of SRC-20 tokens owned by a token from a specific SRC-20 contract.

transferSRC20

/// @notice Transfer SRC-20 tokens to address
/// @param _tokenId The token to transfer from
/// @param _value The address to send the SRC-20 tokens to
/// @param _src20Contract The SRC-20 contract
/// @param _value The number of SRC-20 tokens to transfer
function transferSRC20(
  uint256 _tokenId, 
  address _to, 
  address _src20Contract, 
  uint256 _value
)
  external;

This is used to transfer SRC-20 tokens from a token to an address. This function calls SRC20(_src20Contract).transfer(_to, _value);

This function must authenticate msg.sender.

transferSRC223

  /// @notice Transfer SRC-20 tokens to address or SRC-20 top-down composable
  /// @param _tokenId The token to transfer from
  /// @param _value The address to send the SRC-20 tokens to
  /// @param _src223Contract The `SRC-223` token contract
  /// @param _value The number of SRC-20 tokens to transfer
  /// @param _data Additional data with no specified format, can be used to specify tokenId to transfer to
  function transferSRC223(
    uint256 _tokenId, 
    address _to, 
    address _src223Contract, 
    uint256 _value, 
    bytes _data
  )
    external;

This function is from the SRC-223. It is used to transfer SRC-20 tokens from a token to an address or to another token by putting an integer token value in the _data argument.

This function must authenticate msg.sender.

getSRC20

/// @notice Get SRC-20 tokens from SRC-20 contract.
/// @param _from The current owner address of the SRC-20 tokens that are being transferred.
/// @param _tokenId The token to transfer the SRC-20 tokens to.
/// @param _src20Contract The SRC-20 token contract
/// @param _value The number of SRC-20 tokens to transfer  
function getSRC20(
  address _from, 
  uint256 _tokenId, 
  address _src20Contract, 
  uint256 _value
) 
  external;

This function is used to transfer SRC-20 tokens to an SRC-20 top-down composable when an SRC-20 contract does not have a transferSRC223(uint256 _tokenId, address _to, address _src223Contract, uint256 _value, bytes _data) function.

Before this function can be used the SRC-20 top-down composable contract address must be approved in the SRC-20 contract to transfer the SRC-20 tokens.

This function must authenticate that msg.sender equals _from or has been approved in the SRC-20 contract.

SRC-20 Top-Down Composable Enumeration

Optional interface for top-down composable enumeration:

/// @dev The SRC-165 identifier for this interface is 0xc5fd96cd
interface SRC998SRC20TopDownEnumerable {
  
  /// @notice Get the number of SRC-20 contracts that token owns SRC-20 tokens from
  /// @param _tokenId The token that owns SRC-20 tokens.
  /// @return uint256 The number of SRC-20 contracts
  function totalSRC20Contracts(uint256 _tokenId) external view returns(uint256);
  
  /// @notice Get an SRC-20 contract that token owns SRC-20 tokens from by index
  /// @param _tokenId The token that owns SRC-20 tokens.
  /// @param _index The index position of the SRC-20 contract.
  /// @return address The SRC-20 contract
  function src20ContractByIndex(
    uint256 _tokenId, 
    uint256 _index
  ) 
    external 
    view 
    returns(address);
}

SRC-721 Bottom-Up Composable

SRC-721 bottom-up composables are SRC-721 tokens that attach themselves to other SRC-721 tokens.

SRC-721 bottom-up composable contracts store the owning address of a token and the parent tokenId if any.

/// @title `SRC998SRC721` Bottom-Up Composable Non-Fungible Token
/// @dev See https://github.com/sila-chain/SIPs/blob/master/SIPS/sip-998.md
///  Note: the SRC-165 identifier for this interface is 0xa1b23002
interface SRC998SRC721BottomUp {

  /// @dev This emits when a token is transferred to an SRC-721 token
  /// @param _toContract The contract the token is transferred to
  /// @param _toTokenId The token the token is transferred to
  /// @param _tokenId The token that is transferred  
  event TransferToParent(
    address indexed _toContract, 
    uint256 indexed _toTokenId, 
    uint256 _tokenId
  );
  
  /// @dev This emits when a token is transferred from an SRC-721 token
  /// @param _fromContract The contract the token is transferred from
  /// @param _fromTokenId The token the token is transferred from
  /// @param _tokenId The token that is transferred  
  event TransferFromParent(
    address indexed _fromContract, 
    uint256 indexed _fromTokenId, 
    uint256 _tokenId
  );
  
  /// @notice Get the root owner of tokenId.
  /// @param _tokenId The token to query for a root owner address
  /// @return rootOwner The root owner at the top of tree of tokens and SRC-998 magic value.
  function rootOwnerOf(uint256 _tokenId) external view returns (bytes32 rootOwner);

  /// @notice Get the owner address and parent token (if there is one) of a token
  /// @param _tokenId The tokenId to query.
  /// @return tokenOwner The owner address of the token
  /// @return parentTokenId The parent owner of the token and SRC-998 magic value
  /// @return isParent True if parentTokenId is a valid parent tokenId and false if there is no parent tokenId
  function tokenOwnerOf(
    uint256 _tokenId
  )
    external 
    view
    returns (
      bytes32 tokenOwner, 
      uint256 parentTokenId, 
      bool isParent
    );
 
  /// @notice Transfer token from owner address to a token
  /// @param _from The owner address
  /// @param _toContract The SRC-721 contract of the receiving token
  /// @param _toToken The receiving token
  /// @param _data Additional data with no specified format
  function transferToParent(
    address _from, 
    address _toContract, 
    uint256 _toTokenId, 
    uint256 _tokenId, 
    bytes _data
  )
    external;
   
  /// @notice Transfer token from a token to an address
  /// @param _fromContract The address of the owning contract
  /// @param _fromTokenId The owning token
  /// @param _to The address the token is transferred to.
  /// @param _tokenId The token that is transferred
  /// @param _data Additional data with no specified format
  function transferFromParent(
    address _fromContract, 
    uint256 _fromTokenId, 
    address _to, 
    uint256 _tokenId, 
    bytes _data
  )
    external;
  
  /// @notice Transfer a token from a token to another token
  /// @param _fromContract The address of the owning contract
  /// @param _fromTokenId The owning token
  /// @param _toContract The SRC-721 contract of the receiving token
  /// @param _toToken The receiving token
  /// @param _tokenId The token that is transferred
  /// @param _data Additional data with no specified format
  function transferAsChild(
    address _fromContract, 
    uint256 _fromTokenId, 
    address _toContract, 
    uint256 _toTokenId, 
    uint256 _tokenId, 
    bytes _data
  )
    external;
}

rootOwnerOf

/// @notice Get the root owner of tokenId.
/// @param _tokenId The token to query for a root owner address
/// @return rootOwner The root owner at the top of tree of tokens and SRC-998 magic value.
function rootOwnerOf(uint256 _tokenId) public view returns (bytes32 rootOwner);

This function traverses token owners until the root owner address of _tokenId is found.

The first 4 bytes of rootOwner contain the SRC-998 magic value 0xcd740db5. The last 20 bytes contain the root owner address.

The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a rootOwnerOf function. The magic value is used in such calls to ensure a valid return value is received.

If it is unknown whether a contract has the rootOwnerOf function then the first four bytes of the rootOwner return value must be compared to 0xcd740db5.

0xcd740db5 is equal to:

this.rootOwnerOf.selector ^ this.rootOwnerOfChild.selector ^ 
this.tokenOwnerOf.selector ^ this.ownerOfChild.selector;

Here is an example of a value returned by rootOwnerOf. 0xcd740db50000000000000000e5240103e1ff986a2c8ae6b6728ffe0d9a395c59

tokenOwnerOf

/// @notice Get the owner address and parent token (if there is one) of a token
/// @param _tokenId The tokenId to query.
/// @return tokenOwner The owner address of the token and SRC-998 magic value.
/// @return parentTokenId The parent owner of the token
/// @return isParent True if parentTokenId is a valid parent tokenId and false if there is no parent tokenId
function tokenOwnerOf(
  uint256 _tokenId
)
  external 
  view 
  returns (
    bytes32 tokenOwner, 
    uint256 parentTokenId, 
    bool isParent
  );

This function is used to get the owning address and parent tokenId of a token if there is one stored in the contract.

If isParent is true then tokenOwner is the owning SRC-721 contract address and parentTokenId is a valid parent tokenId. If isParent is false then tokenOwner is a user address and parentTokenId does not contain a valid parent tokenId and must be ignored.

The first 4 bytes of tokenOwner contain the SRC-998 magic value 0xcd740db5. The last 20 bytes contain the token owner address.

The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a tokenOwnerOf function. The magic value is used in such calls to ensure a valid return value is received.

If it is unknown whether a contract has the rootOwnerOf function then the first four bytes of the tokenOwner return value must be compared to 0xcd740db5.

transferToParent

/// @notice Transfer token from owner address to a token
/// @param _from The owner address
/// @param _toContract The SRC-721 contract of the receiving token
/// @param _toToken The receiving token
/// @param _data Additional data with no specified format
function transferToParent(
  address _from, 
  address _toContract, 
  uint256 _toTokenId, 
  uint256 _tokenId, 
  bytes _data
)
  external;

This function is used to transfer a token from an address to a token. msg.sender must be authenticated.

This function must check that _toToken exists in _toContract and throw if not.

transferFromParent

/// @notice Transfer token from a token to an address
/// @param _fromContract The address of the owning contract
/// @param _fromTokenId The owning token
/// @param _to The address the token is transferred to.
/// @param _tokenId The token that is transferred
/// @param _data Additional data with no specified format
function transferFromParent(
  address _fromContract, 
  uint256 _fromTokenId, 
  address _to, 
  uint256 _tokenId, 
  bytes _data
)
  external;

This function is used to transfer a token from a token to an address. msg.sender must be authenticated.

This function must check that _fromContract and _fromTokenId own _tokenId and throw not.

transferAsChild

/// @notice Transfer a token from a token to another token
/// @param _fromContract The address of the owning contract
/// @param _fromTokenId The owning token
/// @param _toContract The SRC-721 contract of the receiving token
/// @param _toToken The receiving token
/// @param _tokenId The token that is transferred
/// @param _data Additional data with no specified format
function transferAsChild(
  address _fromContract, 
  uint256 _fromTokenId, 
  address _toContract, 
  uint256 _toTokenId, 
  uint256 _tokenId, 
  bytes _data
)
  external;

This function is used to transfer a token from a token to another token. msg.sender must be authenticated.

This function must check that _toToken exists in _toContract and throw if not.

This function must check that _fromContract and _fromTokenId own _tokenId and throw if not.

SRC-721 Bottom-Up Composable Enumeration

Optional interface for bottom-up composable enumeration:

/// @dev The SRC-165 identifier for this interface is 0x8318b539
interface SRC998SRC721BottomUpEnumerable {
  
  /// @notice Get the number of SRC-721 tokens owned by parent token.
  /// @param _parentContract The contract the parent SRC-721 token is from.
  /// @param _parentTokenId The parent tokenId that owns tokens
  //  @return uint256 The number of SRC-721 tokens owned by parent token.
  function totalChildTokens(
    address _parentContract, 
    uint256 _parentTokenId
  ) 
    external 
    view 
    returns (uint256);
  
  /// @notice Get a child token by index
  /// @param _parentContract The contract the parent SRC-721 token is from.
  /// @param _parentTokenId The parent tokenId that owns the token
  /// @param _index The index position of the child token
  /// @return uint256 The child tokenId owned by the parent token
  function childTokenByIndex(
    address _parentContract, 
    uint256 _parentTokenId, 
    uint256 _index
  ) 
    external 
    view 
    returns (uint256);
}

SRC-20 Bottom-Up Composable

SRC-20 bottom-up composables are SRC-20 tokens that attach themselves to SRC-721 tokens, or are owned by a user address like standard SRC-20 tokens.

When owned by an SRC-721 token, SRC-20 bottom-up composable contracts store the owning address of a token and the parent tokenId. SRC-20 bottom-up composables add several methods to the SRC-20 and SRC-223 interfaces allowing for querying the balance of parent tokens, and transferring tokens to, from, and between parent tokens.

This functionality can be implemented by adding one additional mapping to track balances of tokens, in addition to the standard mapping for tracking user address balances.

/// @dev This mapping tracks standard SRC20/`SRC-223` ownership, where an address owns
///  a particular amount of tokens.
mapping(address => uint) userBalances;

/// @dev This additional mapping tracks SRC-998 ownership, where an SRC-721 token owns
///  a particular amount of tokens. This tracks contractAddres => tokenId => balance
mapping(address => mapping(uint => uint)) nftBalances;

The complete interface is below.

/// @title `SRC998SRC20` Bottom-Up Composable Fungible Token
/// @dev See https://github.com/sila-chain/SIPs/blob/master/SIPS/sip-998.md
/// Note: The SRC-165 identifier for this interface is 0xffafa991
interface SRC998SRC20BottomUp {
  
  /// @dev This emits when a token is transferred to an SRC-721 token
  /// @param _toContract The contract the token is transferred to
  /// @param _toTokenId The token the token is transferred to
  /// @param _amount The amount of tokens transferred
  event TransferToParent(
    address indexed _toContract, 
    uint256 indexed _toTokenId, 
    uint256 _amount
  );

  /// @dev This emits when a token is transferred from an SRC-721 token
  /// @param _fromContract The contract the token is transferred from
  /// @param _fromTokenId The token the token is transferred from
  /// @param _amount The amount of tokens transferred
  event TransferFromParent(
    address indexed _fromContract, 
    uint256 indexed _fromTokenId, 
    uint256 _amount
  );

  /// @notice Get the balance of a non-fungible parent token
  /// @param _tokenContract The contract tracking the parent token
  /// @param _tokenId The ID of the parent token
  /// @return amount The balance of the token
  function balanceOfToken(
    address _tokenContract, 
    uint256 _tokenId
  )
    external
    view
    returns (uint256 amount);

  /// @notice Transfer tokens from owner address to a token
  /// @param _from The owner address
  /// @param _toContract The SRC-721 contract of the receiving token
  /// @param _toToken The receiving token
  /// @param _amount The amount of tokens to transfer
  function transferToParent(
    address _from, 
    address _toContract, 
    uint256 _toTokenId, 
    uint256 _amount
  )
    external;

  /// @notice Transfer token from a token to an address
  /// @param _fromContract The address of the owning contract
  /// @param _fromTokenId The owning token
  /// @param _to The address the token is transferred to
  /// @param _amount The amount of tokens to transfer
  function transferFromParent(
    address _fromContract, 
    uint256 _fromTokenId, 
    address _to, 
    uint256 _amount
  )
    external;

  /// @notice Transfer token from a token to an address, using `SRC-223` semantics
  /// @param _fromContract The address of the owning contract
  /// @param _fromTokenId The owning token
  /// @param _to The address the token is transferred to
  /// @param _amount The amount of tokens to transfer
  /// @param _data Additional data with no specified format, can be used to specify the sender tokenId
  function transferFromParentSRC223(
    address _fromContract, 
    uint256 _fromTokenId, 
    address _to, 
    uint256 _amount, 
    bytes _data
  )
    external;

  /// @notice Transfer a token from a token to another token
  /// @param _fromContract The address of the owning contract
  /// @param _fromTokenId The owning token
  /// @param _toContract The SRC-721 contract of the receiving token
  /// @param _toToken The receiving token
  /// @param _amount The amount tokens to transfer
  function transferAsChild(
    address _fromContract, 
    uint256 _fromTokenId, 
    address _toContract, 
    uint256 _toTokenId, 
    uint256 _amount
   )
    external;
}

balanceOfToken

/// @notice Get the balance of a non-fungible parent token
/// @param _tokenContract The contract tracking the parent token
/// @param _tokenId The ID of the parent token
/// @return amount The balance of the token
function balanceOfToken(
  address _tokenContract, 
  uint256 _tokenId
)
  external
  view
  returns (uint256 amount);

This function returns the balance of a non-fungible token. It mirrors the standard SRC-20 method balanceOf, but accepts the address of the parent token’s contract, and the parent token’s ID. This method behaves identically to balanceOf, but checks for ownership by SRC-721 tokens rather than user addresses.

transferToParent

/// @notice Transfer tokens from owner address to a token
/// @param _from The owner address
/// @param _toContract The SRC-721 contract of the receiving token
/// @param _toToken The receiving token
/// @param _amount The amount of tokens to transfer
function transferToParent(
  address _from, 
  address _toContract, 
  uint256 _toTokenId, 
  uint256 _amount
)
  external;

This function transfers an amount of tokens from a user address to an SRC-721 token. This function MUST ensure that the recipient contract implements SRC-721 using the SRC-165 supportsInterface function. This function SHOULD ensure that the recipient token actually exists, by calling ownerOf on the recipient token’s contract, and ensuring it neither throws nor returns the zero address. This function MUST emit the TransferToParent event upon a successful transfer (in addition to the standard SRC-20 Transfer event!). This function MUST throw if the _from account balance does not have enough tokens to spend.

transferFromParent

/// @notice Transfer token from a token to an address
/// @param _fromContract The address of the owning contract
/// @param _fromTokenId The owning token
/// @param _to The address the token is transferred to
/// @param _amount The amount of tokens to transfer
function transferFromParent(
  address _fromContract, 
  uint256 _fromTokenId, 
  address _to, 
  uint256 _amount
)
  external;

This function transfers an amount of tokens from an SRC-721 token to an address. This function MUST emit the TransferFromParent event upon a successful transfer (in addition to the standard SRC-20 Transfer event!). This function MUST throw if the balance of the sender SRC-721 token is less than the _amount specified. This function MUST verify that the msg.sender owns the sender SRC-721 token, and MUST throw otherwise.

transferFromParentSRC223

/// @notice Transfer token from a token to an address, using `SRC-223` semantics
/// @param _fromContract The address of the owning contract
/// @param _fromTokenId The owning token
/// @param _to The address the token is transferred to
/// @param _amount The amount of tokens to transfer
/// @param _data Additional data with no specified format, can be used to specify the sender tokenId
function transferFromParentSRC223(
  address _fromContract, 
  uint256 _fromTokenId, 
  address _to, 
  uint256 _amount, 
  bytes _data
)
  external;

This function transfers an amount of tokens from an SRC-721 token to an address. This function has identical requirements to transferFromParent, except that it additionally MUST invoke tokenFallback on the recipient address, if the address is a contract, as specified by SRC-223.

transferAsChild 1

/// @notice Transfer a token from a token to another token
/// @param _fromContract The address of the owning contract
/// @param _fromTokenId The owning token
/// @param _toContract The SRC-721 contract of the receiving token
/// @param _toToken The receiving token
/// @param _amount The amount tokens to transfer
function transferAsChild(
  address _fromContract, 
  uint256 _fromTokenId, 
  address _toContract, 
  uint256 _toTokenId, 
  uint256 _amount
)
  external;

This function transfers an amount of tokens from an SRC-721 token to another SRC-721 token. This function MUST emit BOTH the TransferFromParent and TransferToParent events (in addition to the standard SRC-20 Transfer event!). This function MUST throw if the balance of the sender SRC-721 token is less than the _amount specified. This function MUST verify that the msg.sender owns the sender SRC-721 token, and MUST throw otherwise. This function MUST ensure that the recipient contract implements SRC-721 using the SRC-165 supportsInterface function. This function SHOULD ensure that the recipient token actually exists, by calling ownerOf on the recipient token’s contract, and ensuring it neither throws nor returns the zero address.

Notes

For backwards-compatibility, implementations MUST emit the standard SRC-20 Transfer event when a transfer occurs, regardless of whether the sender and recipient are addresses or SRC-721 tokens. In the case that either sender or recipient are tokens, the corresponding parameter in the Transfer event SHOULD be the contract address of the token.

Implementations MUST implement all SRC-20 and SRC-223 functions in addition to the functions specified in this interface.

Rationale

Two different kinds of composable (top-down and bottom-up) exist to handle different use cases. A regular SRC-721 token cannot own a top-down composable, but it can own a bottom-up composable. A bottom-up composable cannot own a regular SRC-721 but a top-down composable can own a regular SRC-721 token. Having multiple kinds of composables enable different token ownership possibilities.

Which Kind of Composable To Use?

If you want to transfer regular SRC-721 tokens to non-fungible tokens, then use top-down composables.

If you want to transfer non-fungible tokens to regular SRC-721 tokens then use bottom-up composables.

Explicit Transfer Parameters

Every SRC-998 transfer function includes explicit parameters to specify the prior owner and the new owner of a token. Explicitly providing from and to is done intentionally to avoid situations where tokens are transferred in unintended ways.

Here is an example of what could occur if from was not explicitly provided in transfer functions:

An exchange contract is an approved operator in a specific composable contract for user A, user B and user C.

User A transfers token 1 to user B. At the same time the exchange contract transfers token 1 to user C (with the implicit intention to transfer from user A). User B gets token 1 for a minute before it gets incorrectly transferred to user C. The second transfer should have failed but it didn’t because no explicit from was provided to ensure that token 1 came from user A.

Backwards Compatibility

Composables are designed to work with SRC-721, SRC-223 and SRC-20 tokens.

Some older SRC-721 contracts do not have a safeTransferFrom function. The getChild function can still be used to transfer a token to an SRC-721 top-down composable.

If an SRC-20 contract does not have the SRC-223 function transfer(address _to, uint _value, bytes _data) then the getSRC20 function can still be used to transfer SRC-20 tokens to an SRC-20 top-down composable.

Reference Implementation

An implementation can be found here: https://github.com/mattlockyer/composables-998

Security Considerations

Needs discussion.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Matt Lockyer <mattdlockyer@gmail.com>, Nick Mudge <nick@perfectabstractions.com>, Jordan Schalm <jordan.schalm@gmail.com>, sebastian echeverry <sebastian.echeverry@robotouniverse.com>, Zainan Victor Zhou (@xinbenlv), "SRC-998: Composable Non-Fungible Token [DRAFT]," Sila Improvement Proposals, no. 998, July 2018. Available: https://sips.sila.org/SIPS/sip-998.