Compoundable NFT: The Whitepaper

Passengers
9 min readJan 8, 2023

By crc @ Passengers and 0xytocin Labs

The NFT space is fairly young, with the ERC-721 standard finalized back in early 2018, reaching peak hype in 2021. But that was just the beginning. NFT in all its forms is moving up from being a pure, speculative asset into a major real-world utility.

Today, we look to NFTs for accessing special perks through token gating, virtual representations of physical items, proof of ownership and tokenization of assets and much more. In the gaming industry, NFTs allow players to utilize in-game assets, trade them and enhance their overall experience.

The capabilities today

There’s already been a significant adoption of dynamic NFTs, where the metadata gets centrally updated to reflect off-chain changes made to a token, which brings a great deal of flexibility, but not very open, thus becoming application specific.

We wanted to expand on this further. The goal was to be able to modify in-game characters and individual assets by combining or separating them, improving their capabilities and being able to upgrade something that looks seemingly basic into a powerful character, but on-chain. This would enhance the user experience and utility of assets, and also provide ways for the holders to build their own, personalized characters they can trade as a whole.

Together with our friends at 0xytocin Labs, we had to find a way to combine two or more assets, while being able to detach them at a later stage. This would allow us to have a single, initial NFT that is composed of multiple assets which could be detached into separate NFTs, tradable on secondary markets — as a single token transaction.

First we looked at what the industry has already proposed as standards for accomplishing this, but found varying degrees of quality and complexity in implementations, often centered around registry contracts or token linking ideas that had stagnated and been outdated. Some proposed ideas were combining two tokens into a third one, which was also outside of what we needed.

While considering various options, it quickly became clear that linking between external assets would make it complicated as individual assets may be traded away from their original owner, which would need approval revoking and similar mechanisms. Also, when you buy NFTs from marketplaces, you should ideally become the owner of all the assets you see, and not get your asset stripped later on.

Further, centralized registry or repository contracts makes the solution less modularized, where we wanted to achieve a network of tokens attached freely between themselves, on the owner’s request — and not the registry/repository. However, these features may easily be implemented on top of the Compoundable contract.

Compoundable NFTs

What we needed was an easy to use, easy to understand way to attach NFTs to each other, and leave it completely up to the user, not the application, to control the decision making. We just couldn’t find the right solution within the Ethereum community, so through Passengers, we’re bringing this new and exciting innovation to the NFT industry, in what we call Compoundable NFTs (“cNFT” in short). cNFT is a set of extensions to ERC-721 and ERC-1155, keeping them backward compatible, but adding new functionality for applications that will support them. Here’s a sample from the specification (work in progress):

