diff --git a/ReadMe.md b/ReadMe.md index 410ebd2..c6ef190 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -30,6 +30,7 @@ Lesson | Duration | Prerequsites | Video [7 - Checking the safety of a Marlowe contract](lessons/07-safety/) | 10 mins | [Preliminaries](setup/00-local-environment.ipynb) | [8 - Experimental web application using a CIP-45 wallet](lessons/08-cip45/) | 10 mins | [Preliminaries](setup/00-local-environment.ipynb) | [video](https://youtu.be/3cR8tq0WE_8) [9 - Minting with Marlowe Tools](lessons/09-minting/) | 25 mins | [Preliminaries](setup/00-local-environment.ipynb) | [video](https://youtu.be/S0MOipqXpmQ) +[10 - Open Roles](lessons/10-open-roles/) | 27 mins | [Preliminaries](setup/00-local-environment.ipynb) | [video](https://drive.google.com/file/d/1g17LKLJ4cyizfUJDrM44ChaNb6otOce0/view?usp=sharing) ## Under the Hood diff --git a/lessons/10-open-roles/10-open-roles.ipynb b/lessons/10-open-roles/10-open-roles.ipynb new file mode 100644 index 0000000..398888a --- /dev/null +++ b/lessons/10-open-roles/10-open-roles.ipynb @@ -0,0 +1,1401 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "64f34ed8-9ab2-44ab-b27f-bfe57cb35b91", + "metadata": {}, + "source": [ + "# Open-Roles Token Swap Using Marlowe Runtime\\'s REST API\n", + "\n", + "***Before running this notebook, you might want to use Jupyter's \"clear output\" function to erase the results of the previous execution of this notebook. That will make more apparent what has been executed in the current session.***\n", + "\n", + "The token-swap contract example is a simple Marlowe contract that lets parties trade ada for a token. This example differs from Lesson 5 in that is employs an *open role* that allows *anyone* to become the buyer party in the contract. Namely, the identity of the buyer is unknown when the contract is instantiated on the blockchain. Only later does someone decide to participate as the buyer, at which time they receive the role token for the \"buyer\" party.\n", + "\n", + "[A video works through this Jupyter notebook.](https://drive.google.com/file/d/1g17LKLJ4cyizfUJDrM44ChaNb6otOce0/view?usp=sharing)\n", + "\n", + "You can ask questions about Marlowe in [the #ask-marlowe channel on the IOG Discord](https://discord.com/channels/826816523368005654/936295815926927390) or post problems with this lesson to [the issues list for the Marlowe Starter Kit github repository](https://github.com/input-output-hk/marlowe-starter-kit/issues).\n", + "\n", + "In this demonsration we use Marlowe Runtime\\'s REST API, served via `marlowe-web-server`, to run this contract on Cardano\\'s `preprod` public testnet. Marlowe contracts may use either addresses or role tokens for authorization: here we use role tokens and we have Marlowe Runtime mint them.\n", + "\n", + "## Overview of open roles in Marlowe\n", + "\n", + "Although all the roles in a Marlowe contract must be specified when the contract is instantiated on the block-chain, the contract's role tokens need not be distributed to payment addresses at that time. Some roles can be left \"open\" for later assignment and distribution by sending them to a Plutus script called the open-roles validator when they are initially minted. That Plutus script holds the role token until a party makes an `IDeposit` or `IChoice` action as input to the contract; as they perform that action in a transaction, the open-role validator releases the role token to their wallet. This allows parties not identified by address at the time of the contract's instantiation to later participate in the contract.\n", + "\n", + "### Details\n", + "\n", + "One or more open roles can be included in a Marlowe contract by specifying them when Marlowe Runtime builds the transaction for instantiating the contract.\n", + "\n", + "- The creation transaction for the Marlowe contract does the following:\n", + " - Places the thread token in the contract's UTXO.\n", + " - Creates a UTXO for the open-roles validator.\n", + " - The `Datum` references the thread token that is placed in the contract's UTXO.\n", + " - The `Value` contains the open-role token.\n", + "- The open-role assignment transaction does the following:\n", + " - Transfers the open-role token to the change address for the transaction.\n", + " - Verifies the validity of the transaction.\n", + " - The open-role validator ensures that `IDeposit` and/or `IChoice` actions for the open role are present in the input.\n", + " - The open-role validator ensures that the contract's UTXO contains the required thread token.\n", + " - The Marlowe semantics validator does its usual validation, including checking that the relevant inputs are authorized by the open-role token.\n", + "\n", + "![Transactions involved in an open-roles Marlowe contract](open-roles-interaction.png)\n", + "\n", + "\n", + "#### Minting open roles\n", + "\n", + "If role tokens are minted when the contract is created, specify the *thread role* required by the open-role validator and each *open role*. The example below has a thread role name `Thread`, and open role named `Buyer`, and a standard role named `Seller`. In the example below, the creation transaction will mint and send the thread role `Thread` to the Marlowe contract, the open role `Buyer` to the open-roles validator, and the role `Seller` to the seller's wallet address.\n", + "\n", + "```yaml\n", + "roles:\n", + " Thread:\n", + " script: ThreadRole\n", + " Buyer:\n", + " script: OpenRole\n", + " Seller: addr_test1vq9prvx8ufwutkwxx9cmmuuajaqmjqwujqlp9d8pvg6gupczgtm9j\n", + "```\n", + "\n", + "#### Pre-minted open roles\n", + "\n", + "If role tokens have already been minted and are held in the wallet of the contract's creator, then specify the policy ID, the name of the thread token, and the names of the open-role tokens. In the example below, the creation transaction will send the thread role token `Thread` to the Marlowe contract and the open role `Buyer` to the open-roles validator. Any standard role tokens will have to be distributed to parties manually.\n", + "\n", + "```yaml\n", + "roles:\n", + " script: OpenRole\n", + " policyId: ac5591a09baca1e04b5b2f086210cd1a9451032e02c1a327d13ee767\n", + " threadRoleName: Thread\n", + " openRoleNames:\n", + " - Buyer\n", + "```\n", + "\n", + "### Distribution of open-role tokens\n", + "\n", + "The open-role validator authorizes the distribution of the open-role token that it holds to anyone who performs the contract's first action that takes the open role as input. After they have received the open-role token, they can use it as a role token for any future inputs to the contract or for any withdrawals from the role-payout validator address.\n", + "\n", + "Furthermore, the open-role validator will only validate if the Marlowe contract holds the thread token specified when the open-role validator is created. This ties the open role to a particular Marlowe contract.\n", + "\n", + "\n", + "### Security restriction for open roles\n", + "\n", + "Marlowe's prevention of double-satisfaction attacks requires that no external payments be made from a Marlowe contract if another Plutus script runs in the transaction. Thus, if an open-role is distributed in a transaction, the transaction cannot do `Pay` to parties or implicit payments upon `Close`. Typically, the distribution of an open-role in a Marlowe contract will be followed by a `Notify TrueObs` case so that further execution of the contract does not proceed in that transactions. External payments can be made in subsequent transactions.\n", + "\n", + "\n", + "## Example\n", + "\n", + "The open-roles validator lets *anyone with a Cardano address* make a deposit or choice input for a contract that uses open roles. A simple use case for open roles is when a seller might offer a token for sale to anyone who pays the sales price.\n", + "\n", + "1. The seller creates a contract that offers one `BearGarden` token for sale at the price of 8 ada.\n", + "2. The seller deposits the `BearGarden` token.\n", + "3. Anyone can join the contract by depositing the sale price of 8 ada into the contract. In that transaction, the open-role validator will pay them the `Seller` token so that they are now a party to the contract.\n", + "4. Anyone notifies the contract to complete its operation and pay the 8 ada to Marlowe's role-payout validator address for the benefit of the seller and one `BearGarden` for the benefit of the buyer. The `Notify TrueObs` is present because of the aforementioned security restriction.\n", + "5. The seller withdraws their 8 ada from the role-payout validator address.\n", + "6. The buyer withdraws their one `BearGarden` from the role-payout validator address.\n", + "\n", + "In [Marlowe Playground](https://play.marlowe.iohk.io//), the contract looks like this in Blockly format.\n", + "\n", + "![Example open-roles contract for Marlowe](open-roles-contract.png)" + ] + }, + { + "cell_type": "markdown", + "id": "05926130-23df-4922-a4d4-758ff83ee4d8", + "metadata": {}, + "source": [ + "## Preliminaries\n", + "\n", + "See [Preliminaries](../../docs/preliminaries.md) for information on setting up one's environment for using this tutorial.\n", + "\n", + "The first step is to check we have all the required tools and environment variables available to the notebook. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dea4b2b9-71ce-4608-bc97-da0303920a00", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "export SCRIPTS=../../scripts\n", + "export KEYS=../../keys\n", + "source $SCRIPTS/check-tools-and-env.sh" + ] + }, + { + "cell_type": "markdown", + "id": "828e37fe-de02-45ff-8cc1-8fff92058943", + "metadata": {}, + "source": [ + "Make sure you've also [setup and funded](../../setup/01-setup-keys.ipynb) the different parties\n", + "- Seller\n", + " - `$KEYS/lender.address`: Cardano address for the seller\n", + " - `$KEYS/lender.skey`: location of signing key file for the seller\n", + "- Buyer\n", + " - `$KEYS/borrower.address`: Cardano address for the buyer\n", + " - `$KEYS/borrower.skey`: location of signing key file for the buyer" + ] + }, + { + "cell_type": "markdown", + "id": "0860f8a4-081d-4472-a7ce-75495cfaa1e3", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "source": [ + "### Seller address and funds\n", + "\n", + "Check that an address and key has been created for the seller. If not, see \"Creating Addresses and Signing Keys\" in [Setup Keys](../../setup/01-setup-keys.ipynb#Creating-Addresses-and-Signing-Keys)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41c50ec5-8133-4916-b019-ebb51f53dc45", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "SELLER_SKEY=$KEYS/lender.skey\n", + "SELLER_ADDR=$(cat $KEYS/lender.address)\n", + "echo \"SELLER_ADDR = $SELLER_ADDR\"" + ] + }, + { + "cell_type": "markdown", + "id": "3b1f5078-fd67-4325-83c6-56d5019fb06a", + "metadata": {}, + "source": [ + "Check that the seller has at least ten hundred ada and the tokens." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "034726c2-b750-4731-b8ba-f5012099e3d2", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "cardano-cli query utxo --testnet-magic \"$CARDANO_TESTNET_MAGIC\" --address \"$SELLER_ADDR\"" + ] + }, + { + "cell_type": "markdown", + "id": "1f241c70-81bc-4da5-a37e-a947b91f8af7", + "metadata": {}, + "source": [ + "One can view the address on a Cardano explorer. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a22db305-86d6-4598-bf1b-b90522c43c62", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "$SCRIPTS/cardano-scan-address.sh $SELLER_ADDR" + ] + }, + { + "cell_type": "markdown", + "id": "60de901c-b913-4140-810a-f3ad745c695e", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "source": [ + "### Buyer address and funds\n", + "\n", + "Check that an address and key has been created for the buyer provider. If not, see \"Creating Addresses and Signing Keys\" in [Setup Keys](../../setup/01-setup-keys.ipynb#Creating-Addresses-and-Signing-Keys)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dad31bfb-5084-485a-8df9-300ca7920d16", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "BUYER_SKEY=$KEYS/borrower.skey\n", + "BUYER_ADDR=$(cat $KEYS/borrower.address)\n", + "echo \"BUYER_ADDR = $BUYER_ADDR\"" + ] + }, + { + "cell_type": "markdown", + "id": "173b7cbe-c5ce-468c-93f7-3303a0558d32", + "metadata": {}, + "source": [ + "Check that the buyer has at least twenty ada." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7309b4f8-d163-4f4d-ae4c-089e84337e48", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "cardano-cli query utxo --testnet-magic \"$CARDANO_TESTNET_MAGIC\" --address \"$BUYER_ADDR\"" + ] + }, + { + "cell_type": "markdown", + "id": "b75d4b83-d62f-45cc-a420-57eb053b325a", + "metadata": {}, + "source": [ + "One can view the address on a Cardano explorer. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f809441-6539-434d-9a6c-d0e94e6423ec", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "$SCRIPTS/cardano-scan-address.sh $BUYER_ADDR" + ] + }, + { + "cell_type": "markdown", + "id": "098a4ce8-5aa0-4019-948c-a07c404729c0", + "metadata": {}, + "source": [ + "## Design the contract" + ] + }, + { + "cell_type": "markdown", + "id": "97822f54-815f-4f45-a25e-0ee7bf362766", + "metadata": {}, + "source": [ + "### Specify the token to be offered\n", + "\n", + "Fill in the policy ID and name of the token that will be offered for sale. If you need to mint a token for this example, see [Lesson 9. Minting Tokens for Marlowe Contracts](../09-minting)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "452a9b95-aea8-4894-ba71-77ece361feec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "TOKEN_POLICY=8bb3b343d8e404472337966a722150048c768d0a92a9813596c5338d\n", + "TOKEN_NAME=BearGarden\n", + "TOKEN_QUANTITY=1" + ] + }, + { + "cell_type": "markdown", + "id": "fbc3ae43-cd13-4b02-a556-033d54199a9e", + "metadata": {}, + "source": [ + "### Set the deadlines for action.\n", + "\n", + "In a real token swap the payment deadline might be weeks or months, instead of hours." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94a7acd7-b7cb-4c3d-b253-9c49399d4917", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "DEPOSIT_DEADLINE=$((1000 * $(date -u -d \"$date + 1 hour\" +%s)))\n", + "PAYMENT_DEADLINE=$((1000 * $(date -u -d \"$date + 2 hours\" +%s)))\n", + "NOTIFY_DEADLINE=$((1000 * $(date -u -d \"$date + 3 hours\" +%s)))" + ] + }, + { + "cell_type": "markdown", + "id": "ae3afcd8-d657-4ab4-900a-b575c57d37fc", + "metadata": {}, + "source": [ + "### Create a JSON file containing the contract" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f21e820b-0393-4eaf-83a7-ae96f40e3d9a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "yaml2json << EOI > contract.json\n", + "when:\n", + "- case:\n", + " party:\n", + " role_token: Seller\n", + " deposits: $TOKEN_QUANTITY\n", + " of_token:\n", + " currency_symbol: $TOKEN_POLICY\n", + " token_name: $TOKEN_NAME\n", + " into_account:\n", + " role_token: Seller\n", + " then:\n", + " when:\n", + " - case:\n", + " party:\n", + " role_token: Buyer\n", + " deposits: 8000000\n", + " of_token:\n", + " currency_symbol: ''\n", + " token_name: ''\n", + " into_account:\n", + " role_token: Seller\n", + " then:\n", + " pay: $TOKEN_QUANTITY\n", + " token:\n", + " currency_symbol: $TOKEN_POLICY\n", + " token_name: $TOKEN_NAME\n", + " to:\n", + " account:\n", + " role_token: Buyer\n", + " from_account:\n", + " role_token: Seller\n", + " then:\n", + " when:\n", + " - case:\n", + " notify_if: true\n", + " then: close\n", + " timeout: $NOTIFY_DEADLINE\n", + " timeout_continuation: close\n", + " timeout: $PAYMENT_DEADLINE\n", + " timeout_continuation: close\n", + "timeout: $DEPOSIT_DEADLINE\n", + "timeout_continuation: close\n", + "EOI\n", + "jq . contract.json" + ] + }, + { + "cell_type": "markdown", + "id": "5cb9ea72-a132-4a5b-8680-2d9173544f30", + "metadata": {}, + "source": [ + "## Transaction 1. Create the contract\n", + "\n", + "Craft the request specifying the contract and its role tokens.\n", + "\n", + "*Important:* An open-role contract requires one of the role tokens to be designated as a \"thread\" token and sent to the contract's UTXO. In this example we simply call the thread token `Thread`. Instead of assigning it a payment address, we assign it the JSON object `{\"script\": \"ThreadRole\"}`.\n", + "\n", + "Similarly, each open role in the contract must be named. Below we just one one open role named `Buyer`. Instead of assigning it to a payment address, we assign it the JSON object `{\"script\" : \"OpenRole\"}`.\n", + "\n", + "The special destination assignments for the thread and open roles are recognized by Marlowe Runtime as the cue for creating one or more open-role validators and sending the corresponding role tokens to the open-role validator address.\n", + "\n", + "The `Seller` is just a standard role, so we assign it to be sent to the seller's payment address." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "499fded1-754c-4855-923f-bf8b1b9daed8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "yaml2json << EOI > request-1.json\n", + "version: v1\n", + "contract: $(cat contract.json)\n", + "roles:\n", + " Thread:\n", + " script: ThreadRole\n", + " Buyer:\n", + " script: OpenRole\n", + " Seller: $SELLER_ADDR\n", + "minUTxODeposit: $((2 * 1000000))\n", + "metadata: {}\n", + "tags: {}\n", + "EOI\n", + "jq . request-1.json" + ] + }, + { + "cell_type": "markdown", + "id": "7ee9f58b-3553-487b-9189-eb18e01ab260", + "metadata": {}, + "source": [ + "A `HTTP` `POST` request to Marlowe Runtime\\'s `/contracts` endpoint will build the creation transaction for a Marlowe contract. We provide it the JSON file containing the contract and tell it the `MIN_LOVELACE` value that we previously chose. Anyone could create the contract, but in this example the lender will be doing so, so we provide their address to fund the transaction and to receive the change from it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ea7ac46-e3f8-4dac-b4ad-dc14e0039e3b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "curl \"$MARLOWE_RT_WEBSERVER_URL/contracts\" \\\n", + " -X POST \\\n", + " -H 'Content-Type: application/json' \\\n", + " -H \"X-Change-Address: $SELLER_ADDR\" \\\n", + " -d @request-1.json \\\n", + " -o response-1.json \\\n", + " -sS" + ] + }, + { + "cell_type": "markdown", + "id": "d80a2b1b-946a-4ab6-81e6-381d82f0f1db", + "metadata": {}, + "source": [ + "Check to make sure the contract does not contain safety errors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8eaa648b-2b97-4a30-ad97-e65ff34c07db", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "jq .resource.safetyErrors response-1.json" + ] + }, + { + "cell_type": "markdown", + "id": "99fdae96-4ec7-4b78-8644-6421d472442b", + "metadata": {}, + "source": [ + "Record the contract ID for future use." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5deac97d-bdda-4bf2-bc5b-fa79eb137e1e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "CONTRACT_ID=\"$(jq -r '.resource.contractId' response-1.json)\"\n", + "echo \"CONTRACT_ID = $CONTRACT_ID\"" + ] + }, + { + "cell_type": "markdown", + "id": "cb37cf5c-3d0c-4c1d-8bca-8b8e37ded43e", + "metadata": {}, + "source": [ + "Extract the unsigned transaction from the response." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "740947ad-5516-4678-84a4-7edc88473c25", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "jq '.resource.txBody' response-1.json > tx-1.unsigned\n", + "jq -r .type tx-1.unsigned" + ] + }, + { + "cell_type": "markdown", + "id": "b23eef93-75ee-4297-894d-163493487a41", + "metadata": {}, + "source": [ + "There are many ways to sign and submit Cardano transactions:\n", + "- `cardano-cli` at the command line\n", + "- `cardano-wallet` at the command line or as a REST service\n", + "- `cardano-hw-cli` for a hardware wallet at the command line\n", + "- a Babbage-compatible CIP-30 wallet in a web browser\n", + "- `marlowe-cli` at the command line\n", + "\n", + "For convenience, here we use `marlowe-cli transaction submit`. One may have to wait a minute or so for the transactions to be confirmed on the blockchain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bdc96af-7179-4903-af9c-738b204bc7ec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli transaction submit \\\n", + " --tx-body-file tx-1.unsigned \\\n", + " --required-signer $SELLER_SKEY \\\n", + " --timeout 600s" + ] + }, + { + "cell_type": "markdown", + "id": "2b17d8d7-551d-4a2c-966d-4e6248e71a63", + "metadata": {}, + "source": [ + "One can view the transaction on the Cardano and Marlowe explorer and see that the contract has been created and the parties have received their role tokens. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "006ca411-2088-439f-b735-b49892de22e8", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "echo \"Cardano Scan (low level)\"\n", + "$SCRIPTS/cardano-scan-tx.sh \"${CONTRACT_ID/#/%23}\"\n", + "echo\n", + "echo \"Marlowe Scan (high level)\"\n", + "$SCRIPTS/marlowe-scan.sh \"$CONTRACT_ID\"" + ] + }, + { + "cell_type": "markdown", + "id": "0a365be6-10a7-48b8-a981-ace724a5b781", + "metadata": {}, + "source": [ + "## Transaction 2. The seller deposits the `BearGarden` token.\n", + "\n", + "Generate the JSON input for making the deposit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84570eb9-0321-49f5-8429-b31274dd055f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli input deposit \\\n", + " --deposit-party Seller \\\n", + " --deposit-account Seller \\\n", + " --deposit-amount 1 \\\n", + " --deposit-token 8bb3b343d8e404472337966a722150048c768d0a92a9813596c5338d.BearGarden \\\n", + " --out-file input-2.json\n", + "jq . input-2.json" + ] + }, + { + "cell_type": "markdown", + "id": "60d5a7ff-a920-4549-b233-19d30a31e6f1", + "metadata": {}, + "source": [ + "Craft the request for Marlowe Runtime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "487b1e63-5306-4be1-bff9-1293fa0c9ae6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "yaml2json << EOI > request-2.json\n", + "version: v1\n", + "inputs: [$(cat input-2.json)]\n", + "metadata: {}\n", + "tags: {}\n", + "EOI\n", + "jq . request-2.json" + ] + }, + { + "cell_type": "markdown", + "id": "504df4b6-2850-4a88-8ffe-f72aa82ba917", + "metadata": {}, + "source": [ + "Compute the URL for submitting transactions for the contract." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3489d541-9a6a-4b7c-8e72-d6d82c306159", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "CONTRACT_URL=\"$MARLOWE_RT_WEBSERVER_URL/`jq -r '.links.contract' response-1.json`\"\n", + "echo \"CONTRACT_URL = $CONTRACT_URL\"" + ] + }, + { + "cell_type": "markdown", + "id": "0c91fb96-3a22-4c07-a95e-766c5a7ac332", + "metadata": {}, + "source": [ + "Request that Marlowe Runtime build the deposit transaction: the seller deposits their token into the contract using Marlowe Runtime\\'s `HTTP` `POST` `/contract/{contractId}/transactions` endpoint." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8571a2e-23f9-4096-96ac-8c520c27b4c9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "curl \"$CONTRACT_URL/transactions\" \\\n", + " -X POST \\\n", + " -H 'Content-Type: application/json' \\\n", + " -H \"X-Change-Address: $SELLER_ADDR\" \\\n", + " -d @request-2.json \\\n", + " -o response-2.json \\\n", + " -sS" + ] + }, + { + "cell_type": "markdown", + "id": "5e73fd39-8813-4db8-9e9a-f868c7e645e7", + "metadata": {}, + "source": [ + "Extract the unsigned transaction from the response." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d23f472d-ba80-4553-8cd4-b0a144133b20", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "jq '.resource.txBody' response-2.json > tx-2.unsigned\n", + "jq -r .type tx-2.unsigned" + ] + }, + { + "cell_type": "markdown", + "id": "cbd59eb1-c275-4c40-b1fc-8485ed950792", + "metadata": {}, + "source": [ + "Submit the transaction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "278ef129-431e-48a7-8de8-d3b9d912c9df", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli transaction submit \\\n", + " --tx-body-file tx-2.unsigned \\\n", + " --required-signer $SELLER_SKEY \\\n", + " --timeout 600s" + ] + }, + { + "cell_type": "markdown", + "id": "f8e83a1e-abf2-4cdc-804a-e07de6b90eeb", + "metadata": {}, + "source": [ + "On can view the transaction on a Cardano explorer. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6add9fb4-811b-4c97-9bac-7eaf9af2e1fc", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "$SCRIPTS/cardano-scan-tx.sh \"$(jq -r .resource.transactionId response-2.json)\"" + ] + }, + { + "cell_type": "markdown", + "id": "e7171b8f-62bc-48df-ac65-051c45079e24", + "metadata": {}, + "source": [ + "## Transaction 3. The buyer deposits 8 ada and receives the `Buyer` role token.\n", + "\n", + "Generate the JSON input for making the deposit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7cce202b-226f-4f89-b507-92477f33f9c9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli input deposit \\\n", + " --deposit-party Buyer \\\n", + " --deposit-account Seller \\\n", + " --deposit-amount 8000000 \\\n", + " --out-file input-3.json\n", + "jq . input-3.json" + ] + }, + { + "cell_type": "markdown", + "id": "a5573c1d-114b-4643-a3f1-7fb5702bc108", + "metadata": {}, + "source": [ + "Craft the request for Marlowe Runtime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae65b275-6ab6-4a86-93db-ab629b81d5c4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "yaml2json << EOI > request-3.json\n", + "version: v1\n", + "inputs: [$(cat input-3.json)]\n", + "metadata: {}\n", + "tags: {}\n", + "EOI\n", + "jq . request-3.json" + ] + }, + { + "cell_type": "markdown", + "id": "d074bc69-27f0-4a16-afec-8797434b943c", + "metadata": {}, + "source": [ + "Request that Marlowe Runtime build the deposit transaction. This time it is the buyer that is building and submitting the transaction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e088a25-903e-4b56-b197-45a7052bd747", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "curl \"$CONTRACT_URL/transactions\" \\\n", + " -X POST \\\n", + " -H 'Content-Type: application/json' \\\n", + " -H \"X-Change-Address: $BUYER_ADDR\" \\\n", + " -d @request-3.json \\\n", + " -o response-3.json \\\n", + " -sS" + ] + }, + { + "cell_type": "markdown", + "id": "90a54796-d825-4767-935f-6f4f3e84082f", + "metadata": {}, + "source": [ + "Extract the unsigned transaction from the response." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e115489-73b4-43e1-9c74-67bf9eb9fd0f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "jq '.resource.txBody' response-3.json > tx-3.unsigned\n", + "jq -r .type tx-3.unsigned" + ] + }, + { + "cell_type": "markdown", + "id": "7f5021e3-3e1e-4ccb-b502-e72c4d9335f7", + "metadata": {}, + "source": [ + "Submit the transaction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2694104e-0e88-472c-a50d-b80c25438270", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli transaction submit \\\n", + " --tx-body-file tx-3.unsigned \\\n", + " --required-signer $BUYER_SKEY \\\n", + " --timeout 600s" + ] + }, + { + "cell_type": "markdown", + "id": "afe85cc4-5142-4533-a729-4baabc238ece", + "metadata": {}, + "source": [ + "On can view the transaction on a Cardano explorer. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7467009-fb95-4468-833a-6e83bdec652d", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "$SCRIPTS/cardano-scan-tx.sh \"$(jq -r .resource.transactionId response-3.json)\"" + ] + }, + { + "cell_type": "markdown", + "id": "4a1d2412-6888-43c8-aee2-6f03f37b6963", + "metadata": {}, + "source": [ + "## Transaction 4. Notify the contract to pay the `BearGarden` and 8 ada\n", + "\n", + "Generate the JSON input for notifying the contract." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b683874-5553-4be6-9c32-924c189ff800", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli input notify \\\n", + " --out-file input-4.json\n", + "jq . input-4.json" + ] + }, + { + "cell_type": "markdown", + "id": "1b570bf1-b003-4af2-9c13-6ea023bce6ab", + "metadata": {}, + "source": [ + "Craft the request for Marlowe Runtime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04b190d1-8b4e-4ede-85b4-cf30c71098b8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "yaml2json << EOI > request-4.json\n", + "version: v1\n", + "inputs: [$(cat input-4.json)]\n", + "metadata: {}\n", + "tags: {}\n", + "EOI\n", + "jq . request-4.json" + ] + }, + { + "cell_type": "markdown", + "id": "cb93701f-b12e-4841-ba66-5c0e52e3f14f", + "metadata": {}, + "source": [ + "Request that Marlowe Runtime build the notification transaction. Anyone can submit this transaction, but we choose to have the buyer submit it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f2d4b7d-53eb-479f-a8db-4d72055864a8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "curl \"$CONTRACT_URL/transactions\" \\\n", + " -X POST \\\n", + " -H 'Content-Type: application/json' \\\n", + " -H \"X-Change-Address: $BUYER_ADDR\" \\\n", + " -d @request-4.json \\\n", + " -o response-4.json \\\n", + " -sS" + ] + }, + { + "cell_type": "markdown", + "id": "fc36faac-291a-4667-b642-52fb34e433de", + "metadata": {}, + "source": [ + "Extract the unsigned transaction from the response." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6fa18e00-9b22-406f-a360-733047fe81b5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "jq '.resource.txBody' response-4.json > tx-4.unsigned\n", + "jq -r .type tx-4.unsigned" + ] + }, + { + "cell_type": "markdown", + "id": "fd87479c-8a77-431e-b287-c7da8aaf5ea8", + "metadata": {}, + "source": [ + "Submit the transaction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef54c2e8-ad09-4ad6-9071-b791b0c00003", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli transaction submit \\\n", + " --tx-body-file tx-4.unsigned \\\n", + " --required-signer $BUYER_SKEY \\\n", + " --timeout 600s" + ] + }, + { + "cell_type": "markdown", + "id": "652587d7-c732-4ff3-8f7f-4dda2356cba4", + "metadata": {}, + "source": [ + "On can view the transaction on a Cardano explorer. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "507aa31b-22f2-4d70-bb1b-31a1d06dd409", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "$SCRIPTS/cardano-scan-tx.sh \"$(jq -r .resource.transactionId response-4.json)\"" + ] + }, + { + "cell_type": "markdown", + "id": "0ed76e4c-b9b2-445f-9a06-8e8ad2833388", + "metadata": {}, + "source": [ + "## Transaction 5. The buyer withdraws the `BearGarden` token.\n", + "\n", + "Compute the URL for that transaction that paid to Marlowe's role-payout validator address." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22abbcf8-2241-437d-aef5-e99f81a69667", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "TX4_URL=\"$MARLOWE_RT_WEBSERVER_URL/$(jq -r .links.transaction response-4.json)\"\n", + "echo \"TX4_URL = $TX4_URL\"" + ] + }, + { + "cell_type": "markdown", + "id": "f33c347a-7fd2-406c-8b12-fedaf2447c5d", + "metadata": {}, + "source": [ + "Fetch the list of payouts available for withdrawal via a HTTP `GET /contracts/{contract-id}/transactions/{transaction-id}`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3fbc4c0a-e1d7-425a-889c-6d707b13dba8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "curl -sS \"$TX4_URL\" \\\n", + "| jq .resource.payouts \\\n", + "> payouts.json\n", + "json2yaml payouts.json" + ] + }, + { + "cell_type": "markdown", + "id": "ad480024-ba08-4753-93f5-9e0d87ac6917", + "metadata": {}, + "source": [ + "Craft the request for Marlowe Runtime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153c059c-9adf-4699-b86b-115f5ca7899d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "yaml2json << EOI > request-5.json\n", + "payouts:\n", + "- $(jq -r '.[0].payoutId' payouts.json)\n", + "EOI\n", + "jq . request-5.json" + ] + }, + { + "cell_type": "markdown", + "id": "18687892-5c80-43b8-9784-fc68c95a0d34", + "metadata": {}, + "source": [ + "Request that Marlowe Runtime build the withdrawal transaction vi a HTTP `POST /withdrawals`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68b26b38-4fa3-4624-bc9b-6ab53bf0dea4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "curl \"$MARLOWE_RT_WEBSERVER_URL/withdrawals\" \\\n", + " -X POST \\\n", + " -H 'Content-Type: application/json' \\\n", + " -H \"X-Change-Address: $BUYER_ADDR\" \\\n", + " -d @request-5.json \\\n", + " -o response-5.json \\\n", + " -sS" + ] + }, + { + "cell_type": "markdown", + "id": "7dfdc146-5ce3-48f7-b935-106629bcb88d", + "metadata": {}, + "source": [ + "Extract the unsigned transaction from the response." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "589dfb6a-040a-44cc-a2a5-5b446f872d11", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "jq '.resource.txBody' response-5.json > tx-5.unsigned\n", + "jq -r .type tx-5.unsigned" + ] + }, + { + "cell_type": "markdown", + "id": "3059f31e-e31d-408a-bbec-7b52018a7fbb", + "metadata": {}, + "source": [ + "Submit the transaction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "125e3636-6a8f-4c61-88b2-0199f4ade8f5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli transaction submit \\\n", + " --tx-body-file tx-5.unsigned \\\n", + " --required-signer $BUYER_SKEY \\\n", + " --timeout 600s" + ] + }, + { + "cell_type": "markdown", + "id": "5ec98055-26d7-4a61-993d-2a7dd2c9acd2", + "metadata": {}, + "source": [ + "On can view the transaction on a Cardano explorer. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68ca61e1-e7e5-4ecf-90c8-a4a538d74630", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "$SCRIPTS/cardano-scan-tx.sh \"$(jq -r .resource.withdrawalId response-5.json)\"" + ] + }, + { + "cell_type": "markdown", + "id": "11c18009-3ff9-4c5f-a64f-586873940a7c", + "metadata": {}, + "source": [ + "## Transaction 6. The seller withdraws the 8 ada.\n", + "\n", + "Craft the request for Marlowe Runtime." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f51a8bbc-1502-4e3c-ad80-cd4e9814da6f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "yaml2json << EOI > request-6.json\n", + "payouts:\n", + "- $(jq -r '.[1].payoutId' payouts.json)\n", + "EOI\n", + "cat request-6.json" + ] + }, + { + "cell_type": "markdown", + "id": "173a728d-8e72-4e80-a8c6-ec1b4b8b32f7", + "metadata": {}, + "source": [ + "Request that Marlowe Runtime build the withdrawal transaction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "768888c4-7fa4-4380-9649-5dc326d9f50c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "curl \"$MARLOWE_RT_WEBSERVER_URL/withdrawals\" \\\n", + " -X POST \\\n", + " -H 'Content-Type: application/json' \\\n", + " -H \"X-Change-Address: $SELLER_ADDR\" \\\n", + " -d @request-6.json \\\n", + " -o response-6.json \\\n", + " -sS" + ] + }, + { + "cell_type": "markdown", + "id": "36f7a757-09ad-41fd-8f16-12e2bb70f381", + "metadata": {}, + "source": [ + "Extract the unsigned transaction from the response." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1e98a5d-5e92-4474-a298-b25e79a56e01", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "jq '.resource.txBody' response-6.json > tx-6.unsigned\n", + "jq -r .type tx-6.unsigned" + ] + }, + { + "cell_type": "markdown", + "id": "6119a30c-5f62-485b-96d6-448af36aa75c", + "metadata": {}, + "source": [ + "Submit the transaction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b849b26-2261-429e-ae31-19acb65ff492", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "marlowe-cli transaction submit \\\n", + " --tx-body-file tx-6.unsigned \\\n", + " --required-signer $SELLER_SKEY \\\n", + " --timeout 600s" + ] + }, + { + "cell_type": "markdown", + "id": "d68125d8-14d4-40db-a1fb-a14cdcfd76db", + "metadata": {}, + "source": [ + "On can view the transaction on a Cardano explorer. It sometimes takes thirty seconds or so for the transaction to be visible in an explorer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "331d652b-e7fa-4eb0-96a3-e5cfa565b1b9", + "metadata": { + "tags": [], + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "$SCRIPTS/cardano-scan-tx.sh \"$(jq -r .resource.withdrawalId response-6.json)\"" + ] + }, + { + "cell_type": "markdown", + "id": "3cb7facc-8e9f-4452-a2c9-e29bf9b6a6e5", + "metadata": {}, + "source": [ + "## Examine the wallets\n", + "\n", + "Seller's wallet:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8a49292-3efd-45f3-947c-71877acf265c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "cardano-cli query utxo --testnet-magic 1 --address \"$SELLER_ADDR\"" + ] + }, + { + "cell_type": "markdown", + "id": "ae951f46-822f-4418-a6ab-506e39df0b02", + "metadata": {}, + "source": [ + "Buyer's wallet:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "537dc3f7-9e4e-49ae-844e-b8ae9d69243f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "cardano-cli query utxo --testnet-magic 1 --address \"$BUYER_ADDR\"" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Bash with Marlowe Tools", + "language": "bash", + "name": "bash-minimal" + }, + "language_info": { + "codemirror_mode": "shell", + "file_extension": ".sh", + "mimetype": "text/x-sh", + "name": "bash" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/lessons/10-open-roles/open-roles-contract.png b/lessons/10-open-roles/open-roles-contract.png new file mode 100644 index 0000000..7cb552f Binary files /dev/null and b/lessons/10-open-roles/open-roles-contract.png differ diff --git a/lessons/10-open-roles/open-roles-interaction.png b/lessons/10-open-roles/open-roles-interaction.png new file mode 100644 index 0000000..14a4730 Binary files /dev/null and b/lessons/10-open-roles/open-roles-interaction.png differ