Writing schemas

Patchwork data modeling is akin to designing a database schema. You provide fields and their corresponding types that your data will adhere to. We currently have 20 different field types you can use in your schemas, ranging from booleans to integers to strings and more.

Importantly, because the Patchwork metadata standard uses some complex structure packing to be as storage-efficient as possible, we recommend using our PDK to bootstrap your contract. When using our PDK, schemas can be modeled in simple JSON and our codegen will translate it into a valid Patchwork metadata schema.

The following PDK schema outlines a contract that stores rating scores, where each minted NFT has 2 metadata fields: one for the rater, and one for the rating.

Ratings.json
{
  "scopeName": "fooratings",
  "name": "Ratings",
  "symbol": "RATE",
  "schemaURI": "https://ratingsthing/my-metadata.json",
  "imageURI": "https://ratingsthing/asset/{tokenID}.png",
  "fields": [
    {
        "id": 1,
        "key": "rater",
        "type": "char16",
        "description": "Address of the person minting the rating"
    },
    {
        "id": 2,
        "key": "rating",
        "type": "uint8",
        "description": "Rating score from 1-5"
    }
  ]
}

PDK translates this into the following Patchwork721 schema, along with the necessary packing and unpacking logic.

Ratings.sol
function schema() external pure returns (MetadataSchema memory) {
    MetadataSchemaEntry[] memory fields = new MetadataSchemaEntry[](2);
    fields[0] = MetadataSchemaEntry(0, 0, FieldType.ADDRESS, 1, FieldVisibility.PUBLIC, 0, 0, "rater");
    fields[1] = MetadataSchemaEntry(1, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 1, 0, "rating");
    return MetadataSchema(1, fields);
}

Note the use of Patchwork’s MetadataSchemaEntry above and the 8 parameters passed to it. Each parameter describes a special piece of information about the field—from its permissions to its content length (single, fixed-size array, etc.) to its visibility—and also includes the necessary info needed for Patchwork to unpack the data from its stored form.

Here’s the specification for MetadataSchemaEntry:

struct MetadataSchemaEntry {
    uint256 id;
    uint256 permissionId;
    FieldType fieldType;
    uint256 fieldCount;
    FieldVisibility visibility;
    uint256 slot;
    uint256 offset;
    string key;
}

Looking again at the rating field code from the Ratings.sol example, this maps to the following assertion:

My “rating” field has an ID of 1 (these are zero-indexed), permission ID of 0, is a uint8 type, has only 1 field entry/count per record, is publicly visible, is in my second storage slot (again, zero-indexed), has no offset, and has a key of rating.

Again, if you use PDK, you don’t need to worry too much about the details here as this code is all automatically generated for you from your JSON schema.

For the rest of this document, we’ll be focusing solely on JSON schemas, but you can find more Solidity examples in PDKs test suite.

Field types

There are 20 available field types to use in your schemas:

boolean
int8
int16
int32
int64
int128
int256
uint8
uint16
uint32
uint64
uint128
uint256
char8
char16
char32
char64
literef
address
string

Almost all of these are self-explanatory and map directly to Solidity types. The literef type is the outlier: this type represents a reference to a Patchwork Fragment. These saved references are referred to as LiteRefs and follow a special packing scheme for hyper-efficient storage.

Dynamic and fixed-length arrays

By default, your fields will have a length of 1, meaning they only hold one piece of information per field record. If you want to store more than one item per field (i.e. arrays), use the arrayLength key in your schema. Setting arrayLength to 0 turns the field into a dynamic array, setting it to 1 asserts a length of 1 (equivalent to leaving it unset), and any number higher than one turns the field into a fixed-length array of that size.

For example, the following field spec documents 2 fields. The first is a dynamic-length array that can grow unboundedly, and the second is a fixed-length array that can contain 3 items.

"fields": [
    {
        "id": 1,
        "key": "favemojis",
        "type": "char8",
        "arrayLength": 0,
        "description": "Dynamic array of favorite emojis"
    },
    {
        "id": 2,
        "key": "favwords",
        "type": "char16",
        "arrayLength": 3,
        "description": "Three favorite words"
    }
]

The field type string is a dynamic field and will not be packed with other fixed-length values.

Field permissions

When fields have a permissionID, addresses with permission grants will also be able to write to these fields on an individual basis.

The function setPermissions on a Patchwork721 provides a bitfield of “allows” for an address. For example, to allow writing from 0xabcd to fields 1 and 4, which would be 1001 in binary (indicating a 1 or “allow” for position 1 and position 4) or 9 in base 10

To convert from permissionId to a stored value, use 1 << (permissionId - 1) making sure to add all of the converted permission IDs for the complete value to set.

setPermissions(0xabcd, 9)