abstract contract Compoundable {

// Event for when a token is attached to a token Id.
event Attached(uint256 tokenId, address tokenAddress, address originalOwner, uint256 baseTokenId);

// Event for when a token is detached from a token Id.
event Detached(uint256 tokenId, address tokenAddress, address currentOwner, uint256 baseTokenId);

// Event for when tokens is batch attached to a token Id.
event AttachedBatch(uint256 tokenId[], address tokenAddress[], address originalOwner[], uint256 baseTokenId);

// Event for when a token is batch detached from a token Id.
event DetachedBatch(uint256 tokenId[], address tokenAddress[], address currentOwner, uint256 baseTokenId);

/**
* @notice Attach a token to a base token.
* @param tokenId The id of the attachment token.
* @param tokenAddress The address of the attachment token contract.
* @param baseTokenId The id of the base token to attach to.
* @dev Pseudo: attach tokenId from tokenAddress to baseTokenId
*/
function attach(uint256 tokenId, address tokenAddress, uint256 baseTokenId) external {}

/**
* @notice Batch attach multiple tokens to a base token.
* @param tokenId[] Array of token ids of the attaching tokens.
* @param tokenAddress[] Array of addresses of the attaching token contracts.
* @param baseTokenId The id of the base token to attach to.
* @dev Pseudo: attachBatch tokenId[] from tokenAddress[] to baseTokenId
*/
function attachBatch(uint256[] tokenId, address[] tokenAddress, uint256 baseTokenId) external {}

/**
* @notice Detach a token from a base token.
* @param tokenAddress The address of the attachment token contract.
* @param tokenId The id of the attachment token.
* @param baseTokenId The id of the base token.
* @dev Pseudo: detach tokenId of type tokenAddress from baseTokenId
*/
function detach(uint256 tokenId, address tokenAddress, uint256 baseTokenId) external {}

/**
* @notice Batch detach tokens from a base token.
* @param tokenId[] Array of token ids of the attaching tokens.
* @param tokenAddress[] Array of addresses of the attaching contracts.
* @param baseTokenId The token id of the underlying base token.
* @dev Pseudo: detachBatch all tokenId[] of type tokenAddress[] from baseTokenId
*/
function detachBatch(uint256[] tokenId, address tokenAddress[], uint256 baseTokenId) external {}

/**
* @notice Detach all tokens from a base token.
* @param tokenId The token id you want everything detached from.
* @dev Pseudo: detachAll attachments from tokenId
*/
function detachAll(uint256 tokenId) external view returns(address[] calldata, uint256[] calldata) {}

/**
* @notice Find the top level owner of all compounded tokens within this chain.
* @param tokenId The tokenId whose top owner shall be discovered.
* @return address The address of the owner of the top token.
* @return uint256 The tokenId of the top token.
*/
function topOwnerOf(uint256 tokenId) external view returns (address, uint256) {}

/**
* @notice Used to find whether the token has any token attached to it or not.
* @param tokenId The token id, whose attachment status is to be found.
* @return bool true if the token has any token attached to it, else false.
*/
function hasAttachments(uint256 tokenId) external view returns (bool) {}

/**
* @notice Get the attachment addresses and tokenIds attached to a baseTokenId.
* @param tokenId The token id, whose attachment status is to be found.
* @return address[] Array of attachment token addresses.
* @return uint256[] Array of attachment token ids.
*/
function getAllAttachments(uint256 tokenId) external view returns(address[] calldata, uint256[] calldata) {}

/**
* @notice Check if the token is attached to any other token.
* @param tokenId The id of the token to validate.
* @return address The token address to which this token is attached.
* @return uint256 The token id where this token is attached.
*/
function whereIsAttached(uint256 tokenId) external view returns (address, uint256) {}
}

When assets are attached to a Compoundable NFT, they get transferred into the token contract and assigned to the specific Token ID, and when assets are detached, they get transferred out of the token contract and into the owner’s wallet. This enables creation and trading of complex cNFT’s that consist of many, potentially rare or unique assets on the marketplace, while retaining their individual value. In essence, you may transfer the combination of hundreds of tokens, with a single token transaction.

Compound NFTs of various types together into an existing one.

This also enables daisy-chaining of assets, from one Compoundable to another, forming a series of compounded NFT’s, like adding a clip to a weapon and attaching the weapon to a character, who might later be attached to a spaceship.

This is a utility that can be used more broadly, so we’re working towards providing this as EIP (Ethereum Improvement Proposal) to make it become a dedicated ERC (Ethereum Request for Comment), which we hope will be out in a few weeks. That’s IP created by us, the Passengers community, for every other project to be able to adopt and take into use, because you invested in us and made this a reality.

Extending Compoundables with a slot system

To enhance the usability for Passengers itself, we’re adding a few more features in a new contract inheriting Compoundable that allows us to create equipment slots for the Passengers to store weapons, jetpacks, accessories and so on.

This overview shows how you may combine various types of Compoundable tokens to form a larger and more complex NFT.

These ad-hoc modifications to NFT’s will affect the dynamics of a collection, as new creations will surface once people start collecting assets they attach to their own character. As the contract extension allows for direct on-chain interaction to detach assets from a token, it will emit an on-chain event for applications to be able to update its metadata, wherever it may be stored. As applications will fetch the events as they occur, cNFT’s can easily be visually re-generated in real-time, so a token will always reflect the latest changes.

