Skip to content

FEP-0837: Federated Marketplace

by silverpill silverpill@firemail.cc submited on 2023-08-17

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.

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:

  • id (REQUIRED): the proposal’s unique global identifier.
  • type (REQUIRED): the type of the object SHOULD be Proposal. If interoperability with other ActivityPub services is desirable, implementers MAY use object types from Activity Vocabulary, such as Note.
  • attributedTo (REQUIRED): the actor who published the proposal.
  • name (RECOMMENDED): the title of the proposal.
  • content (OPTIONAL): the description of the proposal. The type of content SHOULD be text/html.
  • published (RECOMMENDED): the date and time at which the proposal was published.
  • location (OPTIONAL): indicates a physical location associated with the proposal. The representation of location MUST conform to the recommendations of Activity Vocabulary document, section 5.3 Representing Places.
  • publishes (REQUIRED): the primary intent of this proposal (see below).
  • reciprocal (OPTIONAL): the reciprocal intent of this proposal (see below).
  • unitBased (REQUIRED): indicates whether the quantities expressed in the proposal can be multiplied or not.
  • to (REQUIRED): the audience of the proposal.

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:

  • A good is offered in exchange for money. Transfer of a good is a primary intent and a money transfer is a reciprocal intent.
  • A good is offered as a gift. Transfer of a good is a primary intent and there’s no reciprocal intent.
  • Service is requested in exchange for money. Delivery of a service is a primary intent and money transfer is a reciprocal intent.

Whether proposal is an offer or a request is determined by the properties of the primary intent (see below).

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

  • id (REQUIRED): the unique global identifier of the intent. Implementations SHOULD use URL fragments to identify intents associated with a given proposal. The RECOMMENDED fragment identifiers for primary and reciprocal intents are primary and reciprocal.
  • type (REQUIRED): the type of the object MUST be Intent.
  • action (REQUIRED): the type of economic transaction. The value of this property SHOULD be either deliverService or transfer.
  • resourceConformsTo (RECOMMENDED): the type of an economic resource. Could be any URI.
  • resourceQuantity (REQUIRED): the amount and unit of the economic resource. This is an object with two properties:
  • hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification. The RECOMMENDED unit for countable items is one.
  • hasNumericalValue (REQUIRED): amount of the resource.
  • availableQuantity (OPTIONAL): the quantity of the offered resource currently available.
  • provider: the actor who provides the resource. This property is REQUIRED for offered resources. If used in the primary intent, the proposal is considered an offer and the value of this property MUST match the value of attributedTo property.
  • receiver: the actor who receives the resource. This property is REQUIRED for requested resources. If used in the primary intent, the proposal is considered a request and the value of this property MUST match the value of attributedTo property.

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/",
      "Proposal": "vf:Proposal",
      "Intent": "vf:Intent",
      "receiver": "vf:receiver",
      "provider": "vf:provider",
      "action": "vf:action",
      "unitBased": "vf:unitBased",
      "publishes": "vf:publishes",
      "reciprocal": "vf:reciprocal",
      "resourceConformsTo": "vf:resourceConformsTo",
      "resourceQuantity": "vf:resourceQuantity",
      "hasUnit": "om2:hasUnit",
      "hasNumericalValue": "om2:hasNumericalValue"
    }
  ],
  "type": "Proposal",
  "id": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930",
  "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"
    },
    "provider": "https://market.example/users/alice"
  },
  "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"
    },
    "receiver": "https://market.example/users/alice"
  },
  "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/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/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:

  • id (OPTIONAL): the unique global identifier of the agreement. This property is REQUIRED for finalized agreements.
  • type (REQUIRED): the type of the object MUST be Agreement.
  • clauses (REQUIRED): the list of commitments associated with the agreement.

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

  • id (OPTIONAL): the unique global identifier of the commitment. This property is REQUIRED for commitments in finalized agreements. Implementations SHOULD use URL fragments to identify commitments associated with a given agreement. The RECOMMENDED fragment identifiers for commitments satisfying primary and reciprocal intents of the proposal are primary and reciprocal.
  • type (REQUIRED): the type of the object MUST be Commitment.
  • satisfies (REQUIRED): the reference to an intent.
  • resourceQuantity (REQUIRED): the amount and unit of the economic resource.

The first commitment MUST satisfy the primary intent of the proposal. The second commitment MUST satisfy the reciprocal intent of 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. Otherwise, amounts MUST be multiples of amounts specified in the proposal.

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/",
      "Agreement": "vf:Agreement",
      "clauses": "vf:clauses",
      "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",
    "clauses": [
      {
        "type": "Commitment",
        "satisfies": "https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary",
        "resourceQuantity": {
          "hasUnit": "one",
          "hasNumericalValue": "1"
        }
      },
      {
        "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 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/",
      "Agreement": "vf:Agreement",
      "clauses": "vf:clauses",
      "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",
    "clauses": [
      {
        "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"
        }
      },
      {
        "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",
    "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.