fep

FEP-0837: Federated Marketplace

Summary

This document describes a minimal implementation of a federated marketplace based on ActivityPub protocol and Valueflows vocabulary. In such marketplace actors can publish offers and requests, respond to offers and requests published by other actors, enter into agreements and exchange information necessary to complete these agreements.

History

Extension of ActivityPub protocol with Valueflows vocabulary was initially proposed by Lynn Foster in FEP-d767.

Requirements

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC-2119.

Overview

sequenceDiagram
  actor Alice
  actor Bob
  Note right of Alice: Alice publishes a Proposal
  Bob ->> Alice: Bob sends Offer(Agreement) activity
  Alice ->> Bob: Alice sends Accept(Agreement) activity
  Note over Alice, Bob: Alice and Bob complete the transaction
  Alice ->> Bob: Alice sends confirmation activity

Proposals

Valueflows defines proposals as published requests or offers, sometimes with what is expected in return.

The representation of a proposal is a JSON document with the following properties:

Intents are proposed economic transactions. The primary intent describes what is being offered or requested, and reciprocal intent describes what is expected or offered in return. Some examples:

The representation of an intent is a JSON document with the following properties:

Minimal example:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "om2": "http://www.ontology-of-units-of-measure.org/resource/om-2/",
      "vf": "https://w3id.org/valueflows/ont/vf#",
      "Proposal": "vf:Proposal",
      "Intent": "vf:Intent",
      "action": "vf:action",
      "purpose": "vf:purpose",
      "unitBased": "vf:unitBased",
      "publishes": "vf:publishes",
      "resourceQuantity": "vf:resourceQuantity",
      "hasUnit": "om2:hasUnit",
      "hasNumericalValue": "om2:hasNumericalValue"
    }
  ],
  "type": "Proposal",
  "id": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930",
  "purpose": "offer",
  "attributedTo": "https://market.example/users/alice",
  "publishes": {
    "type": "Intent",
    "id": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary",
    "action": "transfer",
    "resourceQuantity": {
      "hasUnit": "one",
      "hasNumericalValue": "1"
    }
  },
  "unitBased": false,
  "to": "https://www.w3.org/ns/activitystreams#Public"
}

Full example:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "om2": "http://www.ontology-of-units-of-measure.org/resource/om-2/",
      "vf": "https://w3id.org/valueflows/ont/vf#",
      "Proposal": "vf:Proposal",
      "Intent": "vf:Intent",
      "action": "vf:action",
      "purpose": "vf:purpose",
      "unitBased": "vf:unitBased",
      "publishes": "vf:publishes",
      "reciprocal": "vf:reciprocal",
      "resourceConformsTo": "vf:resourceConformsTo",
      "resourceQuantity": "vf:resourceQuantity",
      "availableQuantity": "vf:availableQuantity",
      "hasUnit": "om2:hasUnit",
      "hasNumericalValue": "om2:hasNumericalValue"
    }
  ],
  "type": "Proposal",
  "id": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930",
  "purpose": "offer",
  "attributedTo": "https://market.example/users/alice",
  "name": "Offering used bike",
  "content": "Blue one-speed bike, 15 years old, some rust",
  "published": "2023-06-18T19:22:03.918737Z",
  "location": {
    "type": "Place",
    "longitude": -71.0,
    "latitude": 25.0
  },
  "publishes": {
    "type": "Intent",
    "id": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary",
    "action": "transfer",
    "resourceConformsTo": "https://www.wikidata.org/wiki/Q11442",
    "resourceQuantity": {
      "hasUnit": "one",
      "hasNumericalValue": "1"
    },
    "availableQuantity": {
      "hasUnit": "one",
      "hasNumericalValue": "1"
    }
  },
  "reciprocal": {
    "type": "Intent",
    "id": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal",
    "action": "transfer",
    "resourceConformsTo": "https://www.wikidata.org/wiki/Q4917",
    "resourceQuantity": {
      "hasUnit": "one",
      "hasNumericalValue": "30"
    }
  },
  "unitBased": false,
  "to": "https://www.w3.org/ns/activitystreams#Public"
}

Publishing a proposal

Proposals can be linked to actors (if actor provides a service) or to other objects (if they represent economic resources) using FEP-0ea0 payment links. Proposals can also be added to public collections, or be delivered to actor’s followers using Create activity, or announced by group actors.

If FEP-0ea0 payment link is used, its href attribute MUST contain the proposal ID and its rel array MUST contain the string https://w3id.org/valueflows/ont/vf#Proposal. The value of mediaType attribute SHOULD be application/ld+json; profile="https://www.w3.org/ns/activitystreams".

Example of a proposal attached to an actor via payment link:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "type": "Person",
  "id": "https://market.example/users/alice",
  "inbox": "https://market.example/users/alice",
  "outbox": "https://market.example/users/alice",
  "attachment": [
    {
      "type": "Link",
      "name": "Buy a bike",
      "mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
      "href": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930",
      "rel": ["payment", "https://w3id.org/valueflows/ont/vf#Proposal"]
    }
  ]
}

Consuming implementations which don’t have marketplace features MAY display proposals similarly to Note objects.

Responding to a proposal

Agreements

An interested party responds to a proposal and then parties start negotiating to reach an agreement.

To respond to a proposal, an interested party MUST send an Agreement object wrapped in Offer activity to the actor indicated by the attributedTo property of the proposal. The proposing party MUST either commit to the action described in the proposal or send a rejection.

