{"id":24552030,"url":"https://github.com/mlabs-haskell/cardano-open-oracle-protocol","last_synced_at":"2025-04-15T22:20:52.416Z","repository":{"id":146283266,"uuid":"498136877","full_name":"mlabs-haskell/cardano-open-oracle-protocol","owner":"mlabs-haskell","description":"COOP - Cardano open oracle protocol","archived":false,"fork":false,"pushed_at":"2024-03-20T11:33:52.000Z","size":1046,"stargazers_count":22,"open_issues_count":14,"forks_count":3,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-03-29T02:04:23.548Z","etag":null,"topics":["cardano","catalyst","decentralization","haskell","nix","oracle","plutarch","plutus"],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mlabs-haskell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-05-31T00:38:02.000Z","updated_at":"2024-09-11T09:23:49.000Z","dependencies_parsed_at":"2023-05-25T19:00:25.603Z","dependency_job_id":"a331088c-e1ce-4217-b28e-7642999c611a","html_url":"https://github.com/mlabs-haskell/cardano-open-oracle-protocol","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlabs-haskell%2Fcardano-open-oracle-protocol","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlabs-haskell%2Fcardano-open-oracle-protocol/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlabs-haskell%2Fcardano-open-oracle-protocol/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mlabs-haskell%2Fcardano-open-oracle-protocol/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mlabs-haskell","download_url":"https://codeload.github.com/mlabs-haskell/cardano-open-oracle-protocol/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249162020,"owners_count":21222580,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cardano","catalyst","decentralization","haskell","nix","oracle","plutarch","plutus"],"created_at":"2025-01-23T01:19:49.323Z","updated_at":"2025-04-15T22:20:52.397Z","avatar_url":"https://github.com/mlabs-haskell.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --\u003e\n# Table of Contents\n\n- [Cardano open oracle protocol](#cardano-open-oracle-protocol)\n  - [Introduction](#introduction)\n  - [Documentation](#documentation)\n  - [Getting Started](#getting-started)\n    - [Installing Nix](#installing-nix)\n    - [Building and developing](#building-and-developing)\n    - [Tutorial](#tutorial)\n      - [1. Preparing the environment](#1-preparing-the-environment)\n      - [2. Environment variables and directories](#2-environment-variables-and-directories)\n      - [3. Running a local Cardano network](#3-running-a-local-cardano-network)\n      - [4. Initializing the Protocol](#4-initializing-the-protocol)\n      - [5. Running a TxBuilder gRPC service](#5-running-a-txbuilder-grpc-service)\n      - [6. Running a FactStatementStore gRPC service](#6-running-a-factstatementstore-grpc-service)\n      - [7. Running a Publisher gRPC service](#7-running-a-publisher-grpc-service)\n      - [8. Publishing a Fact Statement](#8-publishing-a-fact-statement)\n      - [9. Garbage collecting obsolete Fact Statement UTxOs](#9-garbage-collecting-obsolete-fact-statement-utxos)\n      - [10. Garbage collecting obsolete Certificate UTxOs](#10-garbage-collecting-obsolete-certificate-utxos)\n      - [11. Referencing published Fact Statement in Consumer dApps](#11-referencing-published-fact-statement-in-consumer-dapps)\n\n\u003c!-- markdown-toc end --\u003e\n\n# Cardano open oracle protocol\n\n![COOP logo](coop-docs/images/coop-logo.png)\n\n## Introduction\n\nThe Cardano open oracle protocol (COOP) is a protocol complemented by\nan open-source SDK for publishing and consuming on-chain data using Cardano\n[CIP-31](https://developers.cardano.org/docs/governance/cardano-improvement-proposals/cip-0031/)\nreference inputs. Reference inputs allow a data provider to publish a data point\nonce and multiple consumers to use the data point in on-chain dApp scripts,\nwithout interfering with each other.\n\nThe purpose of this project is to allow developers in the Cardano ecosystem to\nhost and run their own COOP Publisher and integrate it into their broader\nOracle offerings.\n\nDevelopment of the COOP is led by [MLabs](https://mlabs.city/) with feedback and\ndirection provided by the [Orcfax](https://www.orcfax.link/about/) oracle\nproject which will implement the COOP on its platform.\n\nThis project was graciously funded by the Cardano Treasury in [Catalyst Fund\n8](https://cardano.ideascale.com/c/idea/402572).\n\n## Documentation\n\nThe protocol is described in further detail in the following documents\n\n- [Design document](coop-docs/00-design.md) contains information about the overall goals of this project,\n- [Plutus protocol](coop-docs/02-plutus-protocol.md) contains information about the wallets, tokens, minting policies, validators and transactions used in COOP and their relationship,\n- [Frontend protocol](coop-docs/03-frontend-protocol.md) contains information about how users must interact with the COOP Publisher in order to publish new Fact Statements and garbage collect obsolete Fact Statements,\n- [Backend protocol](coop-docs/04-backend-protocol.md) contains information on the back-end operations needed to serve the Frontend protocol,\n- [Mapping between JSON and Plutus Data](coop-docs/05-json-plutus.md) contains information about how JSON encodings map into PlutusData encoding that can be used 'onchain'.\n\n## Getting Started\n\n### Installing Nix\n\nThe COOP repository relies heavily on the [Nix Package\nManager](https://nixos.org/download.html) for both development and package\ndistribution.\n\nTo install run the following command:\n\n```sh\nsh \u003c(curl -L https://nixos.org/nix/install) --daemon\n```\n\nand follow the instructions.\n\n```sh\n$ nix --version\nnix (Nix) 2.8.0\n```\n\n\u003e NOTE: The repository should work with Nix version greater or equal to 2.8.0.\n\nMake sure to enable [Nix Flakes](https://nixos.wiki/wiki/Flakes#Enable_flakes)\nand IFD by editing either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` on\nyour machine and add the following configuration entries:\n\n```yaml\nexperimental-features = nix-command flakes\nallow-import-from-derivation = true\n```\n\nOptionally, to improve build speed, it is possible to set up binary caches\nmaintained by IOHK and Plutonomicon by setting additional configuration entries:\n\n```yaml\nsubstituters = https://cache.nixos.org https://iohk.cachix.org https://cache.iog.io https://public-plutonomicon.cachix.org\ntrusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= iohk.cachix.org-1:DpRUyj7h7V830dp/i6Nti+NEO2/nhblbov/8MW7Rqoo= public-plutonomicon.cachix.org-1:3AKJMhCLn32gri1drGuaZmFrmnue+KkKrhhubQk/CWc=\n```\n\n### Building and developing\n\nOnce Nix is installed, you should be able to seamlessly use the repository to\ndevelop, build and run packages.\n\nDownload the Git repository:\n\n```sh\ngit clone https://github.com/mlabs-haskell/cardano-open-oracle-protocol.git\n```\n\nTo facilitate seamlessly moving between directories and associated Nix\ndevelopment shells we use [direnv](https://direnv.net) and\n[nix-direnv](https://github.com/nix-community/nix-direnv):\n\nTo install both using `nixpkgs`:\n\n```sh\nnix profile install nixpkgs#direnv\nnix profile install nixpkgs#nix-direnv\n```\n\nYour shell and editors should pick up on the `.envrc` files in different\ndirectories and prepare the environment accordingly. Use `direnv allow` to\nenable the direnv environment and `direnv reload` to reload it when necessary.\nOtherwise, each `.envrc` file in COOP sub-directories contains a proper Nix\ntarget you can use with the `nix develop` command.\nFor example, `nix develop #dev-pab` will build a Nix development shell\nthat has everything needed for developing and compiling the `coop-pab` component.\n\nAdditionally, throughout the repository one can use the [pre-commit](https://pre-commit.com/) tool:\n\n```sh\n$ pre-commit run --all\ncabal-fmt................................................................Passed\nfourmolu.................................................................Passed\nhlint....................................................................Passed\nmarkdownlint.............................................................Passed\nnix-linter...............................................................Passed\nnixpkgs-fmt..............................................................Passed\nshellcheck...............................................................Passed\n```\n\nto run all the code quality tooling specified in the [pre-commit-check config file](pre-commit-check.nix).\nThese `pre-commit` checks need to pass for a `git commit` to be successful.\n\n### Tutorial\n\nThis tutorial demonstrates how to create and operate your own COOP Publisher,\nand how users can eventually use your service to publish new Fact Statements\nprovided.\n\nWhile working through the Tutorial feel free to explore and inspect various Bash\nfunctions and command line tools used. For example, using the `type` standard\nBash function one can discover the definition of some other Bash functions:\n\n```sh\n[coop-env ~ coop-tutorial] $ type coop-get-state\ncoop-get-state is a function\ncoop-get-state ()\n{\n    coop-pab-cli get-state --any-wallet $GOD_PKH;\n    cat $COOP_PAB_DIR/coop-state.json | json_pp\n}\n```\n\nAdditionally, each cli tool provided by COOP support a `--help` flag that\nprovides the detailed explanation of the purpose of commands and their options:\n\n```sh\n[coop-env ~ coop-tutorial] $ coop-plutus-cli --help\n[coop-env ~ coop-tutorial] $ coop-pab-cli --help\n[coop-env ~ coop-tutorial] $ coop-publisher-cli --help\n[coop-env ~ coop-tutorial] $ json-fs-store-cli --help\n```\n\nSince we're going to be running some services, it's useful to know which ports\nare used by which processes, for example:\n\n```sh\n[coop-env ~ coop-tutorial] $ netstat -ntuap | grep LISTEN | grep -E \"local-cluster|cardano-node|json-*|coop-*\"\n```\n\nAs we run the different commands in the tutorial Nix will continue to show the\nworking folder as:\n\n```sh\n[coop-env ~ coop-tutorial] $\n```\n\nWe can orient ourselves by looking for that prompt. If you find yourself in\nanother directory simply navigate back to `coop-tutorial` from the root of this\nrepository.\n\n#### 1. Preparing the environment\n\nA Nix environment is provided with all the tools necessary to run, operate and\nuse COOP.\n\nPrepare the directories and open a provided Nix environment:\n\n```sh\nmkdir coop-tutorial\ncd coop-tutorial\nnix develop github:mlabs-haskell/cardano-open-oracle-protocol#coop-env\n```\n\n\u003e NOTE:\n\u003e If you have downloaded this repository, and have created a tutorial directory\n\u003e outside of it, you can use a relative path to initialize the nix environment.\n\u003e\n\u003e Take for example, a tutorial folder sitting at the same level:\n\u003e\n\u003e ```sh\n\u003e mlabs/\n\u003e ├── coop-open-oracle-protocol\n\u003e └── coop-tutorial\n\u003e ```\n\u003e\n\u003e From within `coop-tutorial/` you can initialize nix as follows:\n\u003e `nix develop ../cardano-open-oracle-protocol#coop-env`.\n\nYou should be given the following prompt:\n\n```sh\n[coop-env ~ coop-tutorial] $\n```\n\nThe environment should now have the following tools available:\n\n- [cardano-node](https://github.com/input-output-hk/cardano-node#using-cardano-node) for running a Cardano network,\n- [cardano-cli](https://github.com/input-output-hk/cardano-node/tree/master/cardano-cli) for orchestrating a `cardano-node`, building, signing and submitting transactions,\n- [plutus-chain-index](https://github.com/input-output-hk/plutus-apps/tree/main/plutus-chain-index) for storing and indexing datums used by the [COOP Plutus protocol](coop-docs/02-plutus-protocol.md),\n- [local-cluster](https://github.com/mlabs-haskell/plutip/tree/master/local-cluster) for running a local/private Cardano network,\n- [coop-pab-cli](coop-pab) for initializing and operating the [COOP Plutus Protocol](coop-docs/02-plutus-protocol.md) and operating the [COOP TxBuilder gRPC](coop-proto/tx-builder-service.proto) service,\n- [coop-plutus-cli](coop-plutus) for providing serialized Plutus programs (ie. on-chain scripts) that implement the [COOP Plutus Protocol](coop-docs/02-plutus-protocol.md),\n- [coop-publisher-cli](coop-publisher) for running a [COOP Publisher gRPC](coop-proto/publisher-service.proto) service that implements the [COOP Frontend protocol](coop-docs/03-frontend-protocol.md),\n- [json-fs-store-cli](coop-extras/json-fact-statement-store) for running a generic JSON-based implementation of the [COOP FactStatementStore gRPC](coop-proto/fact-statement-store-service.proto) service\n- [plutus-json-cli](coop-extras/plutus-json) utility tool for converting between JSON and PlutusData formats\n\nand some other convenience utilities including some Bash functions that wrap the invocation of the bove-mentioned services and command line tools.\n\n#### 2. Environment variables and directories\n\nThroughout the tutorial, various environment variables will need to be set, and\nvarious directories will be created. The creation of environment variables and\ndirectories are shown in their respective contexts in the steps below. They are\nlisted for convenience below.\n\n```env\nCLUSTER_DIR=\".local-cluster\"\nWALLETS=\".wallets\"\nCOOP_PAB_DIR=\".coop-pab-cli\"\nJS_STORE_DIR=\".json-fs-store\"\nCOOP_PUBLISHER_DIR=\".coop-publisher-cli\"\n```\n\n```sh\nmkdir $WALLETS\nmkdir $COOP_PAB_DIR\nmkdir $JS_STORE_DIR\nmkdir $COOP_PUBLISHER_DIR\nmkdir $CLUSTER_DIR $CLUSTER_DIR/scripts $CLUSTER_DIR/txs\n```\n\n#### 3. Running a local Cardano network\n\nLet's first start by preparing and running a local Cardano network\nusing the `local-cluster` utility tool\nfrom [Plutip](https://github.com/mlabs-haskell/plutip) library.\n\nYou can use the provided `run-cluster` Bash function to run these commands for you\n(inspect the content with `type run-cluster`).\nYou should see the cluster running with instructions on how to stop it if necessary:\n\n```sh\nrun-cluster\n...\nCluster is running. Ctrl-C to stop.\n```\n\nThis creates the directories needed for the `local-cluster` to work and starts a Cardano network with 10 wallets (made available in the `$WALLETS` directory) that will be used in the Protocol.\n\nLet's leave the `local-cluster` process running in the foreground of the current shell and open a new `[coop-env ~ coop-tutorial]` shell session to continue with the tutorial.\n\nThe `local-cluster` created some wallets, let's assign them to environment variables that will be referenced throughout this tutorial:\n\n```sh\nmake-exports\nshow-env | grep PKH\n```\n\nThis will output confirmation of the variables and the stored public key hashes\n(PKH) of the wallets.\n\n```sh\ndeclare -x AA_PKH=\"319a165e8cb4c2c3eb898334ac3579eed75bcbb9a274f9ff259e74e3\"\ndeclare -x AUTH_PKH=\"46103a3b0671460efa35aee0f97c27d0a6b97bf59271663db7cd3d04\"\ndeclare -x CERT_RDMR_PKH=\"07c0bed25705dbaeb17ff53553035ddace3fa7a12ca75315ece8583b\"\ndeclare -x FEE_PKH=\"e6250649bed46e3b9343664f543e8ec3ba3eb01128be6b82a0491799\"\ndeclare -x GOD_PKH=\"c12aacc2604e89cd5dac1fb1e324ad552df1b18e2bd4230e8e15cfd5\"\ndeclare -x SUBMITTER_PKH=\"b7e59f40866e6ec88635343b9cc285043d344afbbe001ae645db0553\"\n```\n\nOutput shows some named wallets with their base16 public keys hash identifier. The `SUBMITTER_PKH` is the only wallet not used by the `COOP Publisher` that belongs to the user. We want to hide this wallet from the `local-cluster` to emulate a real-world (distributed) scenario where a third-party user will eventually sign the resulting COOP transaction:\n\n```sh\nmv $WALLETS/signing-key-\"$SUBMITTER_PKH\".skey $WALLETS/my-signing-key-\"$SUBMITTER_PKH\".skey\n```\n\n\u003e NOTE:\n\u003e If the `$SUBMITTER_PKH` signing key is not renamed, when the [Publisher gRPC](coop-proto/publisher-service.proto) service is invoked (below) it will use the key to automatically sign a COOP transaction and submit it to the Cardano Network. This can be useful in test-scenarios or when using COOP in an appropriately secured environment, purely as a publishing mechanism, i.e. in a centralized or federated model the COOP node signs and submits its own transactions. In this scenario the `FEE_PKH` can also be set to that of the `SUBMITTED_PKH` so that fees are automatically returned.\n\nAll other essential wallets are owned by the COOP Publisher and are used throughout its lifecycle. We'll revisit their role as we progress through the tutorial.\n\nThe `make-exports` and `show-env` are provided Bash functions that wrap the parsing of `local-cluster` information and set the appropriate environment variables.\n\n#### 4. Initializing the Protocol\n\nWe're ready now to perform the [COOP Plutus protocol genesis](coop-docs/02-plutus-protocol.md#protocol-genesis)\nusing the `coop-pab-cli` command line tool.\nWe prepare the working directory and run the CLI.\nYou can use Bash function `coop-genesis` to do the same:\n\n```sh\nexport COOP_PAB_DIR=.coop-pab-cli \u0026\u0026 mkdir $COOP_PAB_DIR\ncoop-pab-cli deploy --god-wallet $GOD_PKH --aa-wallet $AA_PKH\n```\n\nWe should see confirmation the command executed successfully.\n\n```sh\n[CONTRACT] [INFO [Any]] deployCoop: Finished\n```\n\nAt this point, a `$COOP_PAB_DIR/coop-deployment.json` file was created that contains all the Plutus scripts associated with the `COOP Publisher`.\n\n\u003e NOTE:\n\u003e The `coop-deployment.json` file is intended to be shared with the users of the Protocol to enable them to assert proper script addresses and token authenticity.\n\nThe [God wallet](coop-docs/02-plutus-protocol.md#god) can be discarded after the\n[Protocol Genesis](coop-docs/02-plutus-protocol.md#protocol-genesis) and the\n[Authentication Authority aka AA\nwallet](coop-docs/02-plutus-protocol.md#authentication-authority) takes the role\nas the root wallet of the Protocol that has the ability to issue new\n[Authentication tokens](coop-docs/02-plutus-protocol.md#auth-token) to\n[Authenticator wallets](coop-docs/02-plutus-protocol.md#authenticator). More on\nthat later...\n\n\u003e NOTE:\n\u003e The [Authentication Authority](coop-docs/02-plutus-protocol.md#authentication-authority) wallets MUST be kept safe as their compromise impacts the integrity of the entire system. Trust in a particular COOP Publisher eventually reduces to this wallet.\n\nContinuing, we should be able to already inspect the state of the Protocol by using a provided `coop-get-state` bash function:\n\n```sh\ncoop-get-state\n```\n\nWe'll see the command runs successfully with JSON output following that.\n\n```sh\ngetState: Success\n```\n\n```json\n{\n  \"cs'certificates\": [],\n  \"cs'currentTime\": [\n    {\n      \"getPOSIXTime\": 1668599399000\n    },\n    {\n      \"getPOSIXTime\": 1668599400000\n    }\n  ],\n  \"cs'factStatements\": []\n}\n```\n\nAs we can see there's currently nothing of interest there.\nThe `cs'certificates` list contains [Certificates](coop-docs/02-plutus-protocol.md#cert-validator)\navailable in the Protocol,\nand the `cs'factStatements` contains a list of all the published\n[Fact Statements](coop-docs/02-plutus-protocol.md#fs-validator).\n`cs'currentTime` is included for convenience to observe the on-chain time.\n\nNow, it's time to issue [Authentication\ntokens](coop-docs/02-plutus-protocol.md#auth-token) to [Authenticator\nwallets](coop-docs/02-plutus-protocol.md#authenticator)\n(you can use Bash script `coop-mint-cert-redeemers`):\n\n```sh\ncoop-pab-cli mint-cert-redeemers \\\n --cert-rdmr-wallet $CERT_RDMR_PKH \\\n --cert-rdmrs-to-mint 100\n```\n\nWhich should show that the certificate redeemer tokens were successfully minted:\n\n```sh\nCONTRACT] [INFO [Any]] mintCertR: Finished\nmintCertRdmrs: Minted $CERT-RDMR tokens with AssetClass\n```\n\nWe will now mint the certificate `$CERT` and authentication `$AUTH` tokens\n(Bash function `coop-mint-authentication`):\n\n```sh\nNOW=$(get-onchain-time) \u0026\u0026 coop-pab-cli mint-auth \\\n --aa-wallet $AA_PKH \\\n --certificate-valid-from $NOW \\\n --certificate-valid-to \"$(expr $NOW + 60 \\* 60 \\* 1000)\" \\\n --auth-wallet $AUTH_PKH\n```\n\nWhich should also be successfully minted.\n\n```sh\nmintAuth: Minted $CERT\nmintAuth: Minted $AUTH\n```\n\n\u003e NOTE:\n\u003e Validity is measured in milliseconds (ms). `60 * 60 * 1000` is `3600000 ms`\n\u003e which means our certificates in this example are valid for one hour.\n\nThe `coop-pab-cli mint-cert-redeemers` issues [Certificate redeemer\ntokens](coop-docs/02-plutus-protocol.md#cert-rdmr-token) to a special wallet\nthat will be used in `coop-pab-cli garbage-collect` command to 'garbage collect'\nobsolete [Certificates](coop-docs/02-plutus-protocol.md#cert-validator) and is a\nprerequisite to `coop-pab-cli mint-auth` transaction. These tokens are never\ndepleted.\n\nThe `coop-pab-cli mint-auth` is the most involved command in the protocol, it's\nintended to be used by the COOP Publisher operator on a regular basis to issue\nnew 'ephemeral' [Authentication\ntokens](coop-docs/02-plutus-protocol.md#auth-token) that are used to\nauthenticate the publishing of each new Fact Statement. Once depleted, they have to\nbe replenished with this command and it's up to the Operator to manage when and\nhow many are issued, a decision based on considering the security exposure of\nthe [Authenticator wallets](coop-docs/02-plutus-protocol.md#authenticator) and\nthe publishing request load.\n\nThe command takes in the [Authentication Authority\nwallet](coop-docs/02-plutus-protocol.md#authentication-authority) that\nauthorizes the issuance of new authentication tokens to an [Authenticator\nwallet](coop-docs/02-plutus-protocol.md#authenticator), setting the certificate\nvalidity to 1 HOUR from 'now', after which this authentication batch, meaning\nboth [Certificates](coop-docs/02-plutus-protocol.md#cert-validator) and\nassociated [Authentication tokens](coop-docs/02-plutus-protocol.md#auth-token)\nbecome invalid and can be discarded.\n\n\u003e NOTE:\n\u003e Authentication tokens that are associated with an expired Certificate cannot be used in the Protocol.\n\nSince all the [Authentication\ntokens](coop-docs/02-plutus-protocol.md#auth-token) are sent in a batch to a\nsingle UTxO held by the [Authenticator\nwallets](coop-docs/02-plutus-protocol.md#authenticator) we provide a convenience\nutility to redistribute these tokens in separate UTxOs (Bash function `coop-redist-auth`):\n\n```sh\ncoop-pab-cli redistribute-auth --auth-wallet $AUTH_PKH\n```\n\nThis will output the following:\n\n```sh\nredistributeAuth: Redistributed outputs for Authenticator\n```\n\n[Authentication tokens](coop-docs/02-plutus-protocol.md#auth-token) are spend by\n[Fact Statement Publishing\ntransactions](coop-docs/02-plutus-protocol.md#mint-fact-statement-tx) to denote\nthe 'authenticity' of the information provided in produced [Fact Statement\nUTxOs](coop-docs/02-plutus-protocol.md#fs-validator). They are also associated\nwith a [Certificate](coop-docs/02-plutus-protocol.md#cert-validator) that\nprovides information on the time validity of [Authentication\ntokens](coop-docs/02-plutus-protocol.md#auth-token) used in a [Fact Statement\nPublishing\ntransactions](coop-docs/02-plutus-protocol.md#mint-fact-statement-tx).\n\n\u003e NOTE:\n\u003e Authenticator wallets are so called 'hot-wallets' used when servicing Fact Statement Publishing requests, as such the Protocol designed a mitigation using [Certificates](coop-docs/02-plutus-protocol.md#cert-validator) that limit the impact a compromised [Authenticator wallet](coop-docs/02-plutus-protocol.md#authenticator) can have on the integrity of the Protocol.\n\nBefore we proceed, let's check in on the state of our Protocol now that we actually introduced our first action:\n\n```sh\ncoop-get-state\n```\n\nState will now look as follows:\n\n```sh\ngetState: Success\n```\n\n```json\n{\n   \"cs'certificates\" : [\n      {\n         \"cert'id\" : \"7279672bf427c10d43492f41ab3af02a8bcb97d9777539fc5eae0b108850c3ce\",\n         \"cert'redeemerAc\" : {\n            \"unAssetClass\" : [\n               {\n                  \"unCurrencySymbol\" : \"6b14c29615e356edfce1eeb652b703daa7c246bd52fa8d87c17aafaf\"\n               },\n               \"c639e2f8b64d6a0bdf1d48de48d832c57342e7980d6a4e98df92ef8c2c54ce75\"\n            ]\n         },\n         \"cert'validity\" : {\n            \"ivFrom\" : [\n               {\n                  \"contents\" : {\n                     \"getPOSIXTime\" : 1668599835000\n                  },\n                  \"tag\" : \"Finite\"\n               },\n               true\n            ],\n            \"ivTo\" : [\n               {\n                  \"contents\" : {\n                     \"getPOSIXTime\" : 1668603435000\n                  },\n                  \"tag\" : \"Finite\"\n               },\n               true\n            ]\n         }\n      }\n   ],\n   \"cs'currentTime\" : [\n      {\n         \"getPOSIXTime\" : 1668601830000\n      },\n      {\n         \"getPOSIXTime\" : 1668601831000\n      }\n   ],\n   \"cs'factStatements\" : []\n}\n```\n\nAs we can see a new [Certificate](coop-docs/02-plutus-protocol.md#cert-validator) has been successfully issued.\n\n#### 5. Running a TxBuilder gRPC service\n\nWe're finally ready to run the first COOP service, namely the [TxBuilder\ngRPC](coop-proto/tx-builder-service.proto) back-end service that has the\nresponsibility of building the COOP Cardano transactions:\n\nThe provided `generate-keys` Bash function will initialize the TLS keys and\ncertificates used by the gRPC service. The service needs access to\n[Authenticator wallets](coop-docs/02-plutus-protocol.md#authenticator) as it\nprovides signatures for the transactions, and a [Fee\nwallet](coop-docs/02-plutus-protocol.md#fee-collector) to send the service fees\nto.\n\n```sh\ngenerate-keys $COOP_PAB_DIR\n```\n\nNow we are ready to run the service (use Bash function `coop-run-tx-builder-grpc`):\n\n```sh\ncoop-pab-cli tx-builder-grpc --auth-wallet $AUTH_PKH --fee-wallet $FEE_PKH\n```\n\n\u003e NOTE:\n\u003e A [Fee wallet](coop-docs/02-plutus-protocol.md#fee-collector) is where the COOP Publisher receives the fees after a successful [Fact Statement Publishing](coop-docs/03-frontend-protocol.md).\n\nYou can inspect and interact with the service using the gRPC utilities provided\nin the environment ([grpcurl](https://github.com/fullstorydev/grpcurl) and\n[grpcui](https://github.com/fullstorydev/grpcui)).\n\nLet's leave the `tx-builder-grpc` process running in the foreground of the\ncurrent shell and open a new `[coop-env ~ coop-tutorial]` shell session to\ncontinue with the tutorial.\n\n#### 6. Running a FactStatementStore gRPC service\n\n\u003e You can use Bash function `run-js-fs-store` to execute commands\n\u003e described in this section.\n\nCOOP provides a low-scale implementation of the [FactStatementStore\ngRPC](coop-proto/fact-statement-store-service.proto) back-end service, namely\nthe [JSON Fact Statement Store](coop-extras/json-fact-statement-store) that, as\nthe name suggests, enables COOP Publisher operators to conveniently maintain a\nstore of JSON encoded Fact Statements that users can refer to and eventually\npublish.\n\nFirst, let's prepare and initialize the service:\n\n```sh\nexport JS_STORE_DIR=.json-fs-store\nmkdir $JS_STORE_DIR\nsqlite3 -batch $JS_STORE_DIR/json-store.db \"\"\njson-fs-store-cli genesis --db $JS_STORE_DIR/json-store.db\ngenerate-keys $JS_STORE_DIR\n```\n\nLet's also add some actual Fact Statements into the store, while we're here:\n\n```sh\njson-fs-store-cli insert-fact-statement --db $JS_STORE_DIR/json-store.db \\\n --fact_statement_id \"id1\" \\\n --json '[\"apples\", \"oranges\", \"pears\"]'\n```\n\n```sh\njson-fs-store-cli insert-fact-statement --db $JS_STORE_DIR/json-store.db \\\n --fact_statement_id \"id2\" \\\n --json '{\"name\": \"Drazen Popovic\", \"age\": 35}'\n```\n\n```sh\njson-fs-store-cli insert-fact-statement --db $JS_STORE_DIR/json-store.db \\\n --fact_statement_id \"id3\" \\\n --json '\"Lorem ipsum\"'\n```\n\nTake a look at the values written by inspecting the fact statement store:\n\n```sh\necho \"SELECT * FROM fact_statements\" | sqlite3 $JS_STORE_DIR/json-store.db\n```\n\nYou expectedly should see:\n\n```text\nid1|[\"apples\", \"oranges\", \"pears\"]\nid2|{\"name\": \"Drazen Popovic\", \"age\": 35}\nid3|\"Lorem ipsum\"\n```\n\nNow we simply start the service:\n\n```sh\njson-fs-store-cli fact-statement-store-grpc --db $JS_STORE_DIR/json-store.db\n```\n\nYou can inspect and interact with the service using the gRPC utilities provided\nin the environment ([grpcurl](https://github.com/fullstorydev/grpcurl) and\n[grpcui](https://github.com/fullstorydev/grpcui)).\n\nLet's leave the `fact-statement-store-grpc` process running in the foreground of\nthe current shell and open a new `[coop-env ~ coop-tutorial]` shell session to\ncontinue with the tutorial. We're almost there!\n\n#### 7. Running a Publisher gRPC service\n\nThe [Publisher gRPC](coop-proto/publisher-service.proto) is the principal\nfronted service that COOP users interact with as described in the [COOP Frontend\nprotocol](coop-docs/03-frontend-protocol.md). This service relies on the\nback-end services that we've already set up, namely the [TxBuilder\ngRPC](coop-proto/tx-builder-service.proto) service and the [FactStatementStore\ngRPC](coop-proto/fact-statement-store-service.proto) service.\n\nIt's straightforward to run (you can use Bash function `run-publisher`):\n\n```sh\nexport COOP_PUBLISHER_DIR=.coop-publisher-cli\ngenerate-keys $COOP_PUBLISHER_DIR\ncoop-publisher-cli publisher-grpc\n```\n\nThe default command line arguments are sufficient for our scenario.\n\nYou can inspect and interact with the service using the gRPC utilities provided\nin the environment ([grpcurl](https://github.com/fullstorydev/grpcurl) and\n[grpcui](https://github.com/fullstorydev/grpcui)).\n\nLet's leave the `publisher-grpc` process running in the foreground of the\ncurrent shell and open a new `[coop-env ~ coop-tutorial]` shell session to\ncontinue with the tutorial. That's the last shell I promise, now we get to\nfinally publish some fact statements.\n\n#### 8. Publishing a Fact Statement\n\nWith the COOP Publisher fully set up, we're ready to have our users publish some\nFact Statements (See[Publishing a Fact\nStatement](coop-docs/03-frontend-protocol.md#publishing-a-fact-statement)).\n\nThe users find out the Fact Statement Identifiers in a way not prescribed by COOP.\nThe Oracle provides some kind of access to their Fact Statement Store, for\nexample by an additional API with some search features, or even allows users to\nrequest a new Fact Statement to be collected/computed and inserted into the\nstore. Regardless, the users must approach the [COOP\nPublisher](coop-proto/publisher-service.proto) with a `Fact Statement\nIdentifier` that the back-end can eventually retrieve from the `Fact Statement\nStore`.\n\n\u003e NOTE:\n\u003e COOP Publisher works with Fact Statements available in some Oracle's Fact Statement Store.\n\u003e Each Fact Statement in a store should get their own unique identifier, but this responsibility falls under a concrete Fact Statement Store operator.\n\nWith that, we already know there are 3 Fact Statements in our Fact Statement\nStore we've set up, namely `id1`, `id2`, and `id3`. Let's publish all three of\nthese Fact Statements:\n\n```sh\n[coop-env ~ coop-tutorial] $ REQ=$(cat \u003c\u003cEOF\n    {\n        \"fsInfos\": [\n            {\n                \"fsId\": \"$(echo -ne id1 | base64)\",\n                \"gcAfter\": {\n                    \"extended\": \"NEG_INF\"\n                }\n            },\n            {\n                \"fsId\": \"$(echo -ne id2 | base64)\",\n                \"gcAfter\": {\n                    \"extended\": \"NEG_INF\"\n                }\n            },\n            {\n                \"fsId\": \"$(echo -ne id3 | base64)\",\n                \"gcAfter\": {\n                    \"extended\": \"NEG_INF\"\n                }\n            }\n        ],\n        \"submitter\": {\n            \"base16\": \"$SUBMITTER_PKH\"\n        }\n    }\nEOF\n)\n```\n\nThis prepares a request to be issued with [grpcurl](https://github.com/fullstorydev/grpcurl).\nThe request lists all the Fact Statement Identifiers we wish to publish, along\nwith their desired validity time (after which they can be 'garbage collected').\nIn this particular case, we've set the time to `NEG_INF` meaning we can garbage\ncollect it at any time after publishing.\n\n\u003e NOTE:\n\u003e Users can specify validity time for the [Fact Statement UTxOs](coop-docs/02-plutus-protocol.md#fs-validator) they created and adjust it to the needs of the dApps they are referenced with.\n\u003e Some Fact Statements are going to be short lived, and some long lived, that largely depends on how the Fact Statement is used by a Cardano dApp.\n\u003e Protocol enables Submitters to 'garbage collect' obsolete [Fact Statement UTxOs](coop-docs/02-plutus-protocol.md#fs-validator) and reclaim the [Min UTxO Ada](https://docs.cardano.org/native-tokens/minimum-ada-value-requirement) held within.\n\u003e\n\u003e Validity time is specified using a [Unix timestamp](https://www.unixtimestamp.com/) in milliseconds. When interacting with the GRPC service ensure that your tooling can convert its native timestamps to milliseconds.\n\nLet's issue a request against the [Publisher gRPC](coop-proto/publisher-service.proto) service:\n\n```sh\nRESP=$(echo $REQ | grpcurl -insecure -import-path $COOP_PROTO \\\n -proto $COOP_PROTO/publisher-service.proto -d @ \\\n localhost:5080 coop.publisher.Publisher/createMintFsTx)\n```\n\nAnd inspect the response:\n\n```sh                            )\necho \"$RESP\" | jq '.info'\n```\n\nThe `jq` result snippet will show the base64 encoded hashes of the fact-store\nstatement IDs:\n\n```json\n{\n  \"txBuilderInfo\": {\n    \"publishedFsIds\": [\n      \"aWQx\",\n      \"aWQy\",\n      \"aWQz\"\n    ]\n  }\n}\n```\n\nWith no errors:\n\n```sh\necho \"$RESP\" | jq '.error'\n```\n\nWe should see the result as:\n\n```sh\nnull\n```\n\nThe [Publisher gRPC](coop-proto/publisher-service.proto) service successfully\nserviced the request and returned a CBOR-encoded Cardano transaction in the\n`mintFsTx` field of the response. Let's format the transaction so\n[cardano-cli](https://github.com/input-output-hk/cardano-node/tree/master/cardano-cli)\ncan understand it:\n\n```sh\necho \"$RESP\" | jq '.mintFsTx | .cborHex = .cborBase16 | del(.cborBase16) | .description = \"\" | .type = \"Tx BabbageEra\"' \\\n \u003e transaction-to-sign.json\n```\n\n\u003e NOTE:\n\u003e Any Cardano wallet could be used as COOP provides a raw CBOR encoded transaction, we just used [cardano-cli](https://github.com/input-output-hk/cardano-node/tree/master/cardano-cli) for convenience to demonstrate the concept.\n\nFinally, we can sign the transaction:\n\n```sh\ncardano-cli transaction sign \\\n --tx-file transaction-to-sign.json \\\n --signing-key-file $WALLETS/my-signing-key-\"$SUBMITTER_PKH\".skey \\\n --out-file transaction-to-submit.json\n```\n\nAnd submit it:\n\n```sh\ncardano-cli transaction submit \\\n --tx-file transaction-to-submit.json  --mainnet\n```\n\nAll being well, the command should be successful:\n\n```sh\nTransaction successfully submitted.\n```\n\nThe transaction was successfully submitted which means we should be able to see that reflected in the state of the Protocol:\n\n```sh\ncoop-get-state \u0026\u0026 jq \".[\\\"cs'factStatements\\\"]\" $COOP_PAB_DIR/coop-state.json\n```\n\n```json\n[\n  {\n    \"fd'fs\": {\n      \"contents\": [\n        {\n          \"contents\": \"6170706c6573\",\n          \"tag\": \"B\"\n        },\n        {\n          \"contents\": \"6f72616e676573\",\n          \"tag\": \"B\"\n        },\n        {\n          \"contents\": \"7065617273\",\n          \"tag\": \"B\"\n        }\n      ],\n      \"tag\": \"List\"\n    },\n    \"fd'fsId\": \"696431\",\n    \"fs'gcAfter\": {\n      \"tag\": \"NegInf\"\n    },\n    \"fs'submitter\": {\n      \"getPubKeyHash\": \"51bf399017a57f8873bf155f818feca7384c4618e5e6367450582325\"\n    }\n  },\n  {\n    \"fd'fs\": {\n      \"contents\": [\n        [\n          {\n            \"contents\": \"616765\",\n            \"tag\": \"B\"\n          },\n          {\n            \"contents\": 35,\n            \"tag\": \"I\"\n          }\n        ],\n        [\n          {\n            \"contents\": \"6e616d65\",\n            \"tag\": \"B\"\n          },\n          {\n            \"contents\": \"4472617a656e20506f706f766963\",\n            \"tag\": \"B\"\n          }\n        ]\n      ],\n      \"tag\": \"Map\"\n    },\n    \"fd'fsId\": \"696432\",\n    \"fs'gcAfter\": {\n      \"tag\": \"NegInf\"\n    },\n    \"fs'submitter\": {\n      \"getPubKeyHash\": \"51bf399017a57f8873bf155f818feca7384c4618e5e6367450582325\"\n    }\n  },\n  {\n    \"fd'fs\": {\n      \"contents\": \"4c6f72656d20697073756d\",\n      \"tag\": \"B\"\n    },\n    \"fd'fsId\": \"696433\",\n    \"fs'gcAfter\": {\n      \"tag\": \"NegInf\"\n    },\n    \"fs'submitter\": {\n      \"getPubKeyHash\": \"51bf399017a57f8873bf155f818feca7384c4618e5e6367450582325\"\n    }\n  }\n]\n```\n\nIndeed, all the Fact Statements have been successfully published and can be used\nby any dApp by simply referencing the desired [Fact Statement\nUTxOs](coop-docs/02-plutus-protocol.md#fs-validator). You can see the\n`fs'submitter` field set to the `SUBMITTER_PKH` which is important for when the\nSubmitter decides to garbage collect the obsolete [Fact Statement\nUTxOs](coop-docs/02-plutus-protocol.md#fs-validator). With that said, let's try\nand do exactly that...\n\n#### 9. Garbage collecting obsolete Fact Statement UTxOs\n\nThe `fs'gcAfter` field of the [Fact Statement\nUTxO](coop-docs/02-plutus-protocol.md#fs-validator) datums denotes when that\nUTxO can be spent by the Submitter (denoted in `fs'submitter` field) that\ncreated that UTxO.\n\n\u003e NOTE:\n\u003e The `fs'gcAfter` validity time is a property of the Fact Statement UTxO, not the Fact Statement itself.\n\u003e It's merely used to enable Submitters manage reclaiming the [Min UTxO Ada](https://docs.cardano.org/native-tokens/minimum-ada-value-requirement) they had to pay for each [Fact Statement UTxO](coop-docs/02-plutus-protocol.md#fs-validator) they created.\n\nSince, for the purpose of this tutorial, we've created the [Fact Statement\nUTxOs](coop-docs/02-plutus-protocol.md#fs-validator) that are considered\n'immediately obsolete', we can proceed and garbage collect them, and thus\nreclaim the [Min UTxO\nAda](https://docs.cardano.org/native-tokens/minimum-ada-value-requirement)\namount locked within.\n\n```sh\nREQ=$(cat \u003c\u003cEOF\n    {\n        \"fsIds\": [\n                 \"$(echo -ne 'id1' | base64)\",\n                 \"$(echo -ne 'id2' | base64)\",\n                 \"$(echo -ne 'id3' | base64)\"\n                 ],\n        \"submitter\": {\n            \"base16\": \"$SUBMITTER_PKH\"\n        }\n    }\nEOF\n)\n```\n\n```sh\nRESP=$(echo $REQ \\\n | grpcurl -insecure -import-path $COOP_PROTO \\\n -proto $COOP_PROTO/publisher-service.proto -d @ \\\n localhost:5080 coop.publisher.Publisher/createGcFsTx)\n```\n\nInspect the result with:\n\n```sh\necho \"$RESP\" | jq '.info'\n```\n\nWe should see the base64 encoded fact-store statement IDs as before are now\nobsolete:\n\n```json\n{\n  \"txBuilderInfo\": {\n    \"obsoleteFsIds\": [\n      \"aWQx\",\n      \"aWQy\",\n      \"aWQz\"\n    ]\n  }\n}\n```\n\nThe [Publisher gRPC](coop-proto/publisher-service.proto) service successfully\nserviced the request and returned a CBOR-encoded Cardano transaction in the\n`gcFsTx` field of the response. Let's format the transaction so\n[cardano-cli](https://github.com/input-output-hk/cardano-node/tree/master/cardano-cli)\ncan understand it, then sign and submit it:\n\n```sh\necho \"$RESP\" | jq '.gcFsTx | .cborHex = .cborBase16 | del(.cborBase16) | .description = \"\" | .type = \"TxBodyBabbage\"' \u003e transaction-to-sign.json\ncardano-cli transaction sign \\\n --tx-body-file transaction-to-sign.json \\\n --signing-key-file $WALLETS/my-signing-key-\"$SUBMITTER_PKH\".skey \\\n --out-file transaction-to-submit.json\ncardano-cli transaction submit \\\n --tx-file transaction-to-submit.json  --mainnet\n```\n\nThe transaction should be successful:\n\n```sh\nTransaction successfully submitted.\n```\n\nGreat! Let's check the state of the Protocol now...\n\n```sh\ncoop-get-state \u0026\u0026 jq \".[\\\"cs'factStatements\\\"]\" $COOP_PAB_DIR/coop-state.json\n```\n\nWe should see an empty list:\n\n```sh\n[]\n```\n\nAs expected, there are no more Fact Statements available in the system.\n\n#### 10. Garbage collecting obsolete Certificate UTxOs\n\nThe COOP Publisher operators can also manage the reclaiming of [Min UTxO\nAda](https://docs.cardano.org/native-tokens/minimum-ada-value-requirement) they\nhad to pay for each [Certificate\nUTxO](coop-docs/02-plutus-protocol.md#cert-validator) they created when issuing\nnew [Authentication tokens](coop-docs/02-plutus-protocol.md#auth-token) with\n`coop-pab-cli mint-auth`.\n\nAgain, inspecting the state with `coop-get-state` we see there's an obsolete\n[Certificate UTxO](coop-docs/02-plutus-protocol.md#cert-validator) that can be\ngarbage collected.\n\n```sh\ncoop-get-state\n```\n\nWe should see the state looks as follows:\n\n```sh\ngetState: Success\n```\n\n```json\n{\n   \"cs'certificates\" : [\n      {\n         \"cert'id\" : \"7279672bf427c10d43492f41ab3af02a8bcb97d9777539fc5eae0b108850c3ce\",\n         \"cert'redeemerAc\" : {\n            \"unAssetClass\" : [\n               {\n                  \"unCurrencySymbol\" : \"6b14c29615e356edfce1eeb652b703daa7c246bd52fa8d87c17aafaf\"\n               },\n               \"c639e2f8b64d6a0bdf1d48de48d832c57342e7980d6a4e98df92ef8c2c54ce75\"\n            ]\n         },\n         \"cert'validity\" : {\n            \"ivFrom\" : [\n               {\n                  \"contents\" : {\n                     \"getPOSIXTime\" : 1668599835000\n                  },\n                  \"tag\" : \"Finite\"\n               },\n               true\n            ],\n            \"ivTo\" : [\n               {\n                  \"contents\" : {\n                     \"getPOSIXTime\" : 1668603435000\n                  },\n                  \"tag\" : \"Finite\"\n               },\n               true\n            ]\n         }\n      }\n   ],\n   \"cs'currentTime\" : [\n      {\n         \"getPOSIXTime\" : 1668601830000\n      },\n      {\n         \"getPOSIXTime\" : 1668601831000\n      }\n   ],\n   \"cs'factStatements\" : []\n}\n```\n\nLet's garbage collect it then...\n\n```sh\ncoop-pab-cli garbage-collect --cert-rdmr-wallet $CERT_RDMR_PKH\n```\n\nGarbage collection should complete successfully:\n\n```sh\n[CONTRACT] [INFO [Any]] burnCerts: Finished\ngarbageCollect: Collected $CERT UTxOs from @CertV using $CERT-RDMR tokens\n```\n\nThis is where [Certificate redeemer wallets](coop-docs/02-plutus-protocol.md#certificate-redeemer) come into play as they hold the tokens that the verifying Plutus script checks when validating the consumption of its outputs.\n\n```sh\ncoop-get-state\n```\n\nResults in:\n\n```sh\ngetState: Success\n```\n\n```json\n{\n   \"cs'certificates\" : [ ],\n   \"cs'currentTime\" : [\n      {\n         \"getPOSIXTime\" : 1668608405000\n      },\n      {\n         \"getPOSIXTime\" : 1668608406000\n      }\n   ],\n   \"cs'factStatements\" : []\n}\n```\n\nAnd we've made the full circle :)\n\n#### 11. Referencing published Fact Statement in Consumer dApps\n\nThe COOP Publisher must announce the deployment file created after [COOP Plutus\nprotocol genesis](coop-docs/02-plutus-protocol.md#protocol-genesis). This file\ncontains the [Fact Statement minting\npolicy](coop-docs/02-plutus-protocol.md#fs-policy) script which is the `Currency\nSymbol` the consuming dApps use to assert the authenticity and provenance of the\nreferenced [Fact Statement UTxOs](coop-docs/02-plutus-protocol.md#fs-validator).\n\nAn example Consumer [validator script](https://github.com/mlabs-haskell/cardano-open-oracle-protocol/blob/dbc2182ab2e8d2ce6053ef9ba7ac9afbaaa2c5cd/coop-plutus/src/Coop/Plutus.hs#L594-L607)\nwas provided to demonstrate how to authenticate [Fact Statement\nUTxOs](coop-docs/02-plutus-protocol.md#fs-validator) on-chain. The script\nperforms a simple assertion on the\n[Value](https://github.com/input-output-hk/plutus/blob/c3918d6027a9a34b6f72a6e4c7bf2e5350e6467e/plutus-ledger-api/src/PlutusLedgerApi/V1/Value.hs#L185)\nof a referenced UTxO to make sure it contains a\n[CurrencySymbol](https://github.com/input-output-hk/plutus/blob/c3918d6027a9a34b6f72a6e4c7bf2e5350e6467e/plutus-ledger-api/src/PlutusLedgerApi/V1/Value.hs#L79)\nof the [$FS](coop-docs/02-plutus-protocol.md#fs-token) tokens it trusts\n\nThe second part of the script demonstrates how to parse a [Plutus\nJSON](coop-docs/05-json-plutus.md) [Fact\nStatement](coop-plutus/resources/sample.json).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmlabs-haskell%2Fcardano-open-oracle-protocol","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmlabs-haskell%2Fcardano-open-oracle-protocol","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmlabs-haskell%2Fcardano-open-oracle-protocol/lists"}