Maintaining metadata

While cNFT utilizes the existing ERC-721/1155 token standards for compatibility, we also wanted to add new metadata fields for marketplaces and applications to identify them. We refer to this as an extension to the ERC-721/ERC-1155 JSON schema standard (see: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md and https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md).

OpenSea and other marketplaces and services have added their own metadata on top of the original ERC-721 and ERC-1155 JSON schema to extend the usability of tokens and how they can be visually represented.

Here’s the original ERC-721 schema as per the specification:

{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents"
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents."
}
}
}

And here’s how OpenSea has extended this schema with additional attributes (“external_url” & “attributes”):

{
"name": "Dave Starbelly",
"description": "Friendly OpenSea Creature that enjoys long swims in the ocean.",
"image": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png",
"external_url": "https://openseacreatures.io/3",
"attributes": [ ... ]
}

These OpenSea extensions are not standardized in the original ERC-721 schema according to the EIP-721, but the intentions of the creators were to leave this to the application developers to create application specific formats (see: https://github.com/ethereum/EIPs/pull/1028#issuecomment-383533224). Due to OpenSea being the major marketplace leader, every NFT project and marketplace has adopted OpenSea’s custom schema extensions to enhance the token usability in the space overall.

With CNFTs, we’re suggesting a new schema extension that deals with compoundable assets:

"asset_uri":"ethereum_uri:contract_address:/token_id"

ethereum_uri is referred to via EIP-831 (see: https://eips.ethereum.org/EIPS/eip-831).

Here’s an example of a Passengers cNFT metadata for an ERC-721 token:

{
"name":"Passengers #1",
"description":"A space-themed, 4k hyper-detail project driven by a compelling story that teleports community members to the frontiers of the galaxy and beyond. From the darkest galaxies to the bottomless voids, Passengers go to all places and help all people. Passengers never quit and never back down.",
"image":"url..",
"attributes":
[{
"trait_type":"Character",
"value":"Vexalis"
},
{
"trait_type":"Faction",
"value":"Security & Tactical"
},
{
"trait_type":"Hairstyle",
"value":"Wave"
},
{
"trait_type":"Hair Color",
"value":"Blonde"
},
{
"trait_type":"Background",
"value":"Emerald Cluster",
},
{
"trait_type":"Suit",
"value":"Skyway Aircrew Suit Venesian"
},
{
"trait_type":"Jetpack",
"value":"Tau Morphic HBoost 6K",
"asset_uri":"eth:0x4c333976...0722/1"
},
{ "trait_type":"Glasses",
"value":"Optical HUD V1",
"asset_uri":"eth:0x4c333976...0722/2"
},
{
"trait_type":"Helmet",
"value":"ICC Beacon Helmet 20.4T",
"asset_uri":"eth:0x4c333976...0722/3"
},
{
"trait_type":"Suit Battery Status",
"value":"OK"
},
{
"trait_type":"Rank",
"value":"Passenger"
}]
}

We created this new industry feature to give our holders the flexibility to adjust their own characters and make the journey much more exciting than just holding a static piece of token. By making this available to the broad community, we’re hoping more projects will follow suit and allow for a much more dynamic experience and enable NFT’s to evolve further.

Thank you for your time,

crc@passengers & 0xytocin Labs

About Passengers

Passengers is a space-themed, 4k hyper-detail project driven by a compelling story that teleports community members to the frontiers of the galaxy and beyond. From the darkest galaxies to the bottomless voids, Passengers go to all places and help all people. Passengers never quit and never back down.

Pioneering the project is Redeploy, a Web3 studio that is focused on producing game titles, high-end animations, and gritting cinematics. Ran by a diverse team who have experience working with some of the biggest gaming and animation IPs in the world. https://redeploystudios.com/

--

--