Scene Config & AssetIDs

Updated 2022-09-12

NFTs store their contents (a name, traits, and other metadata, plus an image, a 3D model, an audio track, etc.) in a token_uri, which can point to a JSON file stored on IPFS or a centralized file store. To facilitate scene configuration and multi-asset scenes, the GSR introduces a second metadata layer, called sceneUri. Consumers of Placement data should merge the scene_uri over the token_uri to get a complete set of metadata to display the scene.

The sceneUri can contain arbitrary data, but a proposed standard for that data follows the tokenUri metadata standard:

  name: Override the name of the NFT
  description: Override the description of the NFT
  image: Override the poster image for the NFT, perhaps with a capture of the 3D scene
  animation_url: a simple file (GLB model, MP4 video) that overrides the animation_uri of the NFT
  /** A URI to an external site that renders the scene */
  externalScene: string;
  /** A pointer to an external site describing the scene */
  external_uri: string;
  /** These should be merged with the NFT’s traits */
  traits: Record<string, any>[];
  /** This holds data about what to render at the scene */
  contents: {
    /** A multi-line text-based message to place at the scene */
    message?: string;
    /** A list of individual assets to render at specific positions in the scene. */
    objects?: Asset[];
    /** An image that should be overlaid over the full scene. May be an animated image or video. */
    overlay?: {
      overlayUri: string
      Opacity: number

Where an Asset describes a 3D asset, along with its position and optionally if that asset describes is an ownable NFT that must be placed within the NFT to be displayed (see next section). All fields are optional:

Asset {
    /** This should match another asset on the GSR, if provided */
    assetId?: string
    /** Right-hand rule position relative to the middle of scene */
    position: {x, y, z}
    scale: {x, y, z}
    rotation: {x, y, z}
    /** URI to the asset. This may be a GLB or audio file. Videos and images should be applied as textures to an asset.
    modelUri: string;
    /** List of textures that should be applied to nodes in the asset, if it is a 3D asset. */
    textures: {node: string; textureUri: string; channel: string; assetIdentifier: string }[]

Multi-asset scenes

While a simple use of the GSR is to place a single 3D NFT at a particular location with some supporting scene configuration, it may be desirable to place multiple 3D NFTs in one scene, or even use other NFTs as textures or audio content within a scene. While the scene’s metadata may contain arbitrary models, textures, and content, this does not allow for the providence of using true NFTs in a single location.

Instead, while a Placement must always describe a single NFT, users may then place other NFTs “inside” a placed NFT. They can do this by calling a placement function on the GSR that, instead of taking a geohash, takes the unique identifier of another NFT. This declares that the second NFT is available to be used in the scene of the primary NFT. So long as this situation continues to hold, applications rendering the scene of the geolocated NFT should display the secondary NFT if it is used in the scene_uri scene. If the secondary NFT is sold, is moved to a new place, or its placement expires, then it SHOULD NOT be displayed when rendering the scene.

Notably, this allows anyone to make their NFT available to another NFT’s scene. But only the owner of the main NFT may choose to use the secondary NFT in its scene. This allows for multiple owners to collaborate on a single scene, while preventing unwanted additions to a scene.


The GSR is designed to support arbitrary unique digital assets. For ERC-721-compliant NFTs, this corresponds to the combination of the chainId, contract address, and token ID of an asset. But other chains and asset standards may have different unique identifiers, and centralized asset stores simply have a GUID. In all cases, the GSR allows owners to geolocate any asset by publishing its unique identifier as a bytestring. A given address can declare one position for a given unique identifier, so an NFT can only be at one place at one time.

To construct this unique AssetId identifier, and allow it to be decoded later by off-chain indexing services, the GSR declares an EncodedAssetId struct, which is passed to any placement function.

It is in the form:

struct EncodedAssetId {
    bytes32 assetType;
    bytes collectionId;
    bytes itemId;

The “assetType” is a keccak256-hashed string describing the asset. Examples would include a hash of “ERC721” and “ERC1155”, as well as “SolanaMetaplex”. Off-chain, each AssetType will be declared to correspond to a specific ABI encoding for the collectionId and itemId.

For instance, for an ERC721, the collectionId will be abi.encodePacked("uint256", chainId, "address", contractAddress), and the itemId will be abi.encodePacked("uint256", tokenId). This struct will be saved in the event logs for every placement event, allowing indexers with knowledge of the encoding scheme for each assetType to decode the values, and use them to verify ownership - but without the expense of storing the ABIs on chain. The collectionId and itemId are stored separately to facilitate search - a hash of the collectionId is stored as an indexed field on the GsrPlacement event emitted by every placement action, which allows consumers to use a bloom filter to search for placement events on a single contract or other “collection” of related assets. Depending on the data hierarchy of a given digital asset project, the collection ID may or may not be useful, and may even be used to store other data like a dynamic ABI for decoding the itemId.

Note that as the GSR does not impose particular constraints on these identifiers, consumers of GSR data should verify that unique identifiers match the expected formatting, and consider them malformed data otherwise.

While the EncodedAssetId is useful for logs and off-chain indexers, for on-chain storage, a hash of the EncodedAssetId will be used as a simple bytes32 unique ID for each placement.

Last updated