Create & Evolve

Creation and Evolution queries

Creating and evolving Living Assets are queries that require the signature of the owner of the corresponding Universe where the affected assets live.

Generate your Universe

When on-boarded to the Living Assets platform, clients are asked to provide a web3 address, and a new Universe will be created, controlled by such web3 address.

  • The initial Universe will be created first in the L2 testnet; when ready to go live, a new Universe will be created in the L2 mainnet.

  • The provided web3 address can be controlled by whatever means desired, from a simple web3 wallet under one person's control, to a sophisticated multisig approach that implements certain business logic and governance. All options are identical from the point of view of the Living Assets platform: all complexity is encapsulated in the provided signature.

Straight-to-Code Examples

To get your hands on working code quickly, please visit the Examples repository, and go straight to the asset_create and asset_evolve scripts inside the nodejs folder.

As shown in the examples, the main class that helps build the query parameters easily is AtomicAssetOps, which can be found in the API Signer NPM package.

import { AtomicAssetOps } from 'freeverse-apisigner-js';

The Execute Mutation

Creating and evolving assets in a given universe is done via a GraphQL mutation which requires three input parameters:

  • ops: a string, in JSON format, with the modification operation wished to be executed in the corresponding Universe.

  • signature: the result of signing the operations string with your account.

  • universe: your universe id.

The mutation will fail if the passed signature does not match with the owner of the universe.

mutation ($opsString: JSON!, $signature: String!)
{ 
    execute(
        input: { 
            ops: [$opsString], 
            signature: $signature,
            universe: universe_id,
        })
    {
        results
    }
}

The state of the newly created/updated assets can be readily queried. The following query returns both the already synchronized (settled) state, as well as the newest (possible pending) state; they both will coincide when the verse returned in the response's receipt is reached.

query {
  assetById(id: "$id") {
      props
      propsSettled
  }
}

Building the mutation with AtomicAssetOps

Instantiate the class AtomicAssetOps to build the three parameters required by the mutation. The following example code build a create_asset operation (via the createAssetOp method), inserts it in the mutation (via the push method), and signs the operation.

const { createAssetOp, AtomicAssetOps } = require('freeverse-apisigner-js');
const assetOps = new AtomicAssetOps({ universeId: 0 });
const operation = createAssetOp({
  nonce,
  ownerId: owner,
  props: asset_props,
  metadata: asset_metadata,
});
assetOps.push({ op: operation });
const sig = assetOps.sign({ web3Account: universe_owner_web3account });
const mutation = assetOps.mutation({ signature: sig });

The content of mutation is to be sent to the GraphQL endpoint. In the example, it looks as follows:

mutation {
    execute(
      input: {
        ops: ["{\"type\":\"create_asset\",\"msg\":{\"nonce\":0,\"owner_id\":\"0x983c1A8FCf74CAF648fD51fd7A6d322a502ae789\",\"props\":\"{\\\"name\\\":\\\"Supercool Dragon\\\",\\\"description\\\":\\\"Legendary creature that loves fire.\\\",\\\"image\\\":\\\"ipfs://QmPCHHeL1i6ZCUnQ1RdvQ5G3qccsjgQF8GkJrWAm54kdtB\\\",\\\"animation_url\\\":\\\"ipfs://QmefzYXCtUXudCy9LYjU4biapHJiP26EGYS8hQjpei472j\\\",\\\"attributes\\\":[{\\\"trait_type\\\":\\\"Rarity\\\",\\\"value\\\":\\\"Scarce\\\"},{\\\"trait_type\\\":\\\"Level\\\",\\\"value\\\":5},{\\\"trait_type\\\":\\\"Weight\\\",\\\"value\\\":123.5}]}\",\"metadata\":\"{\\\"private_data\\\":\\\"that only I will see\\\"}\"}}"],
        signature: "93024b05dcc822776fa84f3dc4070d5e16b9f032b11f4699bf6dbf017fac7fa8587ee9af9c41223c4a7f7fe66d873be1cd6141f0f9efccc13aac402c7e327b2d1b",
        universe: 0,
      }
    )
    {
      results
    }
  }

Signing the Mutation

Universe Owners can use their own web3-compatible method to sign the mutation, instead of using the sign() method provided by the AtomicAssetOps class. Just use the digest() method to get the exact string that needs to be signed:

const digest = assetOps.digest()

Details on the Mutation Parameters

Here are all the details on the ops field required by the Execute Mutation, as well as the parameters it requires.

ops [JSON]

PropertyValues

type

"create_asset" "set_asset_props"

msg

JSON object. See message options below.

msg [JSON]

PropertyValues

nonce

Target owner's user nonce for create_asset Asset nonce for set_asset_props

id

The ID of the asset (mandatory for set_asset_props)

owner_id

The web3 address of the asset owner (mandatory for create_asset)

props

A string containing a JSON object with the public properties of this asset. See Asset Properties Standard for reference.

metadata

A string containing JSON object with the private metadata of this asset

props [JSON]

The props string parameter is a JSON object that contains any public property wished to be assigned to the asset. We advice to follow a de-facto standard, as detailed in Asset Properties Standard. If you wish to follow the standard, the typical fields would include, at least, the following:

PropertyValues

name

The name of the asset

description

The description of the asset

image

A link to the asset image; an ipfs:// route is recommended for decentralization

