Encoding Overview

Object Format

The Relay and Client use the CBOR encoding scheme for objects and to communicate changes made to a Shop. We are using this format for a number of reasons: First, it is a scheme standardized by the IETF (as RFC 8949) and thus comes with a wide range of implementations in many languages. Secondly, it comes with canonical encoding mode, which our initial protobuf implementation lacked. This last point allows us to encode all objects of a Shop such that we can create a state, or merkle root. This hash allows Users to check if the data they have received is complete and consistent.

The object types are as follows:

  • Manifest: the shop’s metadata

  • Listings: the items a shop lists for sale

  • Tags: the tags a shop uses to categorize its listings

  • Inventory: how many items are currently available for sale

  • Orders: the list of orders, what items were ordered and by whom

  • Accounts: the list of user accounts

Object Visibility

Not all objects are visible to every User of a Shop. Certain information is private to Clerks.

The following objects are publicly visible:

Orders and the patches to them are restricted. All of them are visible to Clerks and Guest users can only see those that they created.

Changes to the inventory are restricted to Clerks as well.

Patching Objects

To not re-transmit objects all the time, we drew inspiration from JSON Patch but applied it to using the CBOR scheme. In a nutshell, changes to an object are operationalized, and which value is changed is indicated by the path to the property.

Here is an example, changing the price of a Listing to 100:

{ "op": "replace", "path": ["Listings", 1, "Price"], "value": 100 }

To build transactional blocks of patches, we are introducing the concept of a PatchSet. This is a list of patches that are applied in order to build up the next state of a Shop. Either they all apply correctly or the Relay refuses the write request.

More Patch Examples

Here are some examples of CBOR patches that can be applied to shop objects:

Adding Order Items and Increasing Quantity

In this example we are patching the following Order:

{
  "ID": 42
  "State": 1
  "Items": []
}

with the following PatchSet with four operations:

[
  { "op": "append", "path": ["Orders", 42, "Items"], "value": { "ListingID": 101, "Quantity": 1 } },
  { "op": "append", "path": ["Orders", 42, "Items"], "value": { "ListingID": 202, "VariationIDs": ["large", "blue"], "Quantity": 2 } },
  { "op": "increment", "path": ["Orders", 42, "Items", 0, "Quantity"], "value": 3 }
  { "op": "replace", "path": ["Orders", 42, "State"], "value": 3 }
]

The resulting Order is this:

{
  "ID": 42
  "State": 3
  "Items": [
    { "ListingID": 101, "Quantity": 4 },
    { "ListingID": 202, "Quantity": 2, "VariationIDs": ["large", "blue"] }
  ]
}

Updating Shop Manifest

This example shows a PatchSet with two operations that modify the Shop’s Manifest:

[
  {
    "op": "add",
    "path": ["Manifest", "Payees", 1, Uint8Array("0x1234567890abcdef1234567890abcdef12345678")],
    "value": { "CallAsContract": true }
  },

  {
    "op": "add",
    "path": ["Manifest", "ShippingRegions", "Germany"],
    "value": {
      "Country": "Germany",
      /* empty fields work as wildcards */
      "PostalCode": "",
      "City": "",
      "PriceModifiers": {
        "standard": {
          "ModificationAbsolute": {
            "Amount": 100,
            "Plus": false
          }
        }
      }
    }
  }
]

Updating Listing Images

This example shows a PatchSet that updates the images of a Listing:

  • Removing the first two images from the ListingMetadata

  • Adding two new images to the end of the array

[
  { "op": "remove", "path": ["Listings", 123, "Metadata", "Images", 0] },
  { "op": "remove", "path": ["Listings", 123, "Metadata", "Images", 0] },
  { "op": "append", "path": ["Listings", 123, "Metadata", "Images"], "value": "https://example.com/images/product123-front.jpg" },
  { "op": "append", "path": ["Listings", 123, "Metadata", "Images"], "value": "https://example.com/images/product123-back.jpg" }
]

Object Signatures

We use EIP191 to sign the CBOR encoded header of a PatchSet.

The header contains the following information:

  • The shop ID

  • The KeyCard nonce (usually a counter)

  • The MMR root hash of all objects in the PatchSet

  • The timestamp of the PatchSet

A signed PatchSet thus consists of:

  • The above header

  • A signature of the header

  • A list of patches

Merkle Mountain Ranges (MMRs)

While our PatchSets don’t grow, we still used the MMR scheme defined in this IETF ID. We picked it because it clearly defines how to create roots and accumulators, whereas we found a lot of ambiguity and diversions in other Merkle tree implementations.

In general, Merkle trees allow us to make inclusion proofs, which are a way to prove that a certain object is part of a Merkle tree by providing the necessary sibling nodes to reconstruct the path from a leaf to the root. This allows us to stream only parts of PatchSets to a client, which can then verify the integrity of the received data. Say, if they only require all the patches for a certain listing, they can download only those. See SubscriptionPushRequest for more.

CBOR References

Shop datastructures

Shop

Shop is a static map with the following fields:

Field

Required

Type

SchemaVersion

Yes

uint

Manifest

Yes

Manifest

Accounts

Yes

Accounts

Listings

Yes

Listings

Tags

Yes

Tags

Orders

Yes

Orders

Inventory

Yes

Inventory

Accounts

Accounts is a dynamicmap with the following fields:

Key Type

Value Type