In the first case, the proposer finalizes the agreement and sends Accept(Offer) activity back to the interested party.

In the second case, the proposer sends Reject(Offer) activity. The interested party MAY send Offer(Agreement) activities many times until agreement is reached.

The representation of an agreement is a JSON document with the following properties:

Commitments are promised economic transactions. The representation of a commitment is a JSON document with the following properties:

The first commitment MUST satisfy the primary intent of the proposal. The second commitment MUST satisfy the reciprocal intent of the proposal (if present).

The units specified in the agreement MUST match the units specified in the proposal. If the value of unitBased property of the proposal is false, the amount of resources specified in commitments MUST be equal to amounts specified in the proposal. If the value is true, amounts MUST be multiples of amounts specified in the proposal. If unitBased property is not present on the proposal, arbitrary amounts can be used.

Example of an Offer(Agreement) activity:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "om2": "http://www.ontology-of-units-of-measure.org/resource/om-2/",
      "vf": "https://w3id.org/valueflows/ont/vf#",
      "Agreement": "vf:Agreement",
      "stipulates": "vf:stipulates",
      "stipulatesReciprocal": "vf:stipulatesReciprocal",
      "Commitment": "vf:Commitment",
      "satisfies": "vf:satisfies",
      "resourceQuantity": "vf:resourceQuantity",
      "hasUnit": "om2:hasUnit",
      "hasNumericalValue": "om2:hasNumericalValue"
    }
  ],
  "type": "Offer",
  "id": "https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer",
  "actor": "https://social.example/users/bob",
  "object": {
    "type": "Agreement",
    "stipulates": {
      "type": "Commitment",
      "satisfies": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary",
      "resourceQuantity": {
        "hasUnit": "one",
        "hasNumericalValue": "1"
      }
    },
    "stipulatesReciprocal": {
      "type": "Commitment",
      "satisfies": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal",
      "resourceQuantity": {
        "hasUnit": "one",
        "hasNumericalValue": "30"
      }
    }
  },
  "to": "https://market.example/users/alice"
}

Accepting an agreement

The object of Accept activity MUST be the id of the Offer activity previously sent to the actor.

Accept activity MUST have the result property containing the Agreement object. The finalized agreement and corresponding commitments MUST have an id property. If a similar agreement between parties already exists, it MAY be updated and its id re-used. The quantities specified in the finalized agreement MUST match the quantities specified in Agreement object from the Offer activity.

The finalized agreement MAY have url property containing one or more links to resources associated with the agreement. An example of such resource is a payment page (which can be represented as an FEP-0ea0 link).

Example:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    {
      "om2": "http://www.ontology-of-units-of-measure.org/resource/om-2/",
      "vf": "https://w3id.org/valueflows/ont/vf#",
      "Agreement": "vf:Agreement",
      "stiplulates": "vf:stiplulates",
      "stipulatesReciprocal": "vf:stipulatesReciprocal",
      "Commitment": "vf:Commitment",
      "satisfies": "vf:satisfies",
      "resourceQuantity": "vf:resourceQuantity",
      "hasUnit": "om2:hasUnit",
      "hasNumericalValue": "om2:hasNumericalValue"
    }
  ],
  "type": "Accept",
  "id": "https://market.example/activities/059f08fa-31b1-4136-8d76-5987d705a0ab",
  "actor": "https://market.example/users/alice",
  "object": "https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer",
  "result": {
    "type": "Agreement",
    "id": "https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2",
    "attributedTo": "https://market.example/users/alice",
    "stiplulates": {
      "id": "https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#primary",
      "type": "Commitment",
      "satisfies": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary",
      "resourceQuantity": {
        "hasUnit": "one",
        "hasNumericalValue": "1"
      }
    },
    "stipulatesReciprocal": {
      "id": "https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#reciprocal",
      "type": "Commitment",
      "satisfies": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal",
      "resourceQuantity": {
        "hasUnit": "one",
        "hasNumericalValue": "30"
      }
    },
    "url": {
      "type": "Link",
      "href": "https://pay.example/invoices/7f1f0c81-0108-4c91-9cb1-d38ebccc3aa1",
      "rel": "payment"
    }
  },
  "to": "https://social.example/users/bob"
}

Rejecting an agreement

The object of Reject activity MUST be the id of the Offer activity previously sent to the actor.

Activity MAY contain content property indicating the reason for rejection.

Example:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "type": "Reject",
  "id": "https://market.example/activities/8c05f97f-1531-4b70-9ca8-4ee4a09f36a4",
  "actor": "https://market.example/users/alice",
  "object": "https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer",
  "content": "Not available",
  "to": "https://social.example/users/bob"
}

Confirmations

Economic transaction happens outside the protocol. When both parties complete their parts of the transaction, the proposing party MUST publish a confirmation.

The type and structure of confirmation activity may vary between different marketplaces, but it MUST contain a reference to the Agreement object. The context property is RECOMMENDED for this purpose.

Example:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams"
  ],
  "type": "Create",
  "id": "https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743/create",
  "actor": "https://market.example/users/alice",
  "object": {
    "type": "Document",
    "id": "https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743",
    "name": "Receipt",
    "context": "https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2",
    "published": "2023-07-03T14:13:41.843794Z"
  },
  "to": "https://social.example/users/bob"
}

References

CC0 1.0 Universal (CC0 1.0) Public Domain Dedication

To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.