attributes

a JSON array with each entry containing trait_type and value

animation_url

A link to asset media: video, sound, etc.; an ipfs:// route is recommended for decentralization

Once an asset is created, the number of attributes does not need to remain constant. New attributes can be added as part of the set_asset_props mutation. Examples: adding the charisma attribute to assets that were created without it, or adding a specific license field making explicit the rights transferred to asset owners.

Images, videos, and media in general, can be in any format that the applications/marketplaces that will show the assets are compatible with. Media can be uploaded separately to IPFS and linked in the corresponding image/animation_url fields. The Customizable Marketplace currently supports these formats: jpeg, png, gif, webp, tiff.

metadata [JSON]

The metadata JSON object is intended to expand the asset properties with data that will not be stored in the blockchain. Specific applications or marketplaces may parse this field to implement client-side business logic.

nonce [uint]

All users and assets within a universe are assigned a Number-Used-Once (NOnce) value. Creation and evolution queries require the correct nonce value as parameter, otherwise the mutation will fail. The nonce value prevents replay attacks, whereby the same signed operation is held by an attacker, and relayed multiple times.

  • create_asset operations require passing the user nonce, in a given universe, corresponding to the initial owner to whom the created asset will be assigned.

  • set_asset_propsoperations require passing the asset nonce.

Applications can either keep track of user and asset nonces themselves, or query them when needed before building Execute Mutations, as detailed in these examples:

The Response to the Execute Mutation

The API responses to execute mutations contain a Receipt Json object, which includes the signature of the L2 relayer, confirming that the changes will be applied at the current verse.

If the mutation contains one or several create_asset operations, the receipt contains the assetId of the corresponding created assets.

This is the anatomy of a Receipt:

PropertyValues

ops

an array with the operations applied

results

an array, with entries aligned with ops, containing the unique Ids of the assets created for each create_asset, and a bool confirming the update for each set_asset_props

universe

the universe Id where the ops are applied

signature

the signature provided as input to the query

verse

the verse at which ops will be synchronized with the Layer 1 (= currentVerse + 1)

receiptSignature

the Layer 2 signature committing to the rest of returned parameters

Here's an example of the return of a query that created an asset, and evolved another asset, in one single atomic call:

{
  "data": {
    "execute": {
      "ops": [
        "{\"type\":\"create_asset\",\"msg\":{\"nonce\":227,\"owner_id\":\"0x0A0b39de3E704e8fB1C8E2A8C92c25A1A5f01930\",\"props\":\"{\\\"name\\\":\\\"Supercool Dragon\\\",\\\"description\\\":\\\"Legendary creature that loves ice.\\\",\\\"image\\\":\\\"ipfs://QmPCHHeL1i6ZCUnQ1RdvQ5G3qccsjgQF8GkJrWAm54kdtB\\\",\\\"animation_url\\\":\\\"ipfs://QmefzYXCtUXudCy9LYjU4biapHJiP26EGYS8hQjpei472j\\\",\\\"attributes\\\":[{\\\"trait_type\\\":\\\"Rarity\\\",\\\"value\\\":\\\"Common\\\"},{\\\"trait_type\\\":\\\"Level\\\",\\\"value\\\":10},{\\\"trait_type\\\":\\\"Weight\\\",\\\"value\\\":223.5}]}\",\"metadata\":\"{\\\"private_data\\\":\\\"that has been updated\\\"}\"}}",
        "{\"type\":\"set_asset_props\",\"msg\":{\"nonce\":5,\"id\":\"9997220295222263808910038093126797702019127927349276\",\"props\":\"{\\\"name\\\":\\\"My First Living Asset\\\",\\\"description\\\":\\\"Freeverse living asset. The on-chain properties of this NFT can evolve.\\\",\\\"image\\\":\\\"ipfs://QmUn4gzAAY8vDFNnA25MopB1RWtMmwWdQCEMUe67XiPW8x\\\",\\\"animation_url\\\":\\\"ipfs://QmefzYXCtUXudCy9LYjU4biapHJiP26EGYS8hQjpei472j\\\",\\\"attributes\\\":[{\\\"trait_type\\\":\\\"Level\\\",\\\"value\\\":\\\"1\\\"},{\\\"trait_type\\\":\\\"Shape\\\",\\\"value\\\":\\\"Cilinder\\\"},{\\\"trait_type\\\":\\\"Creation date\\\",\\\"value\\\":\\\"2022-07-13 10:50:01\\\"}]}\",\"metadata\":\"{\\\"private_data\\\":\\\"that has been updated\\\"}\"}}"
      ],
      "signature": "5746cdd53449f8c9d693125fdc44213c95c94904b2c547e7268c7f5028427138489ec2044d0737530671b2f2917d2163781f688718e2c613d05a124627eff64b1c",
      "universe": 0,
      "verse": 62,
      "results": [
        "{\"id\":\"116101747409823859223166001815771088694834615066564912\"}",
        "{\"result\":\"success\"}"
      ],
      "receiptSignature": "c0337ea5f1a1ff35346fc589b59bc55f705dfdb2409d8e0717371832d8343e54460e733e63a4df7b035cf36b8b044befdf73a3087974eeb10197ec2dc95aa6851c"
    }
  }
}

Here's a code example showing how to verify the signer of the receipt signature.

Last updated

freeverse.io