EthereumAddress

Account

Listings

Listings is a dynamicmap with the following fields:

Key Type

Value Type

uint

Listing

Tags

Tags is a dynamicmap with the following fields:

Key Type

Value Type

text

Tag

Orders

Orders is a dynamicmap with the following fields:

Key Type

Value Type

uint

Order

Inventory

Inventory is a dynamicmap with the following fields:

Key Type

Value Type

uint

uint

SignatureSize

SignatureSize is a constant with value: 65

PublicKeySize

PublicKeySize is a constant with value: 33

HashSize

HashSize is a constant with value: 32

EthereumAddressSize

EthereumAddressSize is a constant with value: 20

Signature

Signature is a constrained bytes

PublicKey

PublicKey is a constrained bytes

Hash

Hash is a constrained bytes

EthereumAddress

EthereumAddress is a constrained bytes

Uint256

Uint256 is a constrained bytes

Uint32

Uint32 is a constrained uint

ChainAddress

ChainAddress is a static map with the following fields:

Field

Required

Type

ChainID

Yes

uint

Address

Yes

EthereumAddress

Payee

Payee is a static map with the following fields:

Field

Required

Type

Address

Yes

ChainAddress

CallAsContract

Yes

bool

PayeeMetadata

PayeeMetadata is a static map with the following fields:

Field

Required

Type

CallAsContract

Yes

bool

Manifest

Manifest is a static map with the following fields:

Field

Required

Type

ShopID

Yes

Uint256

Payees

Yes

Map[uint] => Map[EthereumAddress] => PayeeMetadata

AcceptedCurrencies

Yes

Map[uint] => Map[EthereumAddress] => null

PricingCurrency

Yes

ChainAddress

ShippingRegions

No

Map[text] => ShippingRegion

ShippingRegion

ShippingRegion is a static map with the following fields:

Field

Required

Type

Country

Yes

text

PostalCode

Yes

text

City

Yes

text

PriceModifiers

No

Map[text] => PriceModifier

PriceModifier

PriceModifier is a static map with the following fields:

Field

Required

Type

ModificationPrecents

No

Uint256

ModificationAbsolute

No

ModificationAbsolute

ModificationAbsolute

ModificationAbsolute is a static map with the following fields:

Field

Required

Type

Amount

Yes

Uint256

Plus

Yes

bool

Listing

Listing is a static map with the following fields:

Field

Required

Type

ID

Yes

uint

Price

Yes

Uint256

Metadata

Yes

ListingMetadata

ViewState

Yes

ListingViewState

Options

No

Map[text] => ListingOption

StockStatuses

No

Array of ListingStockStatus

ListingMetadata

ListingMetadata is a static map with the following fields:

Field

Required

Type

Title

Yes

text

Description

Yes

text

Images

No

Array of text

ListingOption

ListingOption is a static map with the following fields:

Field

Required

Type

title

Yes

text

variations

No

Map[text] => ListingVariation

ListingVariation

ListingVariation is a static map with the following fields:

Field

Required

Type

VariationInfo

Yes

ListingMetadata

PriceModifier

No

PriceModifier

SKU

No

text

ListingViewState

ListingViewState is an enumeration with the following values:

Name

Value

comment

Unspecified

0

hidden

Published

1

published

Deleted

2

soft-deleted

ListingStockStatus

ListingStockStatus is a static map with the following fields:

Field

Required

Type

VariationIDs

Yes

Array of text

InStock

No

bool

ExpectedInStockBy

No

text

Tag

Tag is a static map with the following fields:

Field

Required

Type

Name

Yes

text

ListingIDs

Yes

Array of uint

Account

Account is a static map with the following fields:

Field

Required

Type

KeyCards

Yes

Array of PublicKey

Guest

Yes

bool

Order

Order is a static map with the following fields:

Field

Required

Type

ID

Yes

uint

Items

Yes

Array of OrderedItem

State

Yes

OrderState

InvoiceAddress

No

AddressDetails

ShippingAddress

No

AddressDetails

CanceledAt

No

text

ChosenPayee

No

Payee

ChosenCurrency

No

ChainAddress

PaymentDetails

No

PaymentDetails

TxDetails

No

OrderPaid

OrderedItem

OrderedItem is a static map with the following fields:

Field

Required

Type

ListingID

Yes

uint

VariationIDs

No

Array of text

Quantity

Yes

Uint32

OrderState

OrderState is an enumeration with the following values:

Name

Value

comment

Unspecified

0

invalid state

Open

1

open to being changed

Canceled

2

canceled

Committed

3

items frozen

PaymentChosen

4

currency and payee chosen

Unpaid

5

details for payment created

Paid

6

payment completed/received

AddressDetails

AddressDetails is a static map with the following fields:

Field

Required

Type

Name

Yes

text

Address1

Yes

text

Address2

No

text

City

Yes

text

PostalCode

Yes

text

Country

Yes

text

EmailAddress

Yes

text

PhoneNumber

No

text

PaymentDetails

PaymentDetails is a static map with the following fields:

Field

Required

Type

PaymentID

Yes

Hash

Total

Yes

Uint256

ListingHashes

Yes

Array of Hash

TTL

Yes

uint

ShopSignature

Yes

Signature

OrderPaid

OrderPaid is a static map with the following fields:

Field

Required

Type

TxHash

No

Hash

BlockHash

Yes

Hash