├── .ci ├── Dockerfile ├── build ├── copy-libraries.sh └── merge-root.sh ├── .env.example ├── .github └── workflows │ └── docusaurus.yml ├── .gitignore ├── .gitlab-ci.yml ├── CHANGELOG.md ├── DEVELOPER.md ├── LISENCE ├── README.md ├── cabal.project ├── docker-compose.yml ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── architecture.md │ ├── hydra_feature_analysis.md │ ├── milestones.md │ ├── scenarioTests.md │ ├── sequenceDiagrams │ │ ├── abort_head.md │ │ ├── close_head.md │ │ ├── commit_utxos.md │ │ ├── decommit_utxos.md │ │ ├── fanout_head.md │ │ ├── initialize_head.md │ │ ├── query_protocol_params.md │ │ └── query_utxos.md │ ├── tutorial.md │ └── tutorials │ │ ├── build_tx.md │ │ ├── close_head.md │ │ ├── commit_utxos.md │ │ ├── fanout.md │ │ ├── initialize_head.md │ │ └── query │ │ ├── head_state.md │ │ ├── protocol_params.md │ │ └── utxos.md ├── docusaurus.config.ts ├── package.json ├── sidebars.ts ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ └── img │ │ ├── abort.jpg │ │ ├── autonomous_agent.png │ │ ├── close.jpg │ │ ├── commit.jpg │ │ ├── decommit.jpg │ │ ├── docusaurus-social-card.jpg │ │ ├── docusaurus.png │ │ ├── fanout.jpg │ │ ├── favicon.ico │ │ ├── hydra-architecture.png │ │ ├── init.jpg │ │ ├── kuber-hydra.drawio.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── protocol-parameters.jpg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ ├── undraw_docusaurus_tree.svg │ │ ├── undraw_online-banking_v7ih.svg │ │ ├── undraw_term-sheet_70lo.svg │ │ ├── undraw_version-control_eiam.svg │ │ └── utxo.jpg ├── tsconfig.json └── yarn.lock ├── kontract-example ├── kontract-example.cabal └── src │ └── Main.hs ├── kuber-hydra ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app │ ├── Api │ │ └── Spec.hs │ ├── Main.hs │ └── Websocket │ │ ├── Aeson.hs │ │ ├── Commands.hs │ │ ├── Forwarder.hs │ │ ├── Middleware.hs │ │ ├── SocketConnection.hs │ │ ├── TxBuilder.hs │ │ └── Utils.hs └── kuber-hydra.cabal ├── kuber-server ├── app │ └── Main.hs ├── kuber-server.cabal └── src │ └── Kuber │ └── Server │ ├── Api.hs │ ├── Core.hs │ ├── Model.hs │ ├── ServantError.hs │ └── Spec.hs ├── kuber-test-lib └── src │ └── Cardano │ └── Kuber │ └── Test │ └── TestInterceptor.hs ├── kuber.cabal ├── src └── Cardano │ └── Kuber │ ├── Api.hs │ ├── Console │ └── ConsoleWritable.hs │ ├── Core │ ├── ChainAPI.hs │ ├── ChainInfo.hs │ ├── Kontract.hs │ ├── KuberAPI.hs │ ├── LocalNodeChainApi.hs │ ├── TxBuilder.hs │ ├── TxFramework.hs │ └── TxScript.hs │ ├── Data │ ├── EraUpdate.hs │ ├── Models.hs │ ├── Parsers.hs │ └── TxBuilderAeson.hs │ ├── Error.hs │ ├── Http │ ├── Client.hs │ ├── MediaType.hs │ └── Spec.hs │ ├── Util.hs │ └── Utility │ ├── ChainInfoUtil.hs │ ├── DataTransformation.hs │ ├── Misc.hs │ ├── QueryHelper.hs │ ├── ScriptUtil.hs │ ├── Text.hs │ └── WalletUtil.hs └── test ├── Main.hs └── Test ├── ApiTest.hs ├── ChainApiTests.hs ├── KuberApiTests.hs ├── MarketPlaceWorkflow.hs ├── ParserTest.hs ├── TestGen.hs └── TransactionJSON ├── exUnitsMultisig.json ├── mintWithDatumInAuxData.json ├── redeemFromSmartContract.json ├── redeemWithReferenceInput.json ├── simpleMint.json ├── simplePay.json └── simplePayWithMetadata.json /.ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 AS layer1 2 | ARG OVERRIDE_CHROOT_DIR=./chroot-env 3 | COPY --chown=root:root ${OVERRIDE_CHROOT_DIR} /layer1 4 | COPY ./.ci/merge-root.sh /merge-root 5 | RUN bash -e /merge-root /layer1 \ 6 | && rm -rf /layer1 \ 7 | && rm -rf merge-root 8 | 9 | FROM ubuntu:20.04 10 | LABEL "maintainer"="Sudip Bhattarai" 11 | COPY --from=layer1 / / 12 | ENV CARDANO_ERA=Conway 13 | WORKDIR /app 14 | EXPOSE 8081 15 | HEALTHCHECK --interval=40s --timeout=10s --start-period=30s --retries=2 CMD [ "/bin/kuber-server" , "--healthcheck" ] 16 | ENTRYPOINT /bin/kuber-server -------------------------------------------------------------------------------- /.ci/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; 3 | 4 | EXECUTABLE="./.docker/build" 5 | 6 | WORKDIR=$(pwd) 7 | DIST_NEWSTYLE_DIR="${WORKDIR}/dist-newstyle/build/docker/dist-newstyle/" 8 | CABAL_CACHE_DIR="${WORKDIR}/dist-newstyle/build/docker/cabal-cache/" 9 | CHROOT_DIR="${WORKDIR}/dist-newstyle/build/docker/chroot-env/" 10 | 11 | mkdir -p $DIST_NEWSTYLE_DIR; mkdir -p $CABAL_CACHE_DIR ; mkdir -p $CHROOT_DIR 12 | function echo-usage() { 13 | echo " Usage $EXECUTABLE [-t tagname]*" 14 | echo " Examples:" 15 | echo " Set up --> $EXECUTABLE cabal update" 16 | echo " Build an image --> $EXECUTABLE" 17 | echo " Build with specific tag --> $EXECUTABLE -t inputoutput/cardano-db-sync:local" 18 | echo " Clean up caches --> $EXECUTABLE clean" 19 | } 20 | 21 | 22 | if [ ! -f ./cabal.project ] 23 | then 24 | echo " Missing cabal.project file current directory"; 25 | echo;echo-usage 26 | exit 1 27 | fi 28 | 29 | ## print help 30 | if grep -qE '^--h' <<< "$*" || grep -qE "^-h" - <<< "$*" 31 | then 32 | echo-usage 33 | exit 1; 34 | fi 35 | 36 | ## clean command for freeing up the caches used for build 37 | if ( grep -qE 'clean' <<< "$*") 38 | then 39 | for dir in "$HOME/.cabal-docker" "./dist-newstyle-docker" "./chroot-env" 40 | do 41 | echo " Removing $dir"; 42 | rm -rf $dir; 43 | done; 44 | exit; 45 | fi 46 | 47 | function showCommand() { 48 | echo "+ $*" 49 | "$@" 50 | } 51 | 52 | 53 | ## execute a command inside the build image 54 | function execute-isolated() { 55 | echo "+ execute-isolated $*" 56 | docker run -i -u"$(id -u):$(id -g)" \ 57 | -v $CABAL_CACHE_DIR:$HOME/.cabal \ 58 | -v $CABAL_CACHE_DIR:/.cabal \ 59 | -v $DIST_NEWSTYLE_DIR:$WORKDIR/dist-newstyle/ \ 60 | -v $CHROOT_DIR:/chroot-env/ \ 61 | -v $WORKDIR:$WORKDIR \ 62 | -e OVERRIDE_CHROOT_DIR=/chroot-env \ 63 | -e HOME=$HOME \ 64 | --entrypoint /bin/bash \ 65 | --workdir $WORKDIR \ 66 | cardanoapi/haskell:9.6.1 -c "$*" 67 | } 68 | 69 | if [[ "$1" == cabal* ]] ; then 70 | execute-isolated "$@" # execute the cabal command 71 | else 72 | ## default build 73 | execute-isolated 'cabal build exe:kuber-server && bash -e ./.ci/copy-libraries.sh' 74 | 75 | ## build final docker image. by determining the git revision or passing the arguments to docker command. 76 | 77 | if [ $# -gt 0 ] 78 | then 79 | showCommand docker build --build-arg OVERRIDE_CHROOT_DIR=./dist-newstyle/build/docker/chroot-env/ -f ./.ci/Dockerfile "$@" . 80 | elif git describe --tags | grep -qE '^[0-9]+(\.[0-9]+)*$' 81 | then 82 | showCommand docker build --build-arg OVERRIDE_CHROOT_DIR=./dist-newstyle/build/docker/chroot-env/ -f ./.ci/Dockerfile -t "dquadrant/kuber:$(git describe --tags)" . 83 | else 84 | showCommand docker build --build-arg OVERRIDE_CHROOT_DIR=./dist-newstyle/build/docker/chroot-env/ -f ./.ci/Dockerfile -t "dquadrant/kuber:$(git rev-parse HEAD)" . 85 | fi 86 | fi 87 | -------------------------------------------------------------------------------- /.ci/copy-libraries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### Author: github.com/mesudip 4 | ### 5 | ### After cabal build is executed, this script searches the required executables, 6 | ### find their shared library dependencies and creates a directory `chroot-env` with it. 7 | ### The directory is self sufficient for running the executable with chroot but configuration files 8 | ### will have to be copied for it to properly work. 9 | ### 10 | set -vx 11 | set -eo pipefail 12 | ROOT_DIR=$(readlink -f .) 13 | ls . 14 | WORKDIR="${OVERRIDE_CHROOT_DIR:-./chroot-env}" 15 | WORKDIR="$(readlink -f $WORKDIR)" 16 | DIST_NEWSTYLE_DIR="$ROOT_DIR/dist-newstyle" 17 | 18 | echo "Creating chroot environment on ./chroot-env" 19 | mkdir -p "$WORKDIR/bin" 20 | mkdir -p "$WORKDIR/usr/bin" 21 | 22 | # given a filename copy it to ./chroot-env 23 | # also find the libraries it depends on and copy them. 24 | function copyExecutable(){ 25 | local FILE=$1 26 | echo "Copying $(basename $FILE)" 27 | cp -u --preserve=timestamps,mode "$FILE" "$WORKDIR${2:-/bin}" 28 | 29 | echo "Collecting the shared library dependencies for $(basename $FILE)..." 30 | deps=$(ldd $FILE | awk 'BEGIN{ORS=" "}$EXECUTABLE\ 31 | ~/^\//{print $1}$3~/^\//{print $3}'\ 32 | | sed 's/,$/\n/') 33 | 34 | set "$(ldd $FILE | grep ld-linux)" 35 | if [ -f "$1" ] 36 | then 37 | echo "Copying $1" 38 | mkdir -p "$WORKDIR$(dirname $1)" 39 | cp -u --preserve=timestamps,mode $1 "$WORKDIR$1" 40 | fi 41 | $D 42 | 43 | #Copy the deps 44 | for dep in $deps 45 | do 46 | echo "Copying $dep" 47 | mkdir -p "$WORKDIR$(dirname $dep)" 48 | cp -u --preserve=timestamps,mode "$dep" "$WORKDIR$dep" 49 | done 50 | } 51 | 52 | function findExecutable() { 53 | find "$DIST_NEWSTYLE_DIR/build" -type f -name "$1" | grep "$(getVersion $1)" 54 | } 55 | 56 | function getVersion(){ 57 | grep -i '^version' $1.cabal | grep -Eo '[[:digit:]]+(\.[[:digit:]]+)*' 58 | } 59 | 60 | #Get the library dependencies 61 | copyExecutable "$(findExecutable kuber-server)" 62 | copyExecutable /bin/bash 63 | copyExecutable /bin/ls 64 | copyExecutable /bin/sh 65 | copyExecutable /bin/cat 66 | copyExecutable /usr/bin/printenv 67 | 68 | echo "Done" 69 | -------------------------------------------------------------------------------- /.ci/merge-root.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; 3 | 4 | ### Author github.com/mesudip 5 | ### Copies files from a chroot isolation directory to root directory. 6 | 7 | for FILE in $( (cd $1 ;find . -type f ) | cut -c 2- ) 8 | do 9 | if [ ! -f $FILE ] 10 | then 11 | echo Creating $FILE 12 | mv "$1$FILE" $FILE 13 | fi 14 | done -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | HYDRA_IP=172.16.238.10 2 | HYDRA_PORT=4001 3 | SERVER_PORT=8081 4 | CARDANO_NODE_SOCKET_PATH=/media/reeshav/084ef290-597c-4168-b443-49fba520fcb5/cardano-node/preview/node.socket 5 | NETWORK=2 -------------------------------------------------------------------------------- /.github/workflows/docusaurus.yml: -------------------------------------------------------------------------------- 1 | name: Docusaurus Build and Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | - update/hydra-docs 9 | tags: 10 | - "*" 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: 18 24 | 25 | - name: Install dependencies 26 | run: npm install 27 | working-directory: ./docs 28 | 29 | - name: Build Docusaurus site 30 | run: npm run build 31 | working-directory: ./docs 32 | 33 | - name: Deploy Docusaurus to GitHub Pages 34 | uses: peaceiris/actions-gh-pages@v3 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | publish_dir: ./docs/build 38 | destination_dir: hydra_docusaurus 39 | keep_files: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist-newstyle 2 | .idea 3 | /.cluster 4 | # vscode related 5 | .vscode 6 | .history 7 | .env -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - deploy 4 | build: 5 | stage: build 6 | cache: 7 | paths: 8 | - dist-newstyle 9 | tags: 10 | - shell 11 | script: 12 | - docker info 13 | - if [ ! -z "$CI_COMMIT_TAG" ]; then COMMON_TAG="release"; elif [ "$CI_COMMIT_REF_NAME" == "master" ]; then COMMON_TAG="latest"; else COMMON_TAG=$CI_COMMIT_REF_SLUG; fi 14 | - if [ ! -z "$CI_COMMIT_TAG" ]; then IMAGE_IDENTIFIER="$CI_COMMIT_TAG"; else IMAGE_IDENTIFIER=$CI_COMMIT_SHA; fi 15 | - ./.ci/build cabal update 16 | - ./.ci/build -t "$CI_REGISTRY_IMAGE:$IMAGE_IDENTIFIER" -t "$CI_REGISTRY_IMAGE:$COMMON_TAG" 17 | - echo $CI_REGISTRY_PASSWORD | docker login --username $CI_REGISTRY_USER $CI_REGISTRY --password-stdin 18 | - docker push $CI_REGISTRY_IMAGE:$IMAGE_IDENTIFIER 19 | - docker push $CI_REGISTRY_IMAGE:$COMMON_TAG 20 | 21 | deploy-dev: 22 | stage: deploy 23 | image: docker:latest 24 | only: 25 | refs: 26 | - develop 27 | script: 28 | - echo $CI_REGISTRY_PASSWORD | docker login --username $CI_REGISTRY_USER $CI_REGISTRY --password-stdin 29 | - docker --host 172.31.0.6:2376 service update --with-registry-auth --image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA kuber_dev_preprod 30 | - docker --host 172.31.0.6:2376 service update --with-registry-auth --image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA kuber_dev_preview 31 | 32 | deploy-release: 33 | stage: deploy 34 | image: docker:latest 35 | only: 36 | - tags 37 | script: 38 | - echo $CI_REGISTRY_PASSWORD | docker login --username $CI_REGISTRY_USER $CI_REGISTRY --password-stdin 39 | - docker --host 172.31.0.7:2376 service update --with-registry-auth --image $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG cardanoapi_preprod 40 | - docker --host 172.31.0.7:2376 service update --with-registry-auth --image $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG cardanoapi_preview 41 | - docker --host 172.31.0.7:2376 service update --with-registry-auth --image $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG cardanoapi_mainnet 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v3.1.2 : Upgrade cardano-api to 10.x 2 | 3 | ### Changes 4 | - Bump cardano-api version to 10.1 5 | 6 | 7 | ## v3.1.1 : Collateral Selection Fix 8 | 9 | ### Fixes 10 | - Collateral selection logic now prefers 5Ada inputs 11 | - Multiple collateral inputs if collateral is not sufficient with 1 utxo. 12 | 13 | ## v3.1.0 : Stable Conway Era Support 14 | This stable release supports adds full support for new conway governance features. 15 | 16 | ##### DependsOn 17 | - cardano-api:9.2.0.0 18 | 19 | ### Changes: kuber-server 20 | - new endpoint `/api/v3/health` now returns nodes current tip and secondsSince last block was minted. If lastBlock was seen before 300 secs, status code will be `503` 21 | 22 | ### Changes: lib:kuber 23 | - when not set, kuber will automatically fill in following field by querying ledger state 24 | - `proposal.deposit` 25 | - `proposal.previousGovActionId` 26 | - `proposal.executionUnits` 27 | - `certificates.deposit` 28 | 29 | ### Fixes 30 | - Fix catchError function on Unhandled error 31 | - Add exception handler on txSubmission 32 | - Use FrameworkError explicitly to improve error handling 33 | - Make Kontract instance of MonadError and Alternative 34 | - Show proper error message for plutus error 35 | - add ex-unit test 36 | - Fix fromJson of proposal field 37 | - fix utxoModal parsing problem 38 | 39 | #### Known Issues: 40 | - **(lib:kuber)** : `Kontract` will omit following fields when using `RemoteKuberConnection` : **\$.proposal[s]**, **\$.votes[s]** or **\$.certificate[s]** 41 | 42 | #### Litmiations: 43 | - voting with script key is not supported 44 | 45 | 46 | ## v3.0.0-rc2 : Tx build bug fix , Add tests 47 | - Fix Bug : Server got irresponsive after requesting `/api/v1/tx` with plutus included transaction 48 | - Add tests to validate that the kuber apis are working via LocalNode Socket connection. 49 | 50 | #### Known Issues: 51 | - `Kontract` is not working via `RemoteKuberConnection` 52 | - If node is at Babbage era when kuber-server starts, after node reaches Conway era, restart is required to enable Conway features. 53 | - `$.validtyStart` is broken and is ignored. 54 | 55 | ## v3.0.0-rc1 : Full Refactor and ConwayEra Support 56 | 57 | ### Changes on kuber-server 58 | - Add new chain-query api-endpoints. 59 | * **GET** /api/v3/protocol-params 60 | * **GET** /api/v3/chain-point 61 | * **GET** /api/v3/utxo 62 | * **GET** /api/v3/system-start 63 | * **GET** /api/v3/genesis-params 64 | - Update default healthcheck url in docker image. (`/api/v3/chain-point` is used for healthcheck ) 65 | - Server starts by querying NodeEra and enables fields based on `BabbageEra` or `ConwayEra` 66 | - TxBuilder json now supports following new fields. 67 | * **$.proposal[s]** Conway Era Governance proposals. 68 | * **$.votes[s]** Conway Era votes. 69 | * **$.certificate[s]** Registration and dregistration certs only for Conway era. (Not available when node is running in babbage era). 70 | 71 | 72 | #### Known Issues: 73 | - If node is at Babbage era when kuber-server starts, after node reaches Conway era, restart is required to enable Conway features. 74 | - `$.validtyStart` is broken and is ignored. 75 | 76 | 77 | ### Changes on Kontract-example [WIP] 78 | - Introduced new package to showcase usage of `Kontract` 79 | 80 | 81 | ### Changes on lib:kuber 82 | - Update code comments for better haddock docs generation 83 | - Implement both `ToJson` and `FromJson` for all the entities used in api 84 | - Reorganize code and introduction of `Kontact` for offchain code. 85 | - Support For ConwayEra **[BREAKING change ]** 86 | - `TxBuilder` is now parametrized on `era` suppporting `Babbage` and `Conway` eras, with `IsTxBuilderEra` constraint. 87 | - Kuber Offchain code required Local Node Socket. **WIP** support running kuber code by connecting to kuber server 88 | - Remove `ChainConnectInfo`and related classes. Instead, use `LocalNodeConnectInfo CardanoMode` directly. 89 | 90 | -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Building locally 2 | The system packages and dependencies required for building Kuber is the same as that of [cardano-node](https://github.com/input-output-hk/cardano-node). 3 | The steps are described in detail in the documentation of [building-cardano-node-from-sources](https://developers.cardano.org/docs/get-started/installing-cardano-node/). 4 | 5 | 6 | In summary, to prepare your system for building kuber from sources, you have to: 7 | - install system dependencies for ghc and cardano-node 8 | - install ghc compiler and tools with ghcup 9 | - install iokhk patch of libsodium on the system 10 | - install lib-secp256 with extensions on the system 11 | 12 | Once everything is installed and ready, kuber will also be ready to run 13 | ``` 14 | cabal update 15 | CARDANO_NODE_SOCKET_PATH=/home/user/.cardano/preprod/node.socket NETWORK=preprod cabal run kuber-server 16 | ``` 17 | 18 | This will start kuber on port `8081`. 19 | -------------------------------------------------------------------------------- /LISENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sudip Bhattarai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kuber 2 | =========== 3 | 4 | Haskell library and API server for composing balanced Cardano transactions. 5 | 6 | [KuberIDE.com](https://kuberide.com) is Oficial IDE and plutus development and trying out kuber-server features. 7 | 8 | OpenSource Kuber playground is [here](https://github.com/dquadrant/kuber-playground) 9 | 10 | 11 | 12 | ## Docs 13 | - [JSON API](https://kuberide.com/kuber/docs/intro) 14 | - [Kuber Haskel Library](https://dquadrant.github.io/kuber/haskell_library/) 15 | - [Kuber-Hydra](https://dquadrant.github.io/kuber/hydra_docusaurus/) 16 | 17 | ## IDE 18 | Official IDE with Plutus compilation support is available at [kuberide.com/ide](https://kuberide.com/kuber/login/?state=\%2fide) 19 | 20 | ## Example Project using Kuber 21 | [cardano-marketplace](https://github.com/dQuadrant/cardano-marketplace) 22 | 23 | 24 | # Run Kuber-Server with docker-compose 25 | 26 | Kuber can be stared easily with [docker-compose.yml](./docker-compose.yml) file. But you will have to wait for cardano-node to sync to latest block 27 | 28 | ```bash 29 | git clone https://github.com/dquadrant/kuber.git 30 | git checkout 3.1.2 31 | docker-compose up -d 32 | ``` 33 | 34 | 35 | If you want to build docker image locally, you can use the helper script 36 | ```bash 37 | $ ./.ci/build 38 | ``` 39 | 40 | ## Developer guide 41 | Instructions for local development of kuber is available in [DEVELOPER.md](DEVELOPER.md) 42 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | -- See CONTRIBUTING for information about these, including some Nix commands 2 | -- you need to run if you change them 3 | index-state: 4 | , hackage.haskell.org 2025-05-07T06:09:46Z 5 | , cardano-haskell-packages 2025-04-29T20:52:57Z 6 | 7 | -- Custom repository for cardano haskell packages, see CONTRIBUTING for more 8 | repository cardano-haskell-packages 9 | url: https://chap.intersectmbo.org/ 10 | secure: True 11 | root-keys: 12 | 3e0cce471cf09815f930210f7827266fd09045445d65923e6d0238a6cd15126f 13 | 443abb7fb497a134c343faf52f0b659bd7999bc06b7f63fa76dc99d631f9bea1 14 | a86a1f6ce86c449c46666bda44268677abf29b5b2d2eb5ec7af903ec2f117a82 15 | bcec67e8e99cabfa7764d75ad9b158d72bfacf70ca1d0ec8bc6b4406d1bf8413 16 | c00aae8461a256275598500ea0e187588c35a5d5d7454fb57eac18d9edb86a56 17 | d4a35cd3121aa00d18544bb0ac01c3e1691d618f462c46129271bccf39f7e8ee 18 | 19 | packages: 20 | . 21 | ./kuber-server 22 | ./kontract-example 23 | ./kuber-hydra 24 | -- ./kuber-test-lib 25 | 26 | -- You never, ever, want this. 27 | write-ghc-environment-files: never 28 | 29 | package cardano-crypto-praos 30 | flags: -external-libsodium-vrf 31 | 32 | -- Always build tests and benchmarks. 33 | tests: True 34 | benchmarks: False 35 | 36 | package cryptonite 37 | -- Using RDRAND instead of /dev/urandom as an entropy source for key 38 | -- generation is dubious. Set the flag so we use /dev/urandom by default. 39 | flags: -support_rdrand 40 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | volumes: 3 | node-ipc: 4 | node-db: 5 | services: 6 | cardano-node: 7 | image: inputoutput/cardano-node:${CARDANO_NODE_VERSION:-9.2.0} 8 | environment: 9 | NETWORK: ${NETWORK:-preprod} 10 | volumes: 11 | - node-db:/data/db 12 | - node-ipc:/ipc 13 | logging: 14 | driver: "json-file" 15 | options: 16 | max-size: "2M" 17 | max-file: "10" 18 | kuber: 19 | image: dquadrant/kuber:${KUBER_VERSION:-3.1.1} 20 | environment: 21 | NETWORK: ${NETWORK:- preprod} 22 | volumes: 23 | - node-ipc:/root/.cardano/preprod/ 24 | # -node-ipc:/root/.cardano/mainnet/ for NETWORK=mainnet or set CARDANO_NODE_SOCKET_PATH explicitly 25 | # -node-ipc:/root/.cardano/preview/ for NETWORK=preview or set CARDANO_NODE_SOCKET_PATH explicitly 26 | # -node-ipc:/root/.cardano/sancho/ for NETWORK=sancho or set CARDANO_NODE_SOCKET_PATH explicitly 27 | 28 | ports: 29 | - 8081:8081 30 | logging: 31 | driver: "json-file" 32 | options: 33 | max-size: "2M" 34 | max-file: "10" 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/hydra_feature_analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hydra Feature Analysis 3 | sidebar_position: 2 4 | --- 5 | 6 | 7 | We have tested the following scenarios in Hydra: 8 | 9 | ## 1. Mint a token and close hydra head 10 | 11 | - A token was minted using a plutusV3 always pass script 12 | - The snapshot was confirmed for minting token 13 | - Head was closed 14 | - Error during fanout 15 | 16 | ```json 17 | "postTxError": { 18 | "failureReason": "TxValidationErrorInCardanoMode (ShelleyTxValidationError ShelleyBasedEraConway (ApplyTxError (ConwayUtxowFailure (UtxoFailure (ValueNotConservedUTxO (MaryValue (Coin 325709888) (MultiAsset (fromList [(PolicyID {policyID = ScriptHash \"29c699a1d8dc832504e4ec37a41286176820c9221c5505f7005bae68\"},fromList [(\"4879647261486561645631\",1),(\"e696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab\",1),(\"fce240ccfcb839aa37e5b04206a84530e027b0d3bfb596e7d0685f6a\",1)])]))) (MaryValue (Coin 325709888) (MultiAsset (fromList [(PolicyID {policyID = ScriptHash \"29c699a1d8dc832504e4ec37a41286176820c9221c5505f7005bae68\"},fromList [(\"4879647261486561645631\",1),(\"e696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab\",1),(\"fce240ccfcb839aa37e5b04206a84530e027b0d3bfb596e7d0685f6a\",1)]),(PolicyID {policyID = ScriptHash \"3a888d65f16790950a72daee1f63aa05add6d268434107cfa5b67712\"},fromList [(\"68796472612d6b75626572\",1)])]))))) :| [])))", 19 | "tag": "FailedToPostTx" 20 | } 21 | ``` 22 | 23 | ## 2. Mint a token, burn it and close hydra head 24 | 25 | - A token was minted using a plutusV3 always pass script. 26 | - The snapshot was confirmed for minting token 27 | - Token was burnt 28 | - The snapshot was confirmed for burning token 29 | - Head was closed 30 | - Fanout Successful 31 | 32 | ## 3. Pay to script and close hydra head 33 | 34 | - Paid **10 ₳** to script address `addr_test1wqag3rt979nep9g2wtdwu8mr4gz6m4kjdpp5zp705km8wys6t2kla` 35 | - Snapshot was confirmed for this transaction 36 | - Head was closed 37 | - Fanout Successful: [21f398e9a5a7661c326036d5e9577b64f28554da9e26387e780a032fdb77e99a](https://preview.cexplorer.io/tx/21f398e9a5a7661c326036d5e9577b64f28554da9e26387e780a032fdb77e99a) 38 | 39 | ## 4. Pay to 500 addresses and close hydra head 40 | 41 | - Transactions for 500 addresses were done within the hydra head 42 | - Snapshot was confirmed for each transaction 43 | - Head was closed 44 | - Error during fanout 45 | 46 | ```json 47 | { 48 | "postTxError": { 49 | "failureReason": "ValidationFailure (WrapExUnits {unWrapExUnits = ExUnits' {exUnitsMem' = 0, exUnitsSteps' = 0}}) (CekError An error has occurred:\nThe machine terminated part way through evaluation due to overspending the budget.\nThe budget when the machine terminated was:\n({cpu: 6396337807\n| mem: -2582})\nNegative numbers indicate the overspent budget; note that this only indicates the budget that was needed for the next step, not to run the program to completion.) [] ..." 50 | } 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/docs/milestones.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hydra Integration for Kuber 3 | --- 4 | 5 | 6 | **Repository:** [https://github.com/dquadrant/kuber](https://github.com/dquadrant/kuber) 7 | 8 | ---------- 9 | 10 | ## Summary 11 | 12 | This project integrates **Kuber**, a Haskell-based Cardano Layer 1 transaction builder, with **Hydra**, Cardano’s Layer 2 scalability solution. 13 | Kuber simplifies transaction construction by allowing developers to define transactions in JSON, abstracting away complex tasks like fee balancing and submission. 14 | The goal is to extend this user-friendly transaction interface to also support the Hydra protocol, enabling developers to submit Layer 2 transactions just as easily as Layer 1. 15 | 16 | Kuber already supports full functionality for Layer 1, including transaction construction, validation, and submission. 17 | This integration will bring the same functionality to Hydra, allowing developers to interact with a Hydra Head as easily as with Layer 1. 18 | 19 | ---------- 20 | 21 | ## Scope of Project 22 | 23 | - Extend Kuber to support building, validating, and submitting transactions over a Hydra Head. 24 | - Implement Hydra lifecycle operations via Kuber API, including initializing the head, committing UTxOs, closing, aborting, and fanout. 25 | - Provide well-structured WebSocket proxy support to receive and interpret Hydra events. 26 | - Ensure Kuber works seamlessly with both **mainnet** and **testnet** deployments. 27 | - Provide a client library to easily integrate with the backend 28 | 29 | ---------- 30 | 31 | # Milestones 32 | 33 | ## M1 Project Planning and Architecture 34 | ### Deliverables 35 | - Deliverable includes this document and project architecture details 36 | - Details are published to github : [Architecture.md](./architecture.md) 37 | 38 | ## M2.1 - Core Hydra Functionalities (Haskell, kuber-server) 39 | 40 | ### Deliverables 41 | 42 | - **2.1.1** Hydra WebSocket Integration – Kuber connects to and parses Hydra WebSocket event stream 43 | - **2.1.2** Hydra Lifecycle Relay API – Endpoints for init, commit, decommit, close, contest, abort, fanout 44 | - **2.1.3** Query API Implementation – REST APIs for querying UTXOs, state, protocol parameters 45 | - **2.1.4** Transaction Build Support – JSON-based builder adjusted for Hydra; validation and balancing 46 | - **2.1.5** Finalization & Documentation – Markdown docs, GitHub issues, testing, demo setup 47 | 48 | 49 | 50 | ### M2.2 - Browser/Nodejs suppport (kuber-client) 51 | 52 | #### Features 53 | - Implementation of Hydra functionalities in JavaScript for the npm package. 54 | - Ability to listen and react to events/transactions on hydra node. 55 | - Submit transactions to hydra node and get transaction status. 56 | - Compatibility with DApps, including wallet connection for transaction signing. 57 | - Extensive test coverage for the npm package. 58 | 59 | #### Deliverables 60 | **2.2.1** Verification of compatibility with existing Kuber/Hydra APIs. 61 | **2.2.2** Client Package - Published npm package and test results in the repository. 62 | 63 | 64 | ### M3: Documentation and Community Engagement 65 | 66 | ### Todos 67 | - Comprehensive documentation for the Hydra-enabled Kuber library, Kuber Playground, and Kuber-Client npm package. 68 | - Tutorials and guides for developers to integrate Hydra features into their applications. 69 | a 70 | ### Deliverables 71 | - Docusaurus deploymennt with docs and tutorials. 72 | 73 | ---------- 74 | 75 | ## Risks and Issues 76 | 77 | - **Hydra WebSocket Latency**: 78 | - Submissions and state transitions may have slow response times. 79 | - **Non-standard API Responses**: 80 | - Some requests (e.g., transaction submission) may result in a `201 Created` instead of `200 OK`, due to asynchronous handling. 81 | - **Hydra Node Setup**: 82 | - Kuber does not manage or deploy Hydra nodes; users must set them up and configure connectivity manually. 83 | 84 | 85 | # 🗓️ Hydra Integration Project Timeline 86 | 87 | 88 | | **Phase** | **Milestone/Task** | **Estimated Duration** | **Start Date** | **End Date** | **Deliverables** | 89 | |-----------|---------------------|------------------------|---------------|--------------|------------------| 90 | | 🔹 M1 | Project Planning & Architecture | 2 weeks | March 1 | March 15 | Architecture document, GitHub setup | 91 | | 🔹 M2.1.1 | Hydra WebSocket Integration | 1.5 weeks | April 6 | April 18 | Hydra WS connection, event parsing | 92 | | 🔹 M2.1.2 | Lifecycle Relay API Endpoints | 1 week | March 16 | March 22 | API: init, commit, close, abort, fanout | 93 | | 🔹 M2.1.3 | Query API Implementation | 1 week | March 23 | March 29 | REST APIs for UTXO, state, protocol params | 94 | | 🔹 M2.1.4 | Hydra Transaction Build Support | 3 weeks | April 19 | May 10 | JSON builder, validation, balancing | 95 | | 🔹 M2.1.5 | Finalization & Documentation | 1 week | May 11 | May 17 | Markdown docs, issue tracking, test/demo | 96 | | 🔹 M2.2.1 | Client-side Hydra Support (npm package) | 3 weeks | May 18 | June 6 | JS package: listen, submit, test, publish | 97 | | 🔹 M2.2.2 | Package Publication | 1 week | June 7 | June 13 | Verified build, published to npm | 98 | | 🔹 M4 | Documentation & Community Engagement | 2 weeks | June 14 | June 27 | Docusaurus deployment, tutorials, examples | 99 | -------------------------------------------------------------------------------- /docs/docs/scenarioTests.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Scenario Tests 6 | 7 | We have tested the following scenarios in Hydra: 8 | 9 | ## 1. Mint a token and close hydra head 10 | 11 | - A token was minted using a plutusV3 always pass script 12 | - The snapshot was confirmed for minting token 13 | - Head was closed 14 | - Error during fanout 15 | 16 | ```json 17 | "postTxError": { 18 | "failureReason": "TxValidationErrorInCardanoMode (ShelleyTxValidationError ShelleyBasedEraConway (ApplyTxError (ConwayUtxowFailure (UtxoFailure (ValueNotConservedUTxO (MaryValue (Coin 325709888) (MultiAsset (fromList [(PolicyID {policyID = ScriptHash \"29c699a1d8dc832504e4ec37a41286176820c9221c5505f7005bae68\"},fromList [(\"4879647261486561645631\",1),(\"e696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab\",1),(\"fce240ccfcb839aa37e5b04206a84530e027b0d3bfb596e7d0685f6a\",1)])]))) (MaryValue (Coin 325709888) (MultiAsset (fromList [(PolicyID {policyID = ScriptHash \"29c699a1d8dc832504e4ec37a41286176820c9221c5505f7005bae68\"},fromList [(\"4879647261486561645631\",1),(\"e696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab\",1),(\"fce240ccfcb839aa37e5b04206a84530e027b0d3bfb596e7d0685f6a\",1)]),(PolicyID {policyID = ScriptHash \"3a888d65f16790950a72daee1f63aa05add6d268434107cfa5b67712\"},fromList [(\"68796472612d6b75626572\",1)])]))))) :| [])))", 19 | "tag": "FailedToPostTx" 20 | } 21 | ``` 22 | 23 | ## 2. Mint a token, burn it and close hydra head 24 | - A token was minted using a plutusV3 always pass script. 25 | - The snapshot was confirmed for minting token 26 | - Token was burnt 27 | - The snapshot was confirmed for burning token 28 | - Head was closed 29 | - Fanout Successful 30 | 31 | ## 3. Pay to script and close hydra head 32 | - Paid **10 ₳** to script address `addr_test1wqag3rt979nep9g2wtdwu8mr4gz6m4kjdpp5zp705km8wys6t2kla` 33 | - Snapshot was confirmed for this transaction 34 | - Head was closed 35 | - Fanout Successful: [21f398e9a5a7661c326036d5e9577b64f28554da9e26387e780a032fdb77e99a](https://preview.cexplorer.io/tx/21f398e9a5a7661c326036d5e9577b64f28554da9e26387e780a032fdb77e99a) 36 | 37 | ## 4. Pay to 500 addresses and close hydra head 38 | - Transactions for 500 addresses were done within the hydra head 39 | - Snapshot was confirmed for each transaction 40 | - Head was closed 41 | - Error during fanout 42 | 43 | ```json 44 | { 45 | "postTxError": { 46 | "failureReason": "ValidationFailure (WrapExUnits {unWrapExUnits = ExUnits' {exUnitsMem' = 0, exUnitsSteps' = 0}}) (CekError An error has occurred:\nThe machine terminated part way through evaluation due to overspending the budget.\nThe budget when the machine terminated was:\n({cpu: 6396337807\n| mem: -2582})\nNegative numbers indicate the overspent budget; note that this only indicates the budget that was needed for the next step, not to run the program to completion.) [] ..." 47 | } 48 | } 49 | ``` -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/abort_head.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Abort Head 3 | --- 4 | 5 | Aborting a Hydra head is a process initiated on the mainchain during the setup phase if the head initialization fails. This failure typically occurs when not all head members successfully post their commit transactions to lock their UTxOs into the head. A party can then post an abort transaction on the mainchain. This transaction transitions the mainchain state machine directly from the initial state to the final state, bypassing the open and closed states. The abort transaction ensures that any UTxOs that were successfully committed are returned to the mainchain and that the head's unique participation tokens are burned, effectively cancelling the head creation and cleaning up the onchain state. 6 | 7 | ![Abort Head](../../static/img/abort.jpg) 8 | -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/close_head.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Close Head 3 | --- 4 | 5 | 6 | Closing a Hydra head means terminating the offchain state channel and settling the final agreed-upon state back onto the mainchain. This process is typically initiated by a head member posting a close transaction on the mainchain, which transitions the mainchain state machine from the open state to the closed state. This transaction includes a certificate representing that party's view of the latest confirmed offchain state. 7 | 8 | ![Close Head](../../static/img/close.jpg) 9 | 10 | -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/commit_utxos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Commit UTxOs 3 | --- 4 | 5 | Each member locks their chosen UTxOs onchain by posting a commit transaction, utilizing their participation token. Finally, a collectCom transaction is posted to aggregate all committed UTxOs into the initial head state and transition the mainchain state machine to the "open" state, enabling offchain processing to commence. 6 | 7 | ![Commit UTxOs](../../static/img/commit.jpg) 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/decommit_utxos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Decommit UTxOs 3 | --- 4 | 5 | Decommitting a UTxO in Hydra means moving it out of the offchain head protocol and back onto the mainchain. This effectively makes the funds or state represented by that UTxO available for standard layer-one operations again. Decommitment can occur when the head is closed, settling the final set of UTxOs back onchain, or incrementally while the head remains open, allowing specific UTxOs to be removed without closing the entire head. 6 | 7 | ![DeCommit UTxOs](../../static/img/decommit.jpg) 8 | -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/fanout_head.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fanout Head 3 | --- 4 | Finally, after the contestation period, a fanout transaction transitions the state machine to the final state, placing the definitive set of UTxOs from the head back onto the mainchain, effectively replacing the initial set that was committed. 5 | 6 | ![Fanout Head](../../static/img/fanout.jpg) 7 | -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/initialize_head.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Initialize Head 3 | --- 4 | 5 | 6 | Initialization of a Hydra head is the process of setting up the offchain state channel for a group of parties. It begins with an initiator posting an initial transaction on the mainchain, which defines the head members and parameters, and forges unique participation tokens for each member. 7 | 8 | ![Initialize Head](../../static/img/init.jpg) 9 | -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/query_protocol_params.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Query Protocol Parameters 3 | --- 4 | Hydra can use its own custom protocol parameters, and it's common to set the transaction fees to zero in these settings to optimize token usage and reduce costs. 5 | 6 | ![Query Protocol Parameters](../../static/img/protocol-parameters.jpg) 7 | -------------------------------------------------------------------------------- /docs/docs/sequenceDiagrams/query_utxos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Query Utxos 3 | --- 4 | 5 | Hydra head maintains a record of available UTxOs that can be spent. 6 | 7 | ![Query UTxOs](../../static/img/utxo.jpg) 8 | -------------------------------------------------------------------------------- /docs/docs/tutorial.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This tutorial walks through the steps to initialize and interact with a Hydra Head using the `kuber-hydra` relay server and client libraries. We’ll demonstrate the process using two Hydra nodes—**Alice** and **Bob**—on the Cardano testnet. 4 | 5 | ## **1. Hydra Node Setup** 6 | 7 | To set up a Hydra Head on the testnet, follow the official Hydra protocol tutorial: 8 | 👉 [Hydra Head Protocol Documentation](https://hydra.family/head-protocol/docs/tutorial) 9 | 10 | In our example setup: 11 | 12 | - **Alice's Hydra Node** runs on `172.16.238.10:4001` 13 | - **Bob's Hydra Node** runs on `172.16.238.20:4002` 14 | 15 | ## **2. Kuber-Hydra Relay Server** 16 | 17 | ### **Repository** 18 | 19 | - GitHub: [kuber](https://github.com/dquadrant/kuber) 20 | 21 | ### **Configuration** 22 | 23 | Create a `.env` file with the following variables (example for Alice): 24 | 25 | ```env 26 | HYDRA_IP=172.16.238.10 27 | HYDRA_PORT=4001 28 | SERVER_PORT=8081 29 | CARDANO_NODE_SOCKET_PATH=/path/to/cardano-node/preview/node.socket 30 | NETWORK=2 31 | ``` 32 | 33 | > The Kuber-Hydra relay API will be accessible at `http://localhost:8081`. 34 | 35 | ## **3. Kuber Client** 36 | 37 | Example repository: [kuber-client-example](https://github.com/cardanoapi/kuber-client-example) 38 | 39 | ### Hydra Service Initialization 40 | 41 | Assuming that the hydra node is running and kuber-hdra server is started on localhost:8081, we can pass the host url to this class constructor to create the service: 42 | 43 | ```ts 44 | import { KuberHydraService } from "kuber-client/service/kuberHydraService"; 45 | 46 | const hydraService = new KuberHydraService("http://localhost:8081"); 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/docs/tutorials/build_tx.md: -------------------------------------------------------------------------------- 1 | 2 | # Building Transactions in Hydra 3 | 4 | **1. Create a Sample Transaction JSON** 5 | 6 | ```ts 7 | const createSampleOutputTx = ( 8 | selectionAddress: string, 9 | outputAddress: string 10 | ) => ({ 11 | selections: [selectionAddress], 12 | outputs: [ 13 | { 14 | address: outputAddress, 15 | value: 2_000_000, 16 | datum: { constructor: 1, fields: [] }, 17 | }, 18 | { 19 | address: outputAddress, 20 | value: 2_000_000, 21 | datum: { constructor: 2, fields: [] }, 22 | }, 23 | ], 24 | }); 25 | ``` 26 | 27 | **2. Generate Address from Key** 28 | 29 | ```ts 30 | import { setup } from "libcardano/lib/cardano/crypto"; 31 | import { Ed25519Key } from "libcardano/cardano/primitives/keys"; 32 | import { ShelleyWallet } from "libcardano/cardano/primitives/address"; 33 | 34 | await setup(); //this is necessary to run Ed25519Key funcitons 35 | 36 | const testWalletSigningKey = await Ed25519Key.fromCardanoCliFile( 37 | path.join("src", "example.sk") 38 | ); 39 | const testWalletAddress = new ShelleyWallet(testWalletSigningKey).addressBech32( 40 | 0 41 | ); // 0 = testnet 42 | ``` 43 | 44 | **3. Create a Hydra Wallet** 45 | 46 | ```ts 47 | async function createHydraWallet( 48 | service: KuberHydraService, 49 | ed25519Key: Ed25519Key, 50 | network: 0 | 1 51 | ): Promise { 52 | try { 53 | const shelleyWallet = new ShelleyWallet(ed25519Key); 54 | const hydraWallet = new HydraWallet(service, shelleyWallet, network); 55 | return hydraWallet; 56 | } catch (error: any) { 57 | return respondWithError(error); 58 | } 59 | } 60 | 61 | const myWallet = await createHydraWallet(hydraService, testWalletSigningKey, 0); 62 | ``` 63 | 64 | > The `HydraWallet` supports CIP-30 functions such as `getUTxO`, `signData`, `signTx`, `submitTx`, etc. 65 | 66 | **4. Build the Transaction** 67 | 68 | ```ts 69 | // For the sake of simplicity, we can fund our own address since the protocol fees are `0`. 70 | const txBody = createSampleOutputTx(testWalletAddress, testWalletAddress); 71 | const buildTxResponse = await hydraService.buildTx(txBody, false); // false = don't submit as we have not signed the transaction yet 72 | ``` 73 | 74 | The response from this function will provide the transaction details in the following format: 75 | 76 | ```json 77 | { 78 | "cborHex": "84a400d90102818258204051dd270c1a51da8645b6c91d23053273547f1f853929fbec5519527e18266d0d0183a300581d60182aeee511419facd4bf4eab7538187288a55a633f579be0cf36897b011a001e8480028201d81843d87b80a300581d60182aeee511419facd4bf4eab7538187288a55a633f579be0cf36897b011a001e8480028201d81843d87b8082581d60182aeee511419facd4bf4eab7538187288a55a633f579be0cf36897b1a00f4240002000ed9010281581c182aeee511419facd4bf4eab7538187288a55a633f579be0cf36897ba0f5f6", 79 | "description": "Ledger Cddl Format", 80 | "hash": "ff6de11af9d0998a85c3eb5333f4afd173a904164630fc0717be8ee819900d4f", 81 | "type": "Unwitnessed Tx ConwayEra" 82 | } 83 | ``` 84 | 85 | **5. Sign and Submit** 86 | 87 | ```ts 88 | const txCborHex = buildTxResponse.cborHex; 89 | const signature = await myWallet.signTx(txCborHex); 90 | ``` 91 | 92 | The wallet's signTx function returns a witness set in cbor hex format. To add this witness set to the transaction, we need to merge the witnesses. 93 | 94 | ```ts 95 | const signedTxHex = txWithMergedSignature(txCborHex, signature); 96 | ``` 97 | 98 | Now, we have the signed transaction that we can submit to the hydra head. 99 | 100 | ```ts 101 | const submissionResult = await myWallet.submitTx(signedTxHex); 102 | console.log("Tx Submitted: ", submissionResult); 103 | ``` 104 | 105 | The response from the wallet is the signed transction cbor hex upon successful submission. -------------------------------------------------------------------------------- /docs/docs/tutorials/close_head.md: -------------------------------------------------------------------------------- 1 | # Closing the Hydra Head 2 | 3 | ```ts 4 | const closeResponse = await hydraService.close(true); 5 | console.log(closeResponse); 6 | ``` 7 | 8 | **Example Response:** 9 | 10 | ```json 11 | { 12 | "contestationDeadline": "2025-05-30T08:11:34Z", 13 | "headId": "84673dfc0cfd3cf404251fa730fbbfef8d8229b9e2f283e59bca2236", 14 | "seq": 17, 15 | "snapshotNumber": 1, 16 | "tag": "HeadIsClosed", 17 | "timestamp": "2025-05-30T08:07:54.34265662Z" 18 | } 19 | ``` 20 | 21 | > 🛠 If you encounter errors on closing, refer to: [Hydra Issue #1039](https://github.com/cardano-scaling/hydra/issues/1039) -------------------------------------------------------------------------------- /docs/docs/tutorials/commit_utxos.md: -------------------------------------------------------------------------------- 1 | 2 | # Committing UTxOs 3 | 4 | Once the Hydra head is initialized, both participating parties must submit their commitments. To do this, define a commit object specifying the UTxOs to be included. If a signing key is provided, the Kuber-Hydra server will generate a signed transaction that can be submitted to the blockchain. The `submit` parameter determines the desired behavior—set it to `true` to submit the transaction immediately, or `false` to receive an unsigned transaction for manual submission later. 5 | 6 | ```ts 7 | const commitment: Commit = { 8 | utxos: [ 9 | "4051dd270c1a51da8645b6c91d23053273547f1f853929fbec5519527e18266d#13", 10 | ], 11 | signKey: { 12 | type: "PaymentSigningKeyShelley_ed25519", 13 | description: "Payment Signing Key", 14 | cborHex: 15 | "5820def9b27d28b25a28156e42a57e1a81d5412517ba2f73784bb230c676dd0d9d65", 16 | }, 17 | }; 18 | const commitResponse = await hydraService.commit(commitment, true); 19 | console.log(commitResponse); 20 | ``` 21 | 22 | On success, you will receive a `Witnessed Tx` in CBOR format. 23 | 24 | ```json 25 | { 26 | "cborHex": "84a800d90102838258204051dd270c1a51da8645b6c91d23053273547f1f853929fbec5519527e18266d0d8258207a169ef2824514edc468e553a816e52dee9516a071e9ec9bdfeb547a2f30c6a4018258207a169ef2824514edc468e553a816e52dee9516a071e9ec9bdfeb547a2f30c6a4030dd90102818258207a169ef2824514edc468e553a816e52dee9516a071e9ec9bdfeb547a2f30c6a40312d90102818258200fd2468a66a0b1cb944cff9512ecfa25cdd2799cb48b07210c449a5ecace267d000182a300581d702043a9f1a685bcf491413a5f139ee42e335157c8c6bc8d9e4018669d01821a0144e7c8a1581c84673dfc0cfd3cf404251fa730fbbfef8d8229b9e2f283e59bca2236a1581ce696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab01028201d81858b3d8799f5820232844b0ebd1f13b62b19bc9ce0a423a84e3d2cc5efa2ac226a96255420d71379fd8799fd8799fd8799f58204051dd270c1a51da8645b6c91d23053273547f1f853929fbec5519527e18266dff0dff583cd8799fd8799fd8799f581c182aeee511419facd4bf4eab7538187288a55a633f579be0cf36897bffd87a80ffa140a1401a01312d00d87980d87a80ffffff581c84673dfc0cfd3cf404251fa730fbbfef8d8229b9e2f283e59bca2236ff82581d60e696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab1a055dc873021a001b55890ed9010281581ce696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab0b58202331de968aa627625c343ae7800e7388d3508b60fb75c545dff8077985b527a707582012e5af821e4510d6651e57185b4dae19e8bd72bf90a8c1e6cd053606cbc46514a200d9010282825820cc13514aae23bd9da11a9032cf7253a1e8f84bc5d077ae973f40f097fdd51f275840853de227e8ddb7aabb026fb1d8cbf8aaab4c2b29eccd975c870bfb64dd4a5a807b8c3fb415b1a6b8a4515815cdb156f3a8a63a404efac5b60d60c2c77787a70d825820d5888ede6fb8cc020ade5147053a9db6d1e25513ed795715decc26f2d22b77b45840a257d2df3741a7b2268e0ef4d48247c17147485ee1aebde8b82acf06655196d7c150701f3f0b82b83ea9eea2bdd7790d5ef735af5251eaf4279be6af535da30705a182000182d87a9f9fd8799fd8799f58204051dd270c1a51da8645b6c91d23053273547f1f853929fbec5519527e18266dff0dffffff821a00d59f801b00000002540be400f5d90103a100a119d90370487964726156312f436f6d6d69745478", 27 | "description": "Ledger Cddl Format", 28 | "hash": "efc69a7604a3b9ce43baad7b062b9d9133517c863cd8b2ee51dcef7b96381f84", 29 | "type": "Witnessed Tx ConwayEra" 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/docs/tutorials/fanout.md: -------------------------------------------------------------------------------- 1 | 2 | # Fanout Transactions to Mainnet 3 | 4 | After the contestation period, finalize and fanout: 5 | 6 | ```ts 7 | const response = await hydraService.fanout(true); 8 | console.log(response); 9 | ``` 10 | 11 | **Example Response:** 12 | 13 | ```json 14 | { 15 | "headId": "84673dfc0cfd3cf404251fa730fbbfef8d8229b9e2f283e59bca2236", 16 | "seq": 22, 17 | "tag": "HeadIsFinalized", 18 | "timestamp": "2025-05-30T08:15:34.750763178Z", 19 | "utxo": { 20 | "4051dd270c1a51da8645b6c91d23053273547f1f853929fbec5519527e18266d#4": { 21 | "address": "addr_test1vqvf72x6j3sr2jtr5p2yu2jr9emzkxpv0gw859yde8kttqsu0vlpf", 22 | "datum": null, 23 | "datumhash": null, 24 | "inlineDatum": null, 25 | "referenceScript": null, 26 | "value": { 27 | "lovelace": 50000000 28 | } 29 | }, 30 | "ff6de11af9d0998a85c3eb5333f4afd173a904164630fc0717be8ee819900d4f#0": { 31 | "address": "addr_test1vqvz4mh9z9qeltx5ha82kafcrpeg3f26vvl40xlqeumgj7cugpfje", 32 | "datum": null, 33 | "inlineDatum": { 34 | "constructor": 2, 35 | "fields": [] 36 | }, 37 | "inlineDatumhash": "ff5f5c41a5884f08c6e2055d2c44d4b2548b5fc30b47efaa7d337219190886c5", 38 | "referenceScript": null, 39 | "value": { 40 | "lovelace": 2000000 41 | } 42 | }, 43 | "ff6de11af9d0998a85c3eb5333f4afd173a904164630fc0717be8ee819900d4f#1": { 44 | "address": "addr_test1vqvz4mh9z9qeltx5ha82kafcrpeg3f26vvl40xlqeumgj7cugpfje", 45 | "datum": null, 46 | "inlineDatum": { 47 | "constructor": 2, 48 | "fields": [] 49 | }, 50 | "inlineDatumhash": "ff5f5c41a5884f08c6e2055d2c44d4b2548b5fc30b47efaa7d337219190886c5", 51 | "referenceScript": null, 52 | "value": { 53 | "lovelace": 2000000 54 | } 55 | }, 56 | "ff6de11af9d0998a85c3eb5333f4afd173a904164630fc0717be8ee819900d4f#2": { 57 | "address": "addr_test1vqvz4mh9z9qeltx5ha82kafcrpeg3f26vvl40xlqeumgj7cugpfje", 58 | "datum": null, 59 | "datumhash": null, 60 | "inlineDatum": null, 61 | "referenceScript": null, 62 | "value": { 63 | "lovelace": 16000000 64 | } 65 | } 66 | } 67 | } 68 | ``` -------------------------------------------------------------------------------- /docs/docs/tutorials/initialize_head.md: -------------------------------------------------------------------------------- 1 | # Initialize Hydra Head 2 | 3 | ```ts 4 | const initResponse = await hydraService.initialize(true); 5 | console.log(initResponse); 6 | ``` 7 | 8 | - The parameter `true` waits indefinitely for a WebSocket response. 9 | - If set to `false`, it waits for 15 seconds and returns a `201 Created` response if no message is received. 10 | 11 | **Example Response:** 12 | 13 | ```json 14 | { 15 | "headId": "84673dfc0cfd3cf404251fa730fbbfef8d8229b9e2f283e59bca2236", 16 | "parties": [ 17 | { 18 | "vkey": "232844b0ebd1f13b62b19bc9ce0a423a84e3d2cc5efa2ac226a96255420d7137" 19 | }, 20 | { 21 | "vkey": "f6c153b29e86166552334ccbc5c2995bf5185561a270ff489e98f03a8dd74e7d" 22 | } 23 | ], 24 | "seq": 2, 25 | "tag": "HeadIsInitializing", 26 | "timestamp": "2025-05-30T07:29:34.370535241Z" 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/docs/tutorials/query/head_state.md: -------------------------------------------------------------------------------- 1 | # Query Head State 2 | 3 | ```ts 4 | const headState = await hydraService.queryHeadState(); 5 | console.log(headState); 6 | ``` 7 | 8 | ```json 9 | { "state": "Partial Commitments Received" } 10 | ``` 11 | 12 | After the other participant has commited, the head state will display the following: 13 | 14 | ```json 15 | { "state": "Open and Ready for Transactions" } 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/docs/tutorials/query/protocol_params.md: -------------------------------------------------------------------------------- 1 | # Query Protocol Parameters 2 | 3 | ```ts 4 | await hydraService.queryProtocolParameters(); 5 | ``` 6 | -------------------------------------------------------------------------------- /docs/docs/tutorials/query/utxos.md: -------------------------------------------------------------------------------- 1 | # Query UTxOs 2 | 3 | ```ts 4 | const utxoResponse = await hydraService.queryUTxOByAddress( 5 | "addr_test1vqvz4mh9z9qeltx5ha82kafcrpeg3f26vvl40xlqeumgj7cugpfje" 6 | ); 7 | console.log(utxoResponse); 8 | ``` 9 | This will return a UTxO type from the libcardano package. 10 | -------------------------------------------------------------------------------- /docs/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import { themes as prismThemes } from "prism-react-renderer"; 2 | import type { Config } from "@docusaurus/types"; 3 | import type * as Preset from "@docusaurus/preset-classic"; 4 | 5 | const config: Config = { 6 | title: "Kuber-Hydra", 7 | tagline: "Streamline your Layer 2 transactions", 8 | favicon: "img/favicon.ico", 9 | 10 | // Set the production url of your site here 11 | url: "https://cadanoapi.github.io", 12 | // Set the // pathname under which your site is served 13 | // For GitHub pages deployment, it is often '//' 14 | baseUrl: "/kuber/hydra_docusaurus/", 15 | 16 | // GitHub pages deployment config. 17 | // If you aren't using GitHub pages, you don't need these. 18 | organizationName: "dQuadrant", // Usually your GitHub org/user name. 19 | projectName: "kuber", // Usually your repo name. 20 | 21 | onBrokenLinks: "throw", 22 | onBrokenMarkdownLinks: "warn", 23 | 24 | // Even if you don't use internationalization, you can use this field to set 25 | // useful metadata like html lang. For example, if your site is Chinese, you 26 | // may want to replace "en" with "zh-Hans". 27 | i18n: { 28 | defaultLocale: "en", 29 | locales: ["en"], 30 | }, 31 | themes: ["@docusaurus/theme-mermaid"], 32 | presets: [ 33 | [ 34 | "classic", 35 | { 36 | docs: { 37 | sidebarPath: "./sidebars.ts", 38 | 39 | editUrl: "https://github.com/dQuadrant/kuber/tree/feat/hydra", 40 | }, 41 | } satisfies Preset.Options, 42 | ], 43 | ], 44 | 45 | themeConfig: { 46 | image: "img/docusaurus-social-card.jpg", 47 | navbar: { 48 | title: "Kuber-Hydra", 49 | logo: { 50 | alt: "My Site Logo", 51 | src: "img/logo.png", 52 | }, 53 | items: [ 54 | { 55 | type: "docSidebar", 56 | sidebarId: "architecture", 57 | position: "left", 58 | label: "Architecture", 59 | }, 60 | { 61 | type: "docSidebar", 62 | sidebarId: "scenarioTests", 63 | position: "left", 64 | label: "Hydra Analysis", 65 | }, 66 | { 67 | type: "docSidebar", 68 | sidebarId: "milestones", 69 | position: "left", 70 | label: "Planning", 71 | }, 72 | { 73 | type: "docSidebar", 74 | sidebarId: "tutorial", 75 | position: "left", 76 | label: "Tutorial", 77 | }, 78 | { 79 | href: "https://github.com/dQuadrant/kuber/tree/feat/hydra", 80 | label: "GitHub", 81 | position: "right", 82 | }, 83 | ], 84 | }, 85 | footer: { 86 | style: "dark", 87 | copyright: `Copyright © ${new Date().getFullYear()} Kuber Hydra`, 88 | }, 89 | prism: { 90 | theme: prismThemes.github, 91 | darkTheme: prismThemes.dracula, 92 | }, 93 | } satisfies Preset.ThemeConfig, 94 | }; 95 | 96 | export default config; 97 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^3.8.0", 19 | "@docusaurus/preset-classic": "^3.8.0", 20 | "@docusaurus/theme-mermaid": "^3.8.0", 21 | "@mdx-js/react": "^3.0.0", 22 | "clsx": "^2.0.0", 23 | "prism-react-renderer": "^2.3.0", 24 | "react": "^18.0.0", 25 | "react-dom": "^18.0.0" 26 | }, 27 | "devDependencies": { 28 | "@docusaurus/module-type-aliases": "^3.8.0", 29 | "@docusaurus/tsconfig": "^3.8.0", 30 | "@docusaurus/types": "^3.8.0", 31 | "typescript": "~5.2.2" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.5%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 3 chrome version", 41 | "last 3 firefox version", 42 | "last 5 safari version" 43 | ] 44 | }, 45 | "engines": { 46 | "node": ">=18.0" 47 | }, 48 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 49 | } 50 | -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | // By default, Docusaurus generates a sidebar from the docs folder structure 15 | architecture: [ 16 | { 17 | type: "doc", 18 | id: "architecture", // Document ID 19 | label: "Architecture", 20 | }, 21 | { 22 | type: "category", 23 | label: "Detailed Sequence Diagrams", // Category label 24 | collapsed: false, // This makes the category open by default 25 | 26 | items: [ 27 | { 28 | type: "doc", 29 | id: "sequenceDiagrams/initialize_head", // Document ID for a sub-doc 30 | }, 31 | { 32 | type: "doc", 33 | id: "sequenceDiagrams/commit_utxos", // Document ID for a sub-doc 34 | }, 35 | { 36 | type: "doc", 37 | id: "sequenceDiagrams/decommit_utxos", // Document ID for a sub-doc 38 | }, 39 | { 40 | type: "doc", 41 | id: "sequenceDiagrams/abort_head", // Document ID for a sub-doc 42 | }, 43 | { 44 | type: "doc", 45 | id: "sequenceDiagrams/close_head", // Document ID for a sub-doc 46 | }, 47 | { 48 | type: "doc", 49 | id: "sequenceDiagrams/query_protocol_params", // Document ID for a sub-doc 50 | }, 51 | { 52 | type: "doc", 53 | id: "sequenceDiagrams/query_utxos", // Document ID for a sub-doc 54 | }, 55 | { 56 | type: "doc", 57 | id: "sequenceDiagrams/fanout_head", // Document ID for a sub-doc 58 | }, 59 | 60 | // { 61 | // type: "doc", 62 | // id: "sequenceDiagramB", // Document ID for another sub-doc 63 | // label: "Sequence Diagram B", 64 | // }, 65 | // { 66 | // type: "doc", 67 | // id: "sequenceDiagramC", // Document ID for another sub-doc 68 | // label: "Sequence Diagram C", 69 | // }, 70 | ], 71 | }, 72 | ], 73 | scenarioTests: [ 74 | { 75 | type: "doc", 76 | id: "hydra_feature_analysis", // Document ID 77 | label: "Hydra Analysis", 78 | }, 79 | ], 80 | tutorial: [ 81 | { 82 | type: "category", 83 | label: "Tutorial", 84 | collapsed: false, 85 | items: [ 86 | { 87 | type: "doc", 88 | id: "tutorial", 89 | }, 90 | { 91 | type: "doc", 92 | id: "tutorials/initialize_head", 93 | }, 94 | { 95 | type: "doc", 96 | id: "tutorials/commit_utxos", 97 | }, 98 | { 99 | type: "category", 100 | label: "Queries", 101 | collapsed: false, 102 | items: [ 103 | { 104 | type: "doc", 105 | id: "tutorials/query/head_state", 106 | }, 107 | { 108 | type: "doc", 109 | id: "tutorials/query/protocol_params", 110 | }, 111 | { 112 | type: "doc", 113 | id: "tutorials/query/utxos", 114 | }, 115 | ], 116 | }, 117 | { 118 | type: "doc", 119 | id: "tutorials/build_tx", 120 | }, 121 | { 122 | type: "doc", 123 | id: "tutorials/close_head", 124 | }, 125 | { 126 | type: "doc", 127 | id: "tutorials/fanout", 128 | }, 129 | ], 130 | }, 131 | ], 132 | milestones: [ 133 | { 134 | type: "doc", 135 | id: "milestones", // Document ID 136 | label: "Milestones", 137 | }, 138 | ], 139 | }; 140 | 141 | export default sidebars; 142 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import Heading from "@theme/Heading"; 3 | import styles from "./styles.module.css"; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: "Intuitive State Monitoring", 14 | Svg: require("@site/static/img/undraw_term-sheet_70lo.svg").default, 15 | description: ( 16 | <> 17 | Easily monitor the hydra head state through an intuitive and 18 | user-friendly api. 19 | 20 | ), 21 | }, 22 | { 23 | title: "Control Your Hydra Head", 24 | Svg: require("@site/static/img/undraw_version-control_eiam.svg").default, 25 | description: ( 26 | <> 27 | Effortlessly initialize your Hydra head, commit and de-commit your 28 | UTxOs, abort when necessary, and seamlessly close and fanout 29 | transactions to the mainchain. 30 | 31 | ), 32 | }, 33 | { 34 | title: "Simplified Wallet Integration and Transaction Building", 35 | Svg: require("@site/static/img/undraw_online-banking_v7ih.svg").default, 36 | description: ( 37 | <> 38 | Easily connect your wallet, create and submit valid hydra transactions 39 | with simple json architecture. 40 | 41 | ), 42 | }, 43 | ]; 44 | 45 | function Feature({ title, Svg, description }: FeatureItem) { 46 | return ( 47 |
48 |
49 | 50 |
51 |
52 | {title} 53 |

{description}

54 |
55 |
56 | ); 57 | } 58 | 59 | export default function HomepageFeatures(): JSX.Element { 60 | return ( 61 |
62 |
63 |
64 | {FeatureList.map((props, idx) => ( 65 | 66 | ))} 67 |
68 |
69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme="dark"] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | 32 | /* Reduce the line height for paragraphs */ 33 | p { 34 | line-height: 1.5; /* Adjust this value as needed */ 35 | margin-bottom: 0.5em; /* Adjust this value as needed */ 36 | } 37 | 38 | /* Reduce the line height and margin for list items */ 39 | li { 40 | line-height: 1.1; /* Adjust this value as needed */ 41 | margin-bottom: 0.5em; /* Adjust this value as needed */ 42 | } 43 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import Link from "@docusaurus/Link"; 3 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 4 | import Layout from "@theme/Layout"; 5 | import HomepageFeatures from "@site/src/components/HomepageFeatures"; 6 | import Heading from "@theme/Heading"; 7 | 8 | import styles from "./index.module.css"; 9 | 10 | function HomepageHeader() { 11 | const { siteConfig } = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 | 16 | {siteConfig.title} 17 | 18 |

{siteConfig.tagline}

19 |
20 | 24 | Architecture 25 | 26 | 30 | Scenario Tests 31 | 32 | 36 | Milestones 37 | 38 | 42 | Tutorial 43 | 44 |
45 | 46 |
47 |
48 | ); 49 | } 50 | 51 | export default function Home(): JSX.Element { 52 | const { siteConfig } = useDocusaurusContext(); 53 | return ( 54 | 58 | 59 |
60 | 61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/abort.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/abort.jpg -------------------------------------------------------------------------------- /docs/static/img/autonomous_agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/autonomous_agent.png -------------------------------------------------------------------------------- /docs/static/img/close.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/close.jpg -------------------------------------------------------------------------------- /docs/static/img/commit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/commit.jpg -------------------------------------------------------------------------------- /docs/static/img/decommit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/decommit.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/fanout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/fanout.jpg -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/hydra-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/hydra-architecture.png -------------------------------------------------------------------------------- /docs/static/img/init.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/init.jpg -------------------------------------------------------------------------------- /docs/static/img/kuber-hydra.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/kuber-hydra.drawio.png -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/logo.png -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/protocol-parameters.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/protocol-parameters.jpg -------------------------------------------------------------------------------- /docs/static/img/utxo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dQuadrant/kuber/95478216721e55d1f976fb2240978500adaef90a/docs/static/img/utxo.jpg -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /kontract-example/kontract-example.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: kontract-example 3 | version: 0.1.0.0 4 | 5 | -- A short (one-line) description of the package. 6 | -- synopsis: 7 | 8 | -- A longer description of the package. 9 | -- description: 10 | 11 | -- A URL where users can report bugs. 12 | -- bug-reports: 13 | 14 | -- The license under which the package is released. 15 | -- license: 16 | author: Sudip 17 | maintainer: sudipbhattarai100@gmail.com 18 | 19 | 20 | executable kontract-example 21 | default-language: Haskell2010 22 | main-is: Main.hs 23 | other-modules: 24 | 25 | -- Plutus.Contract.Wallet.Utils 26 | hs-source-dirs: src 27 | 28 | build-depends: 29 | kuber 30 | , cardano-api 31 | , bytestring 32 | , text 33 | , containers 34 | , base -------------------------------------------------------------------------------- /kontract-example/src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TypeApplications #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | module Main where 4 | import Cardano.Kuber.Console.ConsoleWritable (ConsoleWritable(toConsoleText, toConsoleTextNoPrefix)) 5 | import qualified Data.Text as T 6 | import qualified Data.Set as Set 7 | import Cardano.Kuber.Api 8 | import Cardano.Api 9 | import Control.Monad.IO.Class 10 | import Cardano.Kuber.Data.Parsers 11 | import Cardano.Kuber.Util 12 | import Cardano.Api.Ledger (DRep(..)) 13 | import qualified Debug.Trace as Debug 14 | 15 | remoteKuberConnection :: IO RemoteKuberConnection 16 | remoteKuberConnection = do 17 | (networkName,network) <- getNetworkFromEnv "NETWORK" 18 | createRemoteKuberConnection network "http://localhost:8081" Nothing 19 | 20 | localNodeConnection :: IO ChainConnectInfo 21 | localNodeConnection = chainInfoFromEnv 22 | 23 | printBalanceKontract :: HasChainQueryAPI api => Kontract api BabbageEra FrameworkError () 24 | printBalanceKontract= do 25 | (addr ::AddressInEra BabbageEra) <- kWrapParser $ parseAddressBech32 (T.pack "addr_test1qrmntnd29t3kpnn8uf7d9asr3fzvw7lnah55h52yvaxnfe4g2v2ge520usmkn0zcl46gy38877hej5cnqe6s602xpkyqtpcsrj") 26 | -- drep <- kQueryDRepDistribution (Set.singleton DRepAlwaysAbstain) 27 | -- Debug.traceM(show drep) 28 | tip <- kQueryChainPoint 29 | (utxos ::UTxO BabbageEra) <- kQueryUtxoByAddress (Set.singleton $ addressInEraToAddressAny addr) 30 | liftIO $ do 31 | putStrLn ("Chain is at " ++ (case tip of 32 | ChainPointAtGenesis -> "Genesis" 33 | ChainPoint (SlotNo sn) ha -> "SlotNo:" ++ show sn ++ " BlockHeaderHash:" ++ T.unpack (serialiseToRawBytesHexText ha)) ) 34 | putStrLn $ "Your utxos : \n" ++ toConsoleText " - " utxos 35 | 36 | main :: IO () 37 | main = do 38 | kuberConn <- remoteKuberConnection 39 | result <- evaluateKontract kuberConn printBalanceKontract 40 | case result of 41 | Left e -> putStrLn $ "Unexpected error evaluating printBalance kontract:\n "++ show e 42 | _ -> pure () -------------------------------------------------------------------------------- /kuber-hydra/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for kuber-hydra 2 | 3 | ## 0.1.0.0 -- YYYY-mm-dd 4 | 5 | * First version. Released on an unsuspecting world. 6 | -------------------------------------------------------------------------------- /kuber-hydra/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025, Reeshav Acharya 2 | 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /kuber-hydra/README.md: -------------------------------------------------------------------------------- 1 | # Kuber Hydra 2 | 3 | - [Project Scope and Planning](https://dquadrant.github.io/kuber/hydra_docusaurus/docs/milestones) 4 | - [Architecture](https://dquadrant.github.io/kuber/hydra_docusaurus/docs/architecture) 5 | -------------------------------------------------------------------------------- /kuber-hydra/app/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# OPTIONS_GHC -Wno-name-shadowing #-} 3 | 4 | module Main where 5 | 6 | import Api.Spec (hydraApp) 7 | import Configuration.Dotenv 8 | import Network.Wai.Handler.Warp 9 | import Network.Wai.Handler.WebSockets 10 | import qualified Network.WebSockets as WS 11 | import System.Environment 12 | import Websocket.Aeson 13 | import Websocket.Middleware 14 | import Websocket.SocketConnection 15 | 16 | main :: IO () 17 | main = do 18 | loadFile defaultConfig 19 | hydraIp <- getEnv "HYDRA_IP" 20 | hydraPort <- getEnv "HYDRA_PORT" 21 | serverPort <- getEnv "SERVER_PORT" 22 | putStrLn $ "Starting HTTP and WebSocket server on port " ++ show serverPort 23 | putStrLn $ "Hydra node running on " <> hydraIp <> ":" <> hydraPort 24 | let host = AppConfig hydraIp (read hydraPort) "0.0.0.0" (read serverPort) 25 | noCacheApp = noCacheMiddleware (hydraApp host) 26 | run (read serverPort) $ websocketsOr WS.defaultConnectionOptions (proxyServer host) noCacheApp -------------------------------------------------------------------------------- /kuber-hydra/app/Websocket/Aeson.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE DuplicateRecordFields #-} 4 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} 7 | 8 | {-# HLINT ignore "Use newtype instead of data" #-} 9 | 10 | module Websocket.Aeson where 11 | 12 | import Cardano.Api (ExecutionUnitPrices) 13 | import Cardano.Api.Shelley (ExecutionUnits) 14 | import Data.Aeson 15 | import qualified Data.Aeson as A 16 | import qualified Data.Aeson.Types as Aeson 17 | import qualified Data.Map as M 18 | import qualified Data.Text as T 19 | import Data.Time 20 | import Data.Time.Format.ISO8601 (iso8601ParseM) 21 | import GHC.Generics (Generic) 22 | import GHC.Natural 23 | 24 | data AppConfig = AppConfig 25 | { hydraIp :: String, 26 | hydraPort :: Int, 27 | serverIp :: String, 28 | serverPort :: Int 29 | } 30 | 31 | newtype HydraGetUTxOResponse = HydraGetUTxOResponse 32 | { utxo :: M.Map T.Text A.Value 33 | } 34 | deriving (Generic, Show, ToJSON, FromJSON) 35 | 36 | data HydraCommitTx = HydraCommitTx 37 | { cborHex :: String, 38 | description :: String, 39 | txId :: String 40 | } 41 | deriving (Show, Generic, FromJSON, ToJSON) 42 | 43 | data WSMessage = WSMessage 44 | { tag :: T.Text, 45 | seq :: Maybe Int, 46 | timestamp :: UTCTime 47 | } 48 | deriving (Generic, Show, ToJSON) 49 | 50 | data ContentsAndTag = ContentsAndTag 51 | { contents :: A.Value, 52 | tag :: T.Text 53 | } 54 | deriving (Generic, Show, ToJSON, FromJSON) 55 | 56 | data HydraState = HydraState 57 | { state :: ContentsAndTag 58 | } 59 | deriving (Generic, Show, ToJSON, FromJSON) 60 | 61 | data InitializedHeadParameters = InitializedHeadParameters 62 | { parties :: [VKeys] 63 | } 64 | deriving (Show, Generic, ToJSON, FromJSON) 65 | 66 | data InitializedHeadContents = InitializedHeadContents 67 | { parameters :: InitializedHeadParameters, 68 | pendingCommits :: Maybe [VKeys] 69 | } 70 | deriving (Show, Generic, ToJSON, FromJSON) 71 | 72 | data VKeys = VKeys 73 | { vkey :: T.Text 74 | } 75 | deriving (Show, Generic, ToJSON, FromJSON) 76 | 77 | data InitializedHeadContentsAndTag = InitializedHeadContentsAndTag 78 | { tag :: T.Text, 79 | contents :: InitializedHeadContents 80 | } 81 | deriving (Show, Generic, ToJSON, FromJSON) 82 | 83 | data InitializedHeadResponse = InitializedHeadResponse 84 | { seq :: Maybe Int, 85 | timestamp :: UTCTime, 86 | state :: InitializedHeadContentsAndTag 87 | } 88 | deriving (Show, Generic, ToJSON, FromJSON) 89 | 90 | instance FromJSON WSMessage where 91 | parseJSON = withObject "WSMessage" $ \v -> do 92 | tagVal <- v .: "tag" 93 | seqVal <- v Aeson..:? "seq" 94 | tsVal <- v .: "timestamp" 95 | ts <- iso8601ParseM tsVal 96 | return $ WSMessage tagVal seqVal ts 97 | 98 | data HydraProtocolParameters = HydraProtocolParameters 99 | { collateralPercentage :: Maybe Natural, 100 | committeeMaxTermLength :: Maybe Int, 101 | committeeMinSize :: Maybe Int, 102 | costModels :: M.Map T.Text [Int], 103 | dRepActivity :: Maybe Int, 104 | dRepDeposit :: Maybe Int, 105 | dRepVotingThresholds :: Maybe DRepVotingThresholds, 106 | executionUnitPrices :: Maybe ExecutionUnitPrices, 107 | govActionDeposit :: Maybe Int, 108 | govActionLifetime :: Maybe Int, 109 | maxBlockBodySize :: Natural, 110 | maxBlockExecutionUnits :: Maybe ExecutionUnits, 111 | maxBlockHeaderSize :: Natural, 112 | maxCollateralInputs :: Maybe Natural, 113 | maxTxExecutionUnits :: Maybe ExecutionUnits, 114 | maxTxSize :: Natural, 115 | maxValueSize :: Maybe Natural, 116 | minFeeRefScriptCostPerByte :: Maybe Int, 117 | minPoolCost :: Int, 118 | monetaryExpansion :: Rational, 119 | poolPledgeInfluence :: Rational, 120 | poolRetireMaxEpoch :: Int, 121 | poolVotingThresholds :: Maybe PoolVotingThresholds, 122 | protocolVersion :: Maybe ProtocolVersion, 123 | extraPraosEntropy :: Maybe Int, 124 | stakeAddressDeposit :: Int, 125 | stakePoolDeposit :: Int, 126 | stakePoolTargetNum :: Int, 127 | minUTxOValue :: Maybe Int, 128 | decentralization :: Maybe Rational, 129 | treasuryCut :: Rational, 130 | txFeeFixed :: Int, 131 | txFeePerByte :: Int, 132 | utxoCostPerByte :: Maybe Int 133 | } 134 | deriving (Show, Generic, ToJSON) 135 | 136 | instance FromJSON HydraProtocolParameters 137 | 138 | data DRepVotingThresholds = DRepVotingThresholds 139 | { committeeNoConfidence :: Maybe Double, 140 | committeeNormal :: Maybe Double, 141 | hardForkInitiation :: Maybe Double, 142 | motionNoConfidence :: Maybe Double, 143 | ppEconomicGroup :: Maybe Double, 144 | ppGovGroup :: Maybe Double, 145 | ppNetworkGroup :: Maybe Double, 146 | ppTechnicalGroup :: Maybe Double, 147 | treasuryWithdrawal :: Maybe Double, 148 | updateToConstitution :: Maybe Double 149 | } 150 | deriving (Show, Generic, ToJSON) 151 | 152 | instance FromJSON DRepVotingThresholds 153 | 154 | data PoolVotingThresholds = PoolVotingThresholds 155 | { committeeNoConfidence :: Maybe Double, 156 | committeeNormal :: Maybe Double, 157 | hardForkInitiation :: Maybe Double, 158 | motionNoConfidence :: Maybe Double, 159 | ppSecurityGroup :: Maybe Double 160 | } 161 | deriving (Show, Generic, ToJSON) 162 | 163 | instance FromJSON PoolVotingThresholds 164 | 165 | data ProtocolVersion = ProtocolVersion 166 | { major :: Natural, 167 | minor :: Natural 168 | } 169 | deriving (Show, Generic, ToJSON) 170 | 171 | instance FromJSON ProtocolVersion 172 | 173 | data HeadState 174 | = HeadIsIdle 175 | | WaitingCommitments 176 | | PartiallyCommitted 177 | | HeadIsReady 178 | | HeadIsClosed 179 | | HeadIsContested 180 | 181 | newtype HydraStateResponse = HydraStateResponse 182 | { state :: T.Text 183 | } 184 | deriving (Show, Generic, ToJSON) -------------------------------------------------------------------------------- /kuber-hydra/app/Websocket/Forwarder.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# OPTIONS_GHC -Wno-incomplete-patterns #-} 5 | 6 | module Websocket.Forwarder where 7 | 8 | import Cardano.Kuber.Api (ErrorType (TxSubmissionError), FrameworkError (FrameworkError)) 9 | import Cardano.Kuber.Data.Models 10 | import Data.Aeson 11 | import qualified Data.Aeson as A 12 | import qualified Data.Aeson.Text as AT 13 | import qualified Data.Text as T 14 | import qualified Data.Text.Lazy as TL 15 | import Websocket.Aeson 16 | import Websocket.SocketConnection 17 | 18 | data Action 19 | = InitializeHead 20 | | CommitUTxO 21 | | DeCommitUTxO 22 | | Abort 23 | | GetUTxO 24 | | CloseHead 25 | | ContestHead 26 | | FanOut 27 | | NewTx 28 | 29 | generateResponseTag :: Action -> [(T.Text, Int)] 30 | generateResponseTag action = case action of 31 | InitializeHead -> [("HeadIsInitializing", 200)] 32 | CommitUTxO -> [("", 00)] 33 | DeCommitUTxO -> [("DecommitRequested", 201), ("DecommitApproved", 201), ("DecommitFinalized", 200)] 34 | Abort -> [("HeadIsAborted", 200)] 35 | GetUTxO -> [("GetUTxOResponse", 200)] 36 | CloseHead -> [("HeadIsClosed", 200)] 37 | ContestHead -> [("HeadIsContested", 200)] 38 | FanOut -> [("HeadIsFinalized", 200)] 39 | NewTx -> [("SnapshotConfirmed", 200)] 40 | 41 | hydraHeadInitialized :: T.Text 42 | hydraHeadInitialized = T.pack "Hydra Head Initialized" 43 | 44 | hydraHeadAborted :: T.Text 45 | hydraHeadAborted = T.pack "Hydra Head Aborted" 46 | 47 | sendCommandToHydraNodeSocket :: AppConfig -> Action -> Bool -> IO (T.Text, Int) 48 | sendCommandToHydraNodeSocket appConfig message wait = do 49 | let responseTag = generateResponseTag message 50 | case message of 51 | InitializeHead -> forwardCommands appConfig "{\"tag\": \"Init\"}" responseTag wait 52 | Abort -> forwardCommands appConfig "{\"tag\": \"Abort\"}" responseTag wait 53 | GetUTxO -> forwardCommands appConfig "{\"tag\": \"GetUTxO\"}" responseTag wait 54 | CloseHead -> forwardCommands appConfig "{\"tag\": \"Close\"}" responseTag wait 55 | ContestHead -> forwardCommands appConfig "{\"tag\": \"Contest\"}" responseTag wait 56 | FanOut -> forwardCommands appConfig "{\"tag\": \"Fanout\"}" responseTag wait 57 | 58 | submitHydraTx :: AppConfig -> TxModal -> Bool -> IO (Either FrameworkError TxModal) 59 | submitHydraTx appConfig txm wait = do 60 | hydraResponse <- forwardCommands appConfig newTxCommand (generateResponseTag NewTx) wait 61 | case snd hydraResponse of 62 | 200 -> pure $ Right txm 63 | x -> 64 | pure $ 65 | Left $ 66 | FrameworkError TxSubmissionError $ 67 | "Hydra responsded with status: " <> show x <> " and message: " <> show (fst hydraResponse) 68 | where 69 | newTxCommand = 70 | TL.toStrict . AT.encodeToLazyText $ 71 | A.object 72 | [ "tag" .= ("NewTx" :: T.Text), 73 | "transaction" .= txm 74 | ] 75 | 76 | -- [\"Init\", 77 | -- \"Abort\", 78 | -- \"NewTx\", 79 | -- \"GetUTxO\", 80 | -- \"Decommit\", 81 | -- \"Close\", 82 | -- \"Contest\", 83 | -- \"Fanout\"], 84 | -------------------------------------------------------------------------------- /kuber-hydra/app/Websocket/Middleware.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | 3 | module Websocket.Middleware where 4 | 5 | import qualified Data.Aeson as A 6 | import qualified Data.ByteString.Char8 as BS8 7 | import qualified Data.CaseInsensitive as CI 8 | import Network.HTTP.Types 9 | import Network.Wai 10 | import Servant 11 | 12 | hPragma :: HeaderName 13 | hPragma = CI.mk (BS8.pack "Pragma") 14 | 15 | hExpires :: HeaderName 16 | hExpires = CI.mk (BS8.pack "Expires") 17 | 18 | noCacheMiddleware :: Middleware 19 | noCacheMiddleware app req sendResponse = 20 | app req $ \res -> 21 | let headers = 22 | [ (hCacheControl, BS8.pack "no-store, no-cache, must-revalidate, max-age=0"), 23 | (hPragma, BS8.pack "no-cache"), 24 | (hExpires, BS8.pack "0") 25 | ] 26 | res' = mapResponseHeaders (++ headers) res 27 | in sendResponse res' 28 | 29 | errorMiddleware :: (Eq a, Num a) => a -> ServerError 30 | errorMiddleware status 31 | | status == 300 = err300 32 | | status == 301 = err301 33 | | status == 302 = err302 34 | | status == 303 = err303 35 | | status == 304 = err304 36 | | status == 305 = err305 37 | | status == 307 = err307 38 | | status == 400 = err400 39 | | status == 401 = err401 40 | | status == 402 = err402 41 | | status == 403 = err403 42 | | status == 404 = err404 43 | | status == 405 = err405 44 | | status == 406 = err406 45 | | status == 407 = err407 46 | | status == 409 = err409 47 | | status == 410 = err410 48 | | status == 411 = err411 49 | | status == 412 = err412 50 | | status == 413 = err413 51 | | status == 414 = err414 52 | | status == 415 = err415 53 | | status == 416 = err416 54 | | status == 417 = err417 55 | | status == 418 = err418 56 | | status == 422 = err422 57 | | status == 429 = err429 58 | | status == 500 = err500 59 | | status == 501 = err501 60 | | status == 502 = err502 61 | | status == 503 = err503 62 | | status == 504 = err504 63 | | status == 505 = err505 64 | | otherwise = err500 65 | 66 | type UVerbResponseTypes = 67 | '[ WithStatus 200 A.Value, 68 | WithStatus 201 A.Value, 69 | WithStatus 300 A.Value, 70 | WithStatus 301 A.Value, 71 | WithStatus 302 A.Value, 72 | WithStatus 303 A.Value, 73 | WithStatus 304 A.Value, 74 | WithStatus 305 A.Value, 75 | WithStatus 307 A.Value, 76 | WithStatus 400 A.Value, 77 | WithStatus 401 A.Value, 78 | WithStatus 402 A.Value, 79 | WithStatus 403 A.Value, 80 | WithStatus 404 A.Value, 81 | WithStatus 405 A.Value, 82 | WithStatus 406 A.Value, 83 | WithStatus 407 A.Value, 84 | WithStatus 409 A.Value, 85 | WithStatus 410 A.Value, 86 | WithStatus 411 A.Value, 87 | WithStatus 412 A.Value, 88 | WithStatus 413 A.Value, 89 | WithStatus 414 A.Value, 90 | WithStatus 415 A.Value, 91 | WithStatus 416 A.Value, 92 | WithStatus 417 A.Value, 93 | WithStatus 418 A.Value, 94 | WithStatus 422 A.Value, 95 | WithStatus 429 A.Value, 96 | WithStatus 500 A.Value, 97 | WithStatus 501 A.Value, 98 | WithStatus 502 A.Value, 99 | WithStatus 503 A.Value, 100 | WithStatus 504 A.Value, 101 | WithStatus 505 A.Value 102 | ] -------------------------------------------------------------------------------- /kuber-hydra/app/Websocket/Utils.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE TypeApplications #-} 4 | {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} 5 | 6 | {-# HLINT ignore "Use lambda-case" #-} 7 | 8 | module Websocket.Utils where 9 | 10 | import Cardano.Api 11 | import Cardano.Kuber.Api 12 | import Cardano.Kuber.Data.Parsers 13 | import qualified Data.Aeson as A 14 | import qualified Data.Aeson.Key as K 15 | import qualified Data.Aeson.KeyMap as KM 16 | import Data.ByteString (fromStrict) 17 | import qualified Data.ByteString.Char8 as BS8 18 | import qualified Data.ByteString.Lazy as BSL 19 | import Data.Either 20 | import Data.List 21 | import qualified Data.Map as M 22 | import qualified Data.Set as Set 23 | import qualified Data.Text as T 24 | import qualified Data.Text.Encoding as TE 25 | import qualified Data.Text.Lazy as TL 26 | import qualified Data.Text.Lazy.Encoding as TLE 27 | import Websocket.Aeson 28 | 29 | textToJSON :: T.Text -> Either FrameworkError A.Value 30 | textToJSON jsonText = do 31 | let jsonBytes = TLE.encodeUtf8 (TL.fromStrict jsonText) 32 | case A.eitherDecode jsonBytes of 33 | Right val -> Right val 34 | Left _ -> Left $ FrameworkError ParserError $ "Could not parse to JSON : " <> T.unpack jsonText 35 | 36 | bytestringToJSON :: BSL.ByteString -> Either FrameworkError A.Value 37 | bytestringToJSON bs = case A.eitherDecode bs of 38 | Right val -> Right val 39 | Left _ -> Left $ FrameworkError ParserError $ "Could not parse to JSON : " <> BS8.unpack (BSL.toStrict bs) 40 | 41 | eitherObjectToJSON :: (ToJSON a) => Either FrameworkError a -> IO (Either FrameworkError A.Value) 42 | eitherObjectToJSON obj = case obj of 43 | Left fe -> pure $ Left fe 44 | Right obj' -> case bytestringToJSON $ A.encode obj' of 45 | Left fe -> pure $ Left fe 46 | Right val -> pure $ Right val 47 | 48 | jsonToText :: A.Value -> T.Text 49 | jsonToText = TL.toStrict . TLE.decodeUtf8 . A.encode 50 | 51 | textToLazyByteString :: T.Text -> BSL.ByteString 52 | textToLazyByteString = BSL.fromStrict . TE.encodeUtf8 53 | 54 | createHydraStateResponseAeson :: HeadState -> IO HydraStateResponse 55 | createHydraStateResponseAeson hs = 56 | let stateText = T.pack $ case hs of 57 | HeadIsIdle -> "Head is Idle" 58 | HeadIsContested -> "Head is Contested" 59 | WaitingCommitments -> "Initialized and Waiting For Commitments" 60 | PartiallyCommitted -> "Partial Commitments Received" 61 | HeadIsReady -> "Open and Ready for Transactions" 62 | HeadIsClosed -> "Head is Closed" 63 | in pure $ HydraStateResponse stateText 64 | 65 | parsedTxAnyEra :: BS8.ByteString -> Either FrameworkError (Tx ConwayEra) 66 | parsedTxAnyEra bs = 67 | case parseRawTxInAnyEra bs of 68 | Just (InAnyCardanoEra ConwayEra tx) -> Right (tx :: Tx ConwayEra) 69 | Just (InAnyCardanoEra _ _) -> Left $ FrameworkError ParserError "Unexpected era, expected ConwayEra" 70 | Nothing -> Left $ FrameworkError ParserError "Error parsing transaction cbor" 71 | 72 | createUTxOSchema :: [TxIn] -> IO (Either FrameworkError T.Text) 73 | createUTxOSchema utxos = do 74 | localChain <- chainInfoFromEnv 75 | result <- evaluateKontract localChain (getUtxoDetails @ChainConnectInfo @ConwayEra utxos) 76 | case result of 77 | Left err -> pure $ Left err 78 | Right res -> case res of 79 | UTxO txinMap -> do 80 | let txInKeys = M.keys txinMap 81 | allTxInsPresent = utxos `isSubsetOf` txInKeys 82 | if allTxInsPresent 83 | then 84 | pure $ Right $ (T.pack . BS8.unpack . BSL.toStrict . A.encode) res 85 | else 86 | pure $ Left $ FrameworkError NodeQueryError $ "Error Querying UTxOs : " <> show (utxos `notPresentIn` txInKeys) 87 | 88 | notPresentIn :: (Eq a) => [a] -> [a] -> [a] 89 | notPresentIn a b = filter (`notElem` b) a 90 | 91 | getUtxoDetails :: 92 | (HasChainQueryAPI a, IsTxBuilderEra era) => 93 | [TxIn] -> 94 | Kontract a w FrameworkError (UTxO era) 95 | getUtxoDetails utxoList = do 96 | kQueryUtxoByTxin (Set.fromList utxoList) 97 | 98 | listOfTextToTxIn :: [T.Text] -> IO (Either FrameworkError [TxIn]) 99 | listOfTextToTxIn txins = do 100 | let parsedList = 101 | map 102 | ( \t -> case parseTxIn t of 103 | Just txin -> Right txin 104 | Nothing -> Left t 105 | ) 106 | txins 107 | (correctTxIns, incorrectTexts) = (rights parsedList, lefts parsedList) 108 | if not (null incorrectTexts) 109 | then 110 | pure $ Left $ FrameworkError ParserError $ "Could not parse the following to TxIn: " <> show incorrectTexts 111 | else 112 | pure $ Right correctTxIns 113 | 114 | listOfTxInToText :: [TxIn] -> [T.Text] 115 | listOfTxInToText = map (\(TxIn hash (TxIx num)) -> serialiseToRawBytesHexText hash <> "#" <> T.pack (show $ toInteger num)) 116 | 117 | listOfTextToAddressInEra :: [T.Text] -> IO (Either FrameworkError [AddressInEra ConwayEra]) 118 | listOfTextToAddressInEra textAddresses = do 119 | let parsedList = 120 | map 121 | ( \a -> case parseAddress a of 122 | Just addr -> Right addr 123 | Nothing -> Left a 124 | ) 125 | textAddresses 126 | (correctAddresses, incorrectTexts) = (rights parsedList :: [AddressInEra ConwayEra], lefts parsedList) 127 | if not (null incorrectTexts) 128 | then 129 | pure $ Left $ FrameworkError ParserError $ "Could not parse the following to Address: " <> show incorrectTexts 130 | else 131 | pure $ Right correctAddresses 132 | 133 | isSubsetOf :: (Eq a) => [a] -> [a] -> Bool 134 | isSubsetOf b a = 135 | let missing = nub b \\ a 136 | in null missing 137 | 138 | parseUTxO :: BSL.ByteString -> Either FrameworkError (UTxO ConwayEra) 139 | parseUTxO bs = case A.decode bs :: Maybe (UTxO ConwayEra) of 140 | Just x -> Right x 141 | Nothing -> Left $ FrameworkError ParserError "parseUTxO: Failulre parsing UTxO Json schema to UTxO ConwayEra" 142 | 143 | utxoFromUTxOMap :: [M.Map T.Text A.Value] -> [Either FrameworkError (UTxO ConwayEra)] 144 | utxoFromUTxOMap = map ((parseUTxO . A.encode) . (\x -> KM.fromList [(K.fromText k, v) | (k, v) <- M.toList x])) 145 | 146 | listOfUTxOToSingleUTxO :: [UTxO ConwayEra] -> UTxO ConwayEra 147 | listOfUTxOToSingleUTxO utxos = 148 | UTxO $ mconcat [m | UTxO m <- utxos] 149 | 150 | setOfAddressAnyToAddressesInConwayEra :: Set.Set AddressAny -> [AddressInEra ConwayEra] 151 | setOfAddressAnyToAddressesInConwayEra addrs = 152 | map 153 | ( \anyAddress -> case anyAddress of 154 | AddressShelley addrShelley -> shelleyAddressInEra shelleyBasedEra addrShelley 155 | AddressByron addrByron -> byronAddressInEra addrByron 156 | ) 157 | (Set.toList addrs) -------------------------------------------------------------------------------- /kuber-hydra/kuber-hydra.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | -- The cabal-version field refers to the version of the .cabal specification, 3 | -- and can be different from the cabal-install (the tool) version and the 4 | -- Cabal (the library) version you are using. As such, the Cabal (the library) 5 | -- version used must be equal or greater than the version stated in this field. 6 | -- Starting from the specification version 2.2, the cabal-version field must be 7 | -- the first thing in the cabal file. 8 | 9 | -- Initial package description 'kuber-hydra' generated by 10 | -- 'cabal init'. For further documentation, see: 11 | -- http://haskell.org/cabal/users-guide/ 12 | -- 13 | -- The name of the package. 14 | name: kuber-hydra 15 | 16 | -- The package version. 17 | -- See the Haskell package versioning policy (PVP) for standards 18 | -- guiding when and how versions should be incremented. 19 | -- https://pvp.haskell.org 20 | -- PVP summary: +-+------- breaking API changes 21 | -- | | +----- non-breaking API additions 22 | -- | | | +--- code changes with no API change 23 | version: 0.1.0.0 24 | 25 | -- A short (one-line) description of the package. 26 | -- synopsis: 27 | 28 | -- A longer description of the package. 29 | -- description: 30 | 31 | -- The license under which the package is released. 32 | license: BSD-3-Clause 33 | 34 | -- The file containing the license text. 35 | license-file: LICENSE 36 | 37 | -- The package author(s). 38 | author: Reeshav Acharya 39 | 40 | -- An email address to which users can send suggestions, bug reports, and patches. 41 | maintainer: reeshav99acharya@gmail.com 42 | 43 | -- A copyright notice. 44 | -- copyright: 45 | build-type: Simple 46 | 47 | -- Extra doc files to be distributed with the package, such as a CHANGELOG or a README. 48 | extra-doc-files: CHANGELOG.md 49 | 50 | -- Extra source files to be distributed with the package, such as examples, or a tutorial module. 51 | -- extra-source-files: 52 | 53 | common warnings 54 | ghc-options: -Wall 55 | -threaded 56 | 57 | executable kuber-hydra 58 | -- Import common warning flags. 59 | import: warnings 60 | 61 | -- .hs or .lhs file containing the Main module. 62 | main-is: Main.hs 63 | 64 | -- Modules included in this executable, other than Main. 65 | other-modules: Websocket.Forwarder 66 | Websocket.Commands 67 | Websocket.Utils 68 | Websocket.SocketConnection 69 | Websocket.TxBuilder 70 | Websocket.Aeson 71 | Websocket.Middleware 72 | Api.Spec 73 | 74 | -- LANGUAGE extensions used by modules in this package. 75 | -- other-extensions: 76 | 77 | -- Other library packages from which modules are imported. 78 | build-depends: base ^>=4.17.0.0 79 | , websockets 80 | , cardano-api 81 | , plutus-ledger-api 82 | , plutus-tx 83 | , aeson 84 | , kuber 85 | , text 86 | , bytestring 87 | , base16-bytestring 88 | , blaze-markup 89 | , blaze-html 90 | , network 91 | , http-conduit 92 | , wai-extra 93 | , wai-cors 94 | , wai-middleware-static 95 | , wai-websockets 96 | , servant 97 | , servant-server 98 | , servant-exceptions 99 | , wai 100 | , warp 101 | , servant-exceptions-server 102 | , http-types 103 | , http-media 104 | , cardano-crypto-class 105 | , cardano-ledger-core 106 | , quickcheck-arbitrary-adt 107 | , lens 108 | , lens-aeson 109 | , async 110 | , time 111 | , QuickCheck 112 | , formatting 113 | , cardano-ledger-binary 114 | , cardano-binary 115 | , cardano-ledger-shelley 116 | , cborg 117 | , containers 118 | , text-conversions 119 | , case-insensitive 120 | , servant-client 121 | , unordered-containers 122 | , dotenv 123 | -- Directories containing source files. 124 | hs-source-dirs: app 125 | 126 | -- Base language which the package is written in. 127 | default-language: Haskell2010 128 | -------------------------------------------------------------------------------- /kuber-server/app/Main.hs: -------------------------------------------------------------------------------- 1 | {-# HLINT ignore "Use newtype instead of data" #-} 2 | {-# LANGUAGE NumericUnderscores #-} 3 | {-# LANGUAGE GADTs #-} 4 | module Main where 5 | 6 | 7 | import Network.Wai.Handler.Warp (run, setPort, defaultSettings, setHost, runSettings) 8 | import Kuber.Server.Spec (appWithBackenAndEra) 9 | import Cardano.Kuber.Api (chainInfoFromEnv, throwFrameworkError, ChainConnectInfo, HasChainQueryAPI (kQueryChainPoint), HasCardanoQueryApi(kQueryCurrentEra), evaluateKontract, FrameworkError (..)) 10 | import System.Environment (getArgs, lookupEnv) 11 | import Cardano.Kuber.Util (timestampToSlot) 12 | import Data.Text (stripStart) 13 | import Data.Data (Data) 14 | import Data.Typeable (Typeable) 15 | import Text.Read (readMaybe) 16 | import Data.String (IsString(..)) 17 | import System.IO 18 | import qualified Data.ByteString.Lazy.Char8 as L8 19 | import Network.HTTP.Simple (httpLBS, HttpException (HttpExceptionRequest)) 20 | import Network.HTTP.Client.Conduit (Response(responseStatus, responseBody), HttpException (HttpExceptionRequest, InvalidUrlException), Request (requestBody)) 21 | import System.Exit (exitFailure) 22 | import Network.HTTP.Types (status200) 23 | import Control.Exception (try, catch) 24 | import Data.Function ((&)) 25 | 26 | 27 | import Options.Applicative 28 | import Data.Semigroup ((<>)) 29 | import Cardano.Api (BabbageEraOnwards (BabbageEraOnwardsBabbage, BabbageEraOnwardsConway), AnyCardanoEra (AnyCardanoEra), CardanoEra (..), ConwayEraOnwards (ConwayEraOnwardsConway)) 30 | import Control.Concurrent (threadDelay) 31 | import Data.Char (toLower, toTitle) 32 | import Data.Maybe (fromMaybe) 33 | 34 | data KuberConfig = KuberConfig 35 | { host :: Maybe String 36 | , port :: Int 37 | , healthCheckUrl :: String 38 | , healthCheck :: Bool 39 | } 40 | 41 | sample :: Parser KuberConfig 42 | sample = KuberConfig 43 | <$> option auto ( 44 | long "host" 45 | <> short 'H' 46 | <> metavar "IP-Address" 47 | <> help "IP Address to bind to" 48 | <> showDefaultWith (const "Listen on all available intefaces") 49 | <> value Nothing 50 | ) 51 | <*> option auto 52 | ( long "port" 53 | <> short 'p' 54 | <> help "Port to listen on" 55 | <> showDefault 56 | <> value 8081) 57 | <*> option auto 58 | ( long "url" 59 | <> help "Url for health-check operation" 60 | <> showDefaultWith (const "http://127.0.0.1:8081/api/v3/chain-point") 61 | <> value "http://127.0.0.1:8081/api/v3/chain-point" 62 | <> metavar "URL" ) 63 | <*> switch ( 64 | long "healthcheck" 65 | <> help "Perform health-check request on kuber server" 66 | ) 67 | 68 | opts = info (sample <**> helper) 69 | ( fullDesc 70 | <> progDesc "Kuber Server" 71 | ) 72 | main :: IO () 73 | main = do 74 | 75 | KuberConfig hostStr port healthCheckUrl doHealthCheck <- execParser opts 76 | 77 | if doHealthCheck 78 | then 79 | performRequest healthCheckUrl 80 | 81 | else do 82 | -- enable line buffering for instantaneous logs when kuber-server is run in docker container 83 | hSetBuffering stdout LineBuffering 84 | dcinfo <- chainInfoFromEnv 85 | let settings = setPort port defaultSettings 86 | let settings2 = (case hostStr of 87 | Nothing -> settings 88 | Just s -> setHost (fromString s) settings ) 89 | app <- appWithEra dcinfo 90 | 91 | putStrLn $ "Server started listening on port " ++ show port ++ "." 92 | 93 | runSettings settings2 app 94 | 95 | queryNodeEra :: ChainConnectInfo -> IO AnyCardanoEra 96 | queryNodeEra cinfo = do 97 | era <- evaluateKontract cinfo kQueryCurrentEra 98 | case era of 99 | Left fe -> do 100 | case fe of 101 | FrameworkError et s -> putStrLn $ show et ++ " -> " ++ s 102 | FrameworkErrors fes -> print fes 103 | putStrLn "Retrying Node connection in 10 seconds ..." 104 | threadDelay 10_000_000 105 | queryNodeEra cinfo 106 | Right ace ->pure ace 107 | 108 | performRequest :: String -> IO () 109 | performRequest url = do 110 | res <- catch (httpLBS (fromString url)) exceptionHandler 111 | if responseStatus res /= status200 112 | then do 113 | putStr $ "Response " ++ show (responseStatus res) ++" : " 114 | L8.putStr $ responseBody res 115 | exitFailure 116 | else L8.putStr $ responseBody res 117 | where 118 | exceptionHandler :: HttpException -> IO a 119 | exceptionHandler ex = do 120 | case ex of 121 | HttpExceptionRequest re hec -> putStr (url ++": " ++ show hec) 122 | InvalidUrlException s str -> putStr $ str ++ ": " ++ s 123 | exitFailure 124 | 125 | 126 | defaultEra :: String 127 | defaultEra = "Conway" 128 | 129 | appWithEra dcinfo = do 130 | -- Try to lookup the environment variable 131 | maybeEra <- lookupEnv "CARDANO_ERA" 132 | (AnyCardanoEra nodeEra) <- queryNodeEra dcinfo 133 | eraStr <- case nodeEra of 134 | BabbageEra -> do 135 | putStrLn "Connected to Node at Babbage era" 136 | pure "babbage" 137 | ConwayEra -> do 138 | putStrLn "Connected to Node at Conway era" 139 | pure "conway" 140 | era -> do 141 | putStrLn $ "Node is at " ++ show era ++" Kuber will start in Conway era" 142 | pure "conway" 143 | era <- case maybeEra of 144 | Nothing -> pure eraStr 145 | Just era -> do 146 | putStrLn$ "Starting Kuber with " ++ era ++" era support" 147 | pure era 148 | 149 | pure $ case map toLower era of 150 | "conway" -> appWithBackenAndEra dcinfo BabbageEraOnwardsConway 151 | "babbage" -> appWithBackenAndEra dcinfo BabbageEraOnwardsBabbage 152 | _ -> error $ "Invalid value of era : " ++ era -------------------------------------------------------------------------------- /kuber-server/kuber-server.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: kuber-server 3 | version: 3.1.2.0 4 | 5 | -- A short (one-line) description of the package. 6 | -- synopsis: 7 | 8 | -- A longer description of the package. 9 | -- description: 10 | 11 | -- A URL where users can report bugs. 12 | -- bug-reports: 13 | 14 | license: MIT 15 | author: Sudip Bhattarai 16 | maintainer: Sudip Bhattarai 17 | 18 | -- A copyright notice. 19 | -- copyright: 20 | -- category: 21 | -- extra-source-files: CHANGELOG.md 22 | 23 | library 24 | default-language: Haskell2010 25 | exposed-modules: 26 | Kuber.Server.ServantError 27 | Kuber.Server.Core 28 | Kuber.Server.Spec 29 | Kuber.Server.Model 30 | other-modules: 31 | 32 | -- Plutus.Contract.Wallet.Utils 33 | hs-source-dirs: src 34 | 35 | build-depends: 36 | kuber 37 | , servant 38 | , servant-server 39 | , text-conversions 40 | , cardano-api 41 | , cardano-ledger-core 42 | , cardano-ledger-shelley 43 | , cardano-ledger-alonzo >= 1.6.0 44 | , text 45 | , bytestring 46 | , containers 47 | , filepath 48 | , base >= 4.14 && < 5 49 | , aeson >= 1.5.6.0 50 | , cardano-binary 51 | , http-media 52 | , wai 53 | , http-types 54 | , string-conversions 55 | , scientific 56 | , base-compat-batteries 57 | , wai-cors 58 | , servant-exceptions-server 59 | , servant-exceptions 60 | , warp 61 | , wai-middleware-static 62 | , wai-extra 63 | , time 64 | , plutus-ledger-api 65 | , cardano-slotting 66 | 67 | 68 | executable kuber-server 69 | default-language: Haskell2010 70 | main-is: Main.hs 71 | hs-source-dirs: app 72 | build-depends: 73 | base >= 4.9 && <5 74 | , containers 75 | , unordered-containers 76 | , scientific 77 | , string-conversions 78 | , base-compat-batteries 79 | , bytestring 80 | , serialise 81 | , text 82 | , filepath 83 | , directory 84 | , aeson 85 | , text-conversions 86 | , cardano-api 87 | , cardano-data 88 | , cardano-ledger-alonzo >= 1.6.0 89 | , cardano-ledger-shelley 90 | , cardano-slotting 91 | , plutus-ledger-api 92 | , plutus-tx 93 | , ouroboros-network 94 | , kuber 95 | , servant 96 | , servant-server 97 | , servant-exceptions 98 | , wai 99 | , servant-exceptions-server 100 | , warp 101 | , cborg 102 | , http-types 103 | , http-media 104 | , http-conduit 105 | , wai-cors 106 | , cardano-binary 107 | , cardano-ledger-core 108 | , kuber-server 109 | , optparse-applicative -------------------------------------------------------------------------------- /kuber-server/src/Kuber/Server/Api.hs: -------------------------------------------------------------------------------- 1 | module Kuber.Server.Api where -------------------------------------------------------------------------------- /kuber-server/src/Kuber/Server/Model.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Kuber.Server.Model where 5 | import Data.Word (Word64) 6 | import Cardano.Api 7 | import qualified Data.Aeson.Key as A 8 | import Data.Aeson (toJSON,fromJSON, (.!=)) 9 | import qualified Data.Aeson as A 10 | import Data.Time.Clock.POSIX (POSIXTime) 11 | import Data.Map (Map) 12 | import qualified Data.Map as Map 13 | import qualified Data.ByteString as BS 14 | import qualified PlutusLedgerApi.V1 as Plutus 15 | import PlutusLedgerApi.Common (EvaluationError(..)) 16 | import Cardano.Kuber.Api (FrameworkError) 17 | import Data.Aeson.Types ((.:?)) 18 | import Data.Functor ((<&>)) 19 | 20 | -------------------------------------------------------------------------------- /kuber-server/src/Kuber/Server/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE AllowAmbiguousTypes #-} 2 | {-# LANGUAGE BlockArguments #-} 3 | {-# LANGUAGE DataKinds #-} 4 | {-# LANGUAGE FlexibleContexts #-} 5 | {-# LANGUAGE FlexibleInstances #-} 6 | {-# LANGUAGE GADTs #-} 7 | {-# HLINT ignore "Redundant bracket" #-} 8 | {-# LANGUAGE MultiParamTypeClasses #-} 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | {-# LANGUAGE TypeApplications #-} 11 | {-# LANGUAGE TypeOperators #-} 12 | {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} 13 | 14 | module Kuber.Server.Spec where 15 | 16 | import Cardano.Api 17 | import qualified Cardano.Api.Ledger as Cardano.Ledger.Core.Era 18 | import Cardano.Api.Shelley (LedgerProtocolParameters, ProtocolParameters) 19 | import qualified Cardano.Api.Shelley as Cardano.Api.Eon.ShelleyBasedEra 20 | import Cardano.Kuber.Api 21 | import Cardano.Kuber.Data.Models 22 | import Cardano.Kuber.Data.Parsers 23 | import Cardano.Kuber.Http.Spec 24 | import Cardano.Kuber.Util 25 | import Cardano.Ledger.Alonzo.Scripts (ExUnits (ExUnits)) 26 | import qualified Cardano.Ledger.Crypto 27 | import Control.Exception 28 | ( Exception, 29 | IOException, 30 | SomeException (SomeException), 31 | catch, 32 | throw, 33 | throwIO, 34 | try, 35 | ) 36 | import Control.Monad (liftM) 37 | import Control.Monad.IO.Class (MonadIO (liftIO)) 38 | import Data.Aeson (FromJSON, KeyValue ((.=)), ToJSON (toJSON), decode, object) 39 | import qualified Data.Aeson as Aeson 40 | import qualified Data.ByteString as ByteString 41 | import qualified Data.ByteString.Char8 as BS 42 | import Data.ByteString.Lazy (toStrict) 43 | import qualified Data.ByteString.Lazy 44 | import Data.Functor ((<&>)) 45 | import Data.List (intercalate) 46 | import qualified Data.Map as Map 47 | import Data.String (IsString (fromString)) 48 | import qualified Data.String as String 49 | import Data.Text (Text) 50 | import qualified Data.Text as T 51 | import Data.Typeable (Proxy (..)) 52 | import GHC.Generics (Generic) 53 | import GHC.IO.Exception (IOErrorType (UserError)) 54 | import Kuber.Server.Core 55 | import Kuber.Server.Model 56 | import Kuber.Server.ServantError (HasErrorBody (..), errorMw) 57 | import Network.HTTP.Types 58 | import Network.Wai (Request (requestMethod), Response, ResponseReceived, mapResponseHeaders) 59 | import Network.Wai.Handler.Warp (run) 60 | import Network.Wai.Middleware.Cors (CorsResourcePolicy (..), cors, simpleCors) 61 | import Network.Wai.Middleware.Rewrite (rewriteRoot) 62 | import Network.Wai.Middleware.Static (static) 63 | import Servant 64 | import qualified Servant.API.ContentTypes as ContentTypes 65 | import Servant.Exception (Exception (..), Throws, ToServantErr (..), mapException) 66 | import Servant.Exception.Server 67 | 68 | type KubeServer era = Throws FrameworkError :> KuberServerApi era 69 | 70 | type KuberServerApi_ era = 71 | -- "api" :>"v3" :> QueryApi 72 | "api" 73 | :> "v1" 74 | :> ( KuberApi era 75 | :<|> UtilityApi 76 | ) 77 | 78 | kuberApiServer queryEra a = 79 | ( queryServer queryEra a 80 | :<|> cardanoServer queryEra a 81 | :<|> kuberServer a 82 | :<|> utilityServer a 83 | ) 84 | 85 | queryServer queryEra a = 86 | makeHandler a (queryPparamHandler queryEra) 87 | :<|> makeHandler a (kQueryChainPoint <&> ChainPointModal) 88 | :<|> makeHandler2 a (queryUtxosHandler queryEra) 89 | 90 | cardanoServer cardanoEra a = 91 | (makeHandler a kQuerySystemStart <&> SystemStartModal) 92 | :<|> makeHandler a (kQueryCurrentEra <&> AnyCardanoEraModal) 93 | :<|> (makeHandler a kQueryGenesisParams <&> GenesisParamModal) 94 | :<|> (queryHeahtlHandler a) 95 | 96 | kuberServer a = 97 | makeHandler2 a txBuilderHandler 98 | :<|> makeHandler1 a submitTxHandler 99 | :<|> makeHandler a queryTimeHandler 100 | :<|> makeHandler1 a translatePosixTimeHandler 101 | :<|> makeHandler1 a translateSlotHandler 102 | 103 | utilityServer a = 104 | (makeHandler1 a calculateMinFeeHandler) 105 | :<|> (makeHandler1 a calculateExUnitsHandler) 106 | 107 | corsMiddlewarePolicy = 108 | CorsResourcePolicy 109 | { corsOrigins = Nothing, 110 | corsMethods = [BS.pack "GET", BS.pack "POST", BS.pack "OPTIONS"], 111 | corsRequestHeaders = [fromString "content-type", fromString "api-key"], 112 | corsExposedHeaders = Nothing, 113 | corsMaxAge = Just 3600, 114 | corsVaryOrigin = False, 115 | corsRequireOrigin = False, 116 | corsIgnoreFailures = True 117 | } 118 | 119 | appWithBackenAndEra :: 120 | ( HasChainQueryAPI a, 121 | HasCardanoQueryApi a, 122 | HasLocalNodeAPI a, 123 | HasSubmitApi a, 124 | HasKuberAPI a 125 | ) => 126 | a -> 127 | BabbageEraOnwards era -> 128 | Application 129 | appWithBackenAndEra dcinfo beraonward = rewriteRoot (T.pack "index.html") $ static $ cors (\r -> Just corsMiddlewarePolicy) $ case beraonward of 130 | BabbageEraOnwardsBabbage -> serve @(KubeServer BabbageEra) Proxy $ kuberApiServer BabbageEraOnwardsBabbage dcinfo 131 | BabbageEraOnwardsConway -> serve @(KubeServer ConwayEra) Proxy $ kuberApiServer BabbageEraOnwardsConway dcinfo 132 | 133 | instance ToServantErr FrameworkError where 134 | status (FrameworkError _ _) = status400 135 | status (FrameworkErrors _) = status400 136 | 137 | instance MimeRender PlainText FrameworkError where 138 | mimeRender ct = mimeRender ct . show 139 | -------------------------------------------------------------------------------- /kuber.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: kuber 3 | version: 3.1.2.0 4 | 5 | -- A short (one-line) description of the package. 6 | -- synopsis: 7 | 8 | -- A longer description of the package. 9 | -- description: 10 | 11 | -- A URL where users can report bugs. 12 | -- bug-reports: 13 | 14 | license: MIT 15 | author: Sudip Bhattarai 16 | maintainer: Sudip Bhattarai 17 | 18 | -- A copyright notice. 19 | -- copyright: 20 | -- category: 21 | extra-source-files: CHANGELOG.md 22 | 23 | library 24 | default-language: Haskell2010 25 | exposed-modules: 26 | Cardano.Kuber.Api 27 | Cardano.Kuber.Util 28 | Cardano.Kuber.Console.ConsoleWritable 29 | Cardano.Kuber.Data.Models 30 | Cardano.Kuber.Data.Parsers 31 | Cardano.Kuber.Http.Spec 32 | 33 | other-modules: 34 | Cardano.Kuber.Error 35 | Cardano.Kuber.Core.TxScript 36 | Cardano.Kuber.Core.ChainInfo 37 | Cardano.Kuber.Core.LocalNodeChainApi 38 | Cardano.Kuber.Core.Kontract 39 | Cardano.Kuber.Core.ChainAPI 40 | Cardano.Kuber.Core.TxBuilder 41 | Cardano.Kuber.Core.TxFramework 42 | Cardano.Kuber.Core.KuberAPI 43 | Cardano.Kuber.Data.TxBuilderAeson 44 | Cardano.Kuber.Data.EraUpdate 45 | Cardano.Kuber.Utility.ChainInfoUtil 46 | Cardano.Kuber.Utility.WalletUtil 47 | Cardano.Kuber.Utility.QueryHelper 48 | Cardano.Kuber.Utility.DataTransformation 49 | Cardano.Kuber.Utility.Text 50 | Cardano.Kuber.Utility.ScriptUtil 51 | Cardano.Kuber.Utility.Misc 52 | Cardano.Kuber.Http.Client 53 | Cardano.Kuber.Http.MediaType 54 | 55 | ghc-options: 56 | -Wall -Wnoncanonical-monad-instances -Wunused-packages 57 | -Wincomplete-record-updates 58 | -Wredundant-constraints -Widentities -fobject-code 59 | -fno-ignore-interface-pragmas -fno-omit-interface-pragmas 60 | 61 | hs-source-dirs: src 62 | build-depends: 63 | base >= 4.14 && <5 64 | , containers 65 | , bytestring 66 | , bech32 67 | , serialise 68 | , cborg 69 | , cardano-binary 70 | , text 71 | , filepath 72 | , directory 73 | , aeson >= 1.5.6.0 74 | , text-conversions 75 | , cardano-api ^>= 10.1 76 | , cardano-data >= 1.0 77 | , cardano-ledger-binary 78 | , cardano-ledger-core 79 | , cardano-ledger-babbage 80 | , cardano-ledger-alonzo 81 | , cardano-ledger-shelley 82 | , cardano-ledger-api 83 | , cardano-ledger-mary 84 | , cardano-ledger-conway 85 | , cardano-crypto-class 86 | , cardano-slotting 87 | , plutus-ledger-api 88 | , plutus-tx 89 | , ouroboros-consensus 90 | , ouroboros-network 91 | , ouroboros-network-protocols 92 | , vector 93 | , transformers 94 | , unordered-containers 95 | , time 96 | , servant 97 | , servant-client 98 | , http-media 99 | , http-client 100 | , network-uri 101 | , http-types 102 | , http-client-tls 103 | , lens 104 | , data-default 105 | , io-classes-mtl 106 | , ordered-containers 107 | , dotenv 108 | 109 | test-suite test 110 | default-language: Haskell2010 111 | type: exitcode-stdio-1.0 112 | main-is: Main.hs 113 | hs-source-dirs: test 114 | other-modules: 115 | Test.ParserTest 116 | Test.ApiTest 117 | 118 | build-depends: 119 | base >= 4.9 && <5 120 | , kuber 121 | , QuickCheck -any 122 | , tasty -any 123 | , tasty-hunit -any 124 | , containers 125 | , bytestring 126 | , serialise 127 | , cborg 128 | , cardano-binary 129 | , text 130 | , aeson 131 | , text-conversions 132 | , cardano-api 133 | , cardano-data 134 | , cardano-api-gen 135 | , cardano-ledger-alonzo >= 1.6.0 136 | , cardano-ledger-shelley 137 | , cardano-ledger-conway 138 | , cardano-slotting 139 | , plutus-ledger-api 140 | , plutus-tx 141 | , ouroboros-network 142 | , cardano-binary 143 | , vector 144 | , unordered-containers 145 | , hedgehog 146 | , directory 147 | 148 | ghc-options: 149 | -Wall -Wnoncanonical-monad-instances -Wunused-packages 150 | -Wincomplete-record-updates 151 | -Wredundant-constraints -Widentities -fobject-code 152 | -fno-ignore-interface-pragmas -fno-omit-interface-pragmas 153 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Console/ConsoleWritable.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NumericUnderscores #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE TypeFamilies #-} 4 | module Cardano.Kuber.Console.ConsoleWritable 5 | where 6 | 7 | import Cardano.Api 8 | import qualified Data.Map as Map 9 | import Cardano.Api.Shelley ( TxBody (ShelleyTxBody), ReferenceScript (ReferenceScriptNone, ReferenceScript)) 10 | import GHC.Real 11 | import Data.List 12 | import qualified Data.Set as Set 13 | import Control.Monad (join) 14 | import qualified Cardano.Ledger.Alonzo.Tx as LedgerBody 15 | import qualified Data.Text as T 16 | import Cardano.Api.Ledger (Coin(unCoin)) 17 | -- import Cardano.Ledger.Alonzo.TxBody (ppTxBody) 18 | -- import Cardano.Ledger.Alonzo.Scripts (ppScript) 19 | -- import qualified Shelley.Spec.Ledger.TxBody as LedgerBody (TxIn (TxIn)) 20 | class ConsoleWritable v where 21 | -- ^ toConsoleText prefix -> object -> Printable text 22 | toConsoleText :: String-> v -> String 23 | 24 | toConsoleTextNoPrefix :: v -> String 25 | toConsoleTextNoPrefix v = toConsoleText "" v 26 | 27 | 28 | instance ConsoleWritable TxIn where 29 | toConsoleText prefix txin = prefix ++ toConsoleTextNoPrefix txin 30 | toConsoleTextNoPrefix txin = T.unpack (renderTxIn txin) 31 | 32 | instance IsCardanoEra era => ConsoleWritable (UTxO era) where 33 | toConsoleText prefix (UTxO utxoMap) = prefix ++ intercalate ( "\n" ++ prefix) (map toStrings $ Map.toList utxoMap) 34 | where 35 | toStrings (TxIn txId (TxIx index),TxOut addr value hash refScript )= showStr txId ++ "#" ++ show index ++" : " ++ (case value of 36 | TxOutValueByron (n ) -> show (unCoin n) 37 | TxOutValueShelleyBased sbe va -> case fromLedgerValue sbe va of 38 | value -> intercalate " +" (map vToString $valueToList value )) ++ showRefScript refScript 39 | vToString (AssetId policy asset,Quantity v)=show v ++ " " ++ showStr policy ++ "." ++ showStr asset 40 | vToString (AdaAssetId, Quantity v) = if v >99999 41 | then( 42 | let _rem= v `rem` 1_000_000 43 | _quot= v `quot` 1_000_000 44 | in 45 | case _rem of 46 | 0 -> show _quot ++ " Ada" 47 | v-> show _quot ++"." ++ show _rem++ " Ada" 48 | ) 49 | else show v ++ " Lovelace" 50 | 51 | instance ConsoleWritable Value where 52 | toConsoleText prefix value= prefix ++ renderBalance value 53 | where 54 | renderBalance balance =intercalate ("\n"++prefix) $ map renderAsset (valueToList balance) 55 | renderAsset (ass,q)=case ass of 56 | AdaAssetId -> renderAda q 57 | AssetId p n -> show q ++ "\t"++ showStr p ++ "." ++ showStr n 58 | toTxOut (UTxO a) = Map.elems a 59 | renderAda (Quantity q)= show ((fromIntegral q::Double)/1e6) ++ " Ada" 60 | 61 | toValue (TxOut _ v _ _) = case v of 62 | TxOutValueByron lo -> lovelaceToValue lo 63 | TxOutValueShelleyBased sbe va -> fromLedgerValue sbe va 64 | 65 | instance IsCardanoEra era => ConsoleWritable (TxOut ctx era ) where 66 | toConsoleTextNoPrefix t@(TxOut aie val datum refScript) = T.unpack (serialiseAddress aie) ++ toConsoleText " : " (toValue t ) ++ showRefScript refScript 67 | where 68 | toValue (TxOut _ v _ _) = case v of 69 | TxOutValueByron lo -> lovelaceToValue lo 70 | TxOutValueShelleyBased sbe va -> fromLedgerValue sbe va 71 | 72 | toConsoleText prefix txout = prefix ++ toConsoleTextNoPrefix txout 73 | 74 | showStr x = init $ tail $ show x 75 | 76 | showRefScript refScript = case refScript of 77 | ReferenceScript rtisidsie sial -> case sial of { ScriptInAnyLang sl sc -> (case sl of 78 | SimpleScriptLanguage -> " + SimpleScript(" 79 | PlutusScriptLanguage psv -> " + PlutusScript(" ) ++ show ( hashScript sc) ++ ")" } 80 | ReferenceScriptNone -> "" -------------------------------------------------------------------------------- /src/Cardano/Kuber/Core/ChainAPI.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE UndecidableInstances #-} 4 | {-# LANGUAGE DataKinds #-} 5 | {-# LANGUAGE AllowAmbiguousTypes #-} 6 | module Cardano.Kuber.Core.ChainAPI where 7 | import Cardano.Kuber.Core.Kontract 8 | import Cardano.Kuber.Error 9 | import Cardano.Api.Shelley 10 | import Cardano.Api 11 | import Cardano.Slotting.Time (SystemStart) 12 | import Data.Time.Clock.POSIX (POSIXTime) 13 | import Data.Set (Set) 14 | import PlutusTx.Prelude (traceError) 15 | import qualified Cardano.Ledger.Api as Ledger 16 | import Cardano.Api.Ledger (StandardCrypto, GovState, DRepState, Credential, KeyRole (DRepRole), DRep, Coin) 17 | import Cardano.Kuber.Core.TxBuilder (IsTxBuilderEra) 18 | import Data.Map (Map) 19 | 20 | 21 | class HasChainQueryAPI a where 22 | -- Core query functions 23 | kQueryProtocolParams :: IsTxBuilderEra era => Kontract a w FrameworkError (LedgerProtocolParameters era) 24 | kQueryUtxoByAddress :: IsTxBuilderEra era => Set AddressAny -> Kontract a w FrameworkError (UTxO era) 25 | kQueryUtxoByTxin :: IsTxBuilderEra era => Set TxIn -> Kontract a w FrameworkError (UTxO era) 26 | kQueryChainPoint :: Kontract a w FrameworkError ChainPoint 27 | 28 | class HasCardanoQueryApi a where 29 | kQuerySystemStart :: Kontract a w FrameworkError SystemStart -- for hydra this one is init timestamp. 30 | kGetNetworkId :: Kontract a w FrameworkError NetworkId 31 | kQueryGenesisParams :: Kontract a w FrameworkError (GenesisParameters ShelleyEra) 32 | kQueryCurrentEra :: Kontract a w FrameworkError AnyCardanoEra 33 | kQueryStakeDeposit :: Set StakeCredential -> Kontract a w FrameworkError (Map StakeCredential Coin) 34 | kQueryDrepState :: Set (Credential 'DRepRole StandardCrypto) -> Kontract a w FrameworkError (Map (Credential 'DRepRole StandardCrypto) (DRepState StandardCrypto)) 35 | kQueryGovState :: IsTxBuilderEra era => Kontract a w FrameworkError (GovState (ShelleyLedgerEra era)) 36 | kQueryDRepDistribution :: Set (DRep StandardCrypto) -> Kontract a w FrameworkError (Map (DRep StandardCrypto) Coin) 37 | 38 | 39 | 40 | class HasSubmitApi a where 41 | kSubmitTx :: InAnyCardanoEra Tx -> Kontract a w FrameworkError () -------------------------------------------------------------------------------- /src/Cardano/Kuber/Core/ChainInfo.hs: -------------------------------------------------------------------------------- 1 | module Cardano.Kuber.Core.ChainInfo 2 | where 3 | 4 | import Cardano.Api 5 | import Cardano.Api.Shelley 6 | import Cardano.Slotting.Time 7 | import Debug.Trace as Debug 8 | import Data.Functor ((<&>)) 9 | import Cardano.Kuber.Utility.QueryHelper 10 | import Data.ByteString.Char8 (unpack) 11 | import qualified Cardano.Ledger.Alonzo.PParams as Alonzo 12 | import qualified Cardano.Ledger.Babbage.PParams as Babbage 13 | import Cardano.Ledger.Crypto (StandardCrypto) 14 | import qualified Cardano.Ledger.Core as Ledger 15 | import Data.Set (Set) 16 | import Data.Time.Clock.POSIX (POSIXTime) 17 | 18 | 19 | -- class ChainInfo v where 20 | -- getConnectInfo :: v-> LocalNodeConnectInfo CardanoMode 21 | -- getNetworkId :: v -> NetworkId 22 | 23 | 24 | -- ChainConnectInfo wraps (LocalNodeConnectInfo CardanoMode) 25 | -- This is the minimal information required to connect to a cardano node 26 | -- newtype ChainConnectInfo= ChainConnectInfo (LocalNodeConnectInfo CardanoMode) 27 | 28 | 29 | data KuberConnectInfo = KuberConnectInfo { 30 | kuberUrl :: String 31 | , apiKey :: Maybe String 32 | , networkId :: NetworkId 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Core/Kontract.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE InstanceSigs #-} 2 | {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} 3 | {-# HLINT ignore "Use <$>" #-} 4 | {-# LANGUAGE BlockArguments #-} 5 | {-# LANGUAGE FlexibleInstances #-} 6 | {-# LANGUAGE MultiParamTypeClasses #-} 7 | {-# LANGUAGE ScopedTypeVariables #-} 8 | {-# LANGUAGE TypeApplications #-} 9 | module Cardano.Kuber.Core.Kontract 10 | where 11 | import Cardano.Api 12 | import Control.Exception (try, Exception, SomeException, catch) 13 | import Cardano.Kuber.Error (FrameworkError (..), ErrorType (..)) 14 | import Control.Applicative (Alternative (empty, (<|>))) 15 | import Control.Exception.Base (throw) 16 | 17 | 18 | data Exception e => Kontract api w e r = 19 | KResult !r 20 | | KError !e 21 | | KLift ( api -> IO (Either e r)) 22 | 23 | eitherToKontract :: Exception e => Either e r -> Kontract api w e r 24 | eitherToKontract e = case e of 25 | Left err -> KError err 26 | Right r -> KResult r 27 | 28 | instance Exception e => Functor (Kontract api w e ) where 29 | fmap f (KResult x) = KResult (f x) 30 | fmap _ (KError x) = KError x 31 | fmap f (KLift x) = KLift $ \api -> (fmap . fmap ) f (x api ) 32 | 33 | instance Exception e => Applicative (Kontract api w e ) where 34 | pure = KResult 35 | 36 | 37 | (<*>) :: Kontract api w e (a -> b) -> Kontract api w e a -> Kontract api w e b 38 | (<*>) (KResult f) a = fmap f a 39 | (<*>) (KError e) _ = KError e 40 | (<*>) _ (KError e) = KError e 41 | (<*>) (KLift far) (KResult a) = KLift $ \ api -> do 42 | result <- far api 43 | case result of 44 | Left e -> pure $ Left e 45 | Right fab -> pure $ Right $ fab a 46 | (<*>) (KLift lf) (KLift lf') = KLift $ \api -> do 47 | fe <- lf api 48 | ve <- lf' api 49 | pure $ do 50 | f <- fe 51 | v <- ve 52 | pure $ f v 53 | 54 | instance Exception e => Alternative (Kontract api w e) where 55 | empty :: Kontract api w e a 56 | empty = liftIO $ throw ( FrameworkError LibraryError "Empty Alternative for Kontract" ) 57 | 58 | (<|>) :: Kontract api w e a -> Kontract api w e a -> Kontract api w e a 59 | (KError _) <|> b = b 60 | a <|> _ = a 61 | 62 | instance Exception e => Monad (Kontract api w e ) where 63 | 64 | (>>=) :: Kontract api w e a -> (a -> Kontract api w e b) -> Kontract api w e b 65 | (>>=) (KResult r) f = f r 66 | (>>=) (KError r) _ = KError r 67 | (>>=) (KLift ior) f = KLift $ \api -> do 68 | result <- ior api 69 | case result of 70 | Left e -> pure $ Left e 71 | Right a -> evaluateKontract' api ( f a) 72 | 73 | instance Exception e => MonadIO (Kontract api w e) where 74 | liftIO :: IO r -> Kontract api w e r 75 | liftIO io = KLift $ \_ -> try io 76 | 77 | 78 | evaluateKontract' :: Exception e => a -> Kontract a w e r -> IO (Either e r ) 79 | evaluateKontract' api contract = do 80 | case contract of 81 | KResult r -> pure $ pure r 82 | KError e -> pure $ Left e 83 | KLift f -> f api 84 | 85 | evaluateKontract :: a -> Kontract a w FrameworkError r -> IO (Either FrameworkError r ) 86 | evaluateKontract api contract = do 87 | case contract of 88 | KResult r -> pure $ pure r 89 | KError e -> pure $ Left e 90 | KLift f -> mapException $ f api 91 | where 92 | mapException action = catch action someExHandler 93 | 94 | kGetBackend :: Exception e => Kontract a w e a 95 | kGetBackend = KLift $ \api -> pure (pure api) 96 | 97 | kError :: ErrorType -> String -> Kontract api w FrameworkError r 98 | kError t msg= KError (FrameworkError t msg) 99 | 100 | 101 | kWrapParser :: Either String r -> Kontract api w FrameworkError r 102 | kWrapParser m = case m of 103 | Left msg -> kError ParserError msg 104 | Right v -> KResult v 105 | 106 | 107 | instance MonadError FrameworkError (Kontract api w FrameworkError) where 108 | throwError = KError 109 | catchError :: Kontract api w FrameworkError a -> (FrameworkError -> Kontract api w FrameworkError a) -> Kontract api w FrameworkError a 110 | catchError (KResult r) _ = KResult r 111 | catchError (KError e) handler = handler e 112 | catchError (KLift action) handler = KLift $ \api -> do 113 | result <- try @SomeException (action api) 114 | let handlerResult err = case handler err of 115 | KResult r -> return (Right r) 116 | KError e' -> return (Left e') 117 | KLift action' -> action' api 118 | case result of 119 | Left e -> handlerResult $ unHandledError e 120 | Right (Left err) -> handlerResult err 121 | Right(Right r) -> return (Right r) 122 | 123 | unHandledError :: SomeException -> FrameworkError 124 | unHandledError e = FrameworkError LibraryError ("Unhandled : " ++ show e) 125 | 126 | someExHandler :: Applicative f => SomeException -> f (Either FrameworkError b) 127 | someExHandler (e::SomeException ) = pure $ Left $ unHandledError e 128 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Core/KuberAPI.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE GADTs #-} 4 | {-# LANGUAGE InstanceSigs #-} 5 | {-# LANGUAGE LambdaCase #-} 6 | {-# LANGUAGE TypeApplications #-} 7 | {-# LANGUAGE UndecidableInstances #-} 8 | 9 | module Cardano.Kuber.Core.KuberAPI where 10 | 11 | import Cardano.Api 12 | import Cardano.Api.Shelley (LedgerProtocolParameters (unLedgerProtocolParameters), TxBody (ShelleyTxBody), convertToLedgerProtocolParameters, fromShelleyTxIn) 13 | import Cardano.Kuber.Core.ChainAPI 14 | import Cardano.Kuber.Core.Kontract 15 | import Cardano.Kuber.Core.LocalNodeChainApi (ChainConnectInfo, HasLocalNodeAPI (..), kEvaluateExUnits') 16 | import Cardano.Kuber.Core.TxBuilder 17 | import Cardano.Kuber.Core.TxFramework (executeTxBuilder, executeRawTxBuilder) 18 | import Cardano.Kuber.Error 19 | import Cardano.Kuber.Utility.Misc 20 | import Cardano.Ledger.Api (EraTxBody (inputsTxBodyL)) 21 | import Cardano.Ledger.Babbage.TxBody (BabbageEraTxBody (referenceInputsTxBodyL)) 22 | import Control.Lens ((^.)) 23 | import Data.Functor ((<&>)) 24 | import Data.Map (Map) 25 | import qualified Data.Set as Set 26 | import Data.Time.Clock.POSIX (POSIXTime) 27 | import Cardano.Api.Ledger (Coin) 28 | 29 | type ShelleyWitCount = Word 30 | 31 | type ByronWitCount = Word 32 | 33 | class HasKuberAPI a where 34 | kTxBuildTxBody :: IsTxBuilderEra era => TxBuilder_ era -> Kontract a w FrameworkError (TxBody era) 35 | kBuildTx :: IsTxBuilderEra era => TxBuilder_ era -> Kontract a w FrameworkError (Tx era) 36 | kBuildRawTx :: IsTxBuilderEra era => TxBuilder_ era -> LedgerProtocolParameters era -> Kontract a w FrameworkError (Tx era) 37 | kTimeToSlot :: POSIXTime -> Kontract a w FrameworkError SlotNo 38 | kSlotToTime :: SlotNo -> Kontract a w FrameworkError POSIXTime 39 | kEvaluateExUnits :: IsTxBuilderEra era => Tx era -> Kontract a w FrameworkError (Map ScriptWitnessIndex (Either FrameworkError ExecutionUnits)) 40 | kCalculateMinFee :: IsTxBuilderEra era => Tx era -> Kontract a w FrameworkError Coin 41 | kBuildAndSubmit :: IsTxBuilderEra era => TxBuilder_ era -> Kontract a w FrameworkError (Tx era) 42 | 43 | instance HasKuberAPI LocalNodeConnectInfo where 44 | kTxBuildTxBody = kTxBuildTxBody' 45 | kBuildTx = kBuildTx' 46 | kBuildRawTx = kBuildRawTx' 47 | kTimeToSlot = kTimeToSlot' 48 | kSlotToTime = kSlotToTime' 49 | kEvaluateExUnits = kEvaluateExUnits'' 50 | kCalculateMinFee = kCalculateMinFee' 51 | kBuildAndSubmit = kBuildAndSubmit' 52 | 53 | instance HasKuberAPI ChainConnectInfo where 54 | kTxBuildTxBody = kTxBuildTxBody' 55 | kBuildTx = kBuildTx' 56 | kBuildRawTx = kBuildRawTx' 57 | kTimeToSlot = kTimeToSlot' 58 | kSlotToTime = kSlotToTime' 59 | kEvaluateExUnits = kEvaluateExUnits'' 60 | kCalculateMinFee = kCalculateMinFee' 61 | kBuildAndSubmit = kBuildAndSubmit' 62 | 63 | kTxBuildTxBody' builder = executeTxBuilder builder <&> fst 64 | 65 | kBuildTx' builder = executeTxBuilder builder <&> snd 66 | 67 | kBuildRawTx' builder pparams = executeRawTxBuilder builder pparams <&> snd 68 | 69 | kTimeToSlot' time = timestampToSlot <$> kQuerySystemStart <*> kQueryEraHistory <*> pure time 70 | 71 | kSlotToTime' slot = slotToTimestamp <$> kQuerySystemStart <*> kQueryEraHistory <*> pure slot 72 | 73 | kEvaluateExUnits'' tx = do 74 | let txBody = getTxBody tx 75 | allInputs = resolveEra bShelleyBasedEra txBody 76 | 77 | utxos <- kQueryUtxoByTxin allInputs 78 | kEvaluateExUnits' (getTxBody tx) utxos 79 | 80 | resolveEra :: ShelleyBasedEra era -> TxBody era -> Set.Set TxIn 81 | resolveEra sbera body = case sbera of 82 | ShelleyBasedEraBabbage -> getTxUnknownInputs (body :: TxBody BabbageEra) 83 | ShelleyBasedEraConway -> getTxUnknownInputs (body :: TxBody ConwayEra) 84 | _ -> error "Unexpected" 85 | 86 | getTxUnknownInputs txBody = 87 | let ledgerTxBody = case txBody of ShelleyTxBody sbe tb scs tbsd m_ad tsv -> tb 88 | ins = ledgerTxBody ^. inputsTxBodyL 89 | refs = ledgerTxBody ^. referenceInputsTxBodyL --case txBody of { ShelleyTxBody sbe tb scs tbsd m_ad tsv -> btbReferenceInputs tb } 90 | in Set.map fromShelleyTxIn (ins <> refs) 91 | 92 | kCalculateMinFee' :: (HasChainQueryAPI a, IsTxBuilderEra era) => Tx era -> Kontract a w FrameworkError Coin 93 | kCalculateMinFee' tx = do 94 | kCalculateMinFee'' (getTxBody tx) (fromInteger $ toInteger $ length (getTxWitnesses tx)) 0 95 | 96 | kCalculateMinFee'' :: (HasChainQueryAPI a, IsTxBuilderEra era) => TxBody era -> Word -> Word -> Kontract a w FrameworkError Coin 97 | kCalculateMinFee'' txbody shelleyWitnesses byronWitnesses = do 98 | protocolParams <- kQueryProtocolParams 99 | let 100 | allInputs = resolveEra bShelleyBasedEra txbody 101 | 102 | utxo <- kQueryUtxoByTxin allInputs 103 | 104 | -- todo: fix this to support reference scripts 105 | pure $ calculateMinTxFee shelleyBasedEra (unLedgerProtocolParameters protocolParams) utxo txbody shelleyWitnesses 106 | 107 | kBuildAndSubmit' :: (HasChainQueryAPI api, HasCardanoQueryApi api, HasLocalNodeAPI api, IsTxBuilderEra era, HasSubmitApi api) => TxBuilder_ era -> Kontract api w FrameworkError (Tx era) 108 | kBuildAndSubmit' builder = do 109 | tx <- executeTxBuilder builder <&> snd 110 | kSubmitTx (InAnyCardanoEra bCardanoEra tx) 111 | pure tx 112 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Core/LocalNodeChainApi.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE InstanceSigs #-} 3 | {-# LANGUAGE LambdaCase #-} 4 | {-# OPTIONS_GHC -Wno-orphans #-} 5 | 6 | module Cardano.Kuber.Core.LocalNodeChainApi where 7 | 8 | import Cardano.Api hiding (queryChainPoint, queryCurrentEra, queryEraHistory, queryGovState, querySystemStart) 9 | import Cardano.Api.Shelley hiding (queryChainPoint, queryCurrentEra, queryEraHistory, queryGovState, querySystemStart) 10 | import Cardano.Kuber.Core.ChainAPI 11 | import Cardano.Kuber.Core.Kontract 12 | import Cardano.Kuber.Core.TxBuilder (IsTxBuilderEra (bCardanoEra), TxBuilder) 13 | import Cardano.Kuber.Data.EraUpdate (updatePParamEra) 14 | import Cardano.Kuber.Error 15 | import Cardano.Kuber.Utility.QueryHelper (queryChainPoint, queryCurrentEra, queryDRepDistribution, queryDRepState, queryEraHistory, queryGenesesisParams, queryGenesesisParams', queryGovState, queryProtocolParam, queryStakeDeposits, querySystemStart, queryTxins, queryUtxos, submitTx) 16 | import qualified Cardano.Ledger.Babbage.Tx as Ledger 17 | import Cardano.Slotting.Time (SystemStart) 18 | import Data.Functor ((<&>)) 19 | import Data.Map (Map) 20 | import qualified Data.Map as Map 21 | import qualified Data.Set as Set 22 | import Data.Time.Clock.POSIX (POSIXTime) 23 | 24 | class ChainInfo v where 25 | getConnectInfo :: v -> LocalNodeConnectInfo 26 | getNetworkId :: v -> NetworkId 27 | 28 | class HasLocalNodeAPI a where 29 | kQueryEraHistory :: Kontract a w FrameworkError EraHistory 30 | 31 | -- ChainConnectInfo wraps (LocalNodeConnectInfo CardanoMode) 32 | -- This is the minimal information required to connect to a cardano node 33 | newtype ChainConnectInfo = ChainConnectInfo LocalNodeConnectInfo 34 | 35 | instance HasChainQueryAPI LocalNodeConnectInfo where 36 | kQueryProtocolParams = liftLnciQuery queryProtocolParam 37 | kQueryUtxoByAddress = liftLnciQuery2 queryUtxos 38 | kQueryUtxoByTxin = liftLnciQuery2 queryTxins 39 | kQueryChainPoint = liftLnciQuery queryChainPoint 40 | 41 | instance HasCardanoQueryApi LocalNodeConnectInfo where 42 | kQuerySystemStart = liftLnciQuery querySystemStart 43 | kQueryGenesisParams = liftLnciQuery queryGenesesisParams' 44 | kGetNetworkId = KLift $ \c -> pure $ pure $ localNodeNetworkId c 45 | kQueryCurrentEra = liftLnciQuery queryCurrentEra 46 | kQueryGovState = liftLnciQuery queryGovState 47 | kQueryStakeDeposit = liftLnciQuery2 (queryStakeDeposits ShelleyBasedEraConway) 48 | kQueryDrepState = liftLnciQuery2 (Cardano.Kuber.Utility.QueryHelper.queryDRepState ShelleyBasedEraConway) 49 | kQueryDRepDistribution = liftLnciQuery2 (Cardano.Kuber.Utility.QueryHelper.queryDRepDistribution ShelleyBasedEraConway) 50 | 51 | instance HasLocalNodeAPI LocalNodeConnectInfo where 52 | kQueryEraHistory = liftLnciQuery queryEraHistory 53 | 54 | instance HasSubmitApi LocalNodeConnectInfo where 55 | kSubmitTx = liftLnciQuery2 submitTx 56 | 57 | instance HasChainQueryAPI ChainConnectInfo where 58 | kQueryProtocolParams = liftCinfoQuery queryProtocolParam 59 | kQueryUtxoByAddress = liftCinfoQuery2 queryUtxos 60 | kQueryUtxoByTxin = liftCinfoQuery2 queryTxins 61 | kQueryChainPoint = liftCinfoQuery queryChainPoint 62 | 63 | instance HasCardanoQueryApi ChainConnectInfo where 64 | kQuerySystemStart = liftCinfoQuery querySystemStart 65 | kQueryGenesisParams = liftCinfoQuery queryGenesesisParams' 66 | kGetNetworkId = KLift $ \(ChainConnectInfo c) -> pure $ pure $ localNodeNetworkId c 67 | kQueryCurrentEra = liftCinfoQuery queryCurrentEra 68 | kQueryGovState = liftCinfoQuery queryGovState 69 | kQueryStakeDeposit = liftCinfoQuery2 (queryStakeDeposits ShelleyBasedEraConway) 70 | kQueryDrepState = liftCinfoQuery2 (Cardano.Kuber.Utility.QueryHelper.queryDRepState ShelleyBasedEraConway) 71 | kQueryDRepDistribution = liftCinfoQuery2 (Cardano.Kuber.Utility.QueryHelper.queryDRepDistribution ShelleyBasedEraConway) 72 | 73 | instance HasLocalNodeAPI ChainConnectInfo where 74 | kQueryEraHistory = liftCinfoQuery queryEraHistory 75 | 76 | instance HasSubmitApi ChainConnectInfo where 77 | kSubmitTx tx = KLift $ \(ChainConnectInfo c) -> submitTx c tx 78 | 79 | liftCinfoQuery q = KLift $ \(ChainConnectInfo c) -> q c 80 | 81 | liftCinfoQuery2 q p = KLift $ \(ChainConnectInfo c) -> q c p 82 | 83 | liftLnciQuery q = KLift $ \c -> q c 84 | 85 | liftLnciQuery2 q p = KLift $ \c -> q c p 86 | 87 | kEvaluateExUnits' :: (HasChainQueryAPI a, HasCardanoQueryApi a, HasLocalNodeAPI a, IsTxBuilderEra era) => TxBody era -> UTxO era -> Kontract a w FrameworkError (Map ScriptWitnessIndex (Either FrameworkError ExecutionUnits)) 88 | kEvaluateExUnits' txbody utxos = do 89 | sStart <- kQuerySystemStart 90 | eHhistory <- kQueryEraHistory 91 | pParams <- kQueryProtocolParams 92 | case evaluateTransactionExecutionUnits 93 | cardanoEra 94 | sStart 95 | (toLedgerEpochInfo eHhistory) 96 | pParams 97 | utxos 98 | txbody of 99 | Left tve -> KError $ FrameworkError ExUnitCalculationError (show tve) 100 | Right mp -> 101 | pure $ 102 | Map.map 103 | ( \case 104 | Left see -> Left (fromScriptExecutionError see txbody) 105 | Right (_, eu) -> pure eu 106 | ) 107 | mp 108 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Core/TxScript.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE UndecidableInstances #-} 3 | {-# LANGUAGE LambdaCase #-} 4 | {-# LANGUAGE MonoLocalBinds #-} 5 | module Cardano.Kuber.Core.TxScript where 6 | import Cardano.Api 7 | import Cardano.Api.Shelley (PlutusScript(..)) 8 | import qualified Data.ByteString.Short as SBS 9 | 10 | data TxSimpleScript = TxSimpleScript SimpleScript 11 | deriving(Show) 12 | 13 | data TxScript = TxScriptSimple SimpleScript 14 | | TxScriptPlutus TxPlutusScript 15 | deriving (Show) 16 | 17 | 18 | data TxPlutusScript = 19 | TxPlutusScriptV1 (PlutusScript PlutusScriptV1) 20 | | TxPlutusScriptV2 (PlutusScript PlutusScriptV2) 21 | | TxPlutusScriptV3 (PlutusScript PlutusScriptV3) 22 | deriving (Show) 23 | 24 | class IsPlutusVersion v where 25 | toTxPlutusScriptInstance :: PlutusScript v -> TxPlutusScript 26 | 27 | instance IsPlutusVersion PlutusScriptV1 where 28 | toTxPlutusScriptInstance = TxPlutusScriptV1 29 | 30 | instance IsPlutusVersion PlutusScriptV2 where 31 | toTxPlutusScriptInstance = TxPlutusScriptV2 32 | 33 | instance IsPlutusVersion PlutusScriptV3 where 34 | toTxPlutusScriptInstance = TxPlutusScriptV3 35 | 36 | class IsPlutusScript sc where 37 | toTxPlutusScript :: sc -> TxPlutusScript 38 | 39 | class IsSimpleScript sc where 40 | toTxSimpleScript :: sc -> TxSimpleScript 41 | 42 | instance IsPlutusScript TxPlutusScript where 43 | toTxPlutusScript = id 44 | 45 | instance IsSimpleScript TxSimpleScript where 46 | toTxSimpleScript = id 47 | 48 | instance IsSimpleScript SimpleScript where 49 | toTxSimpleScript = TxSimpleScript 50 | 51 | hashPlutusScript :: TxPlutusScript -> ScriptHash 52 | hashPlutusScript sc = case sc of 53 | TxPlutusScriptV1 ps -> hashScript (PlutusScript PlutusScriptV1 ps) 54 | TxPlutusScriptV2 ps -> hashScript (PlutusScript PlutusScriptV2 ps) 55 | TxPlutusScriptV3 ps -> hashScript (PlutusScript PlutusScriptV3 ps) 56 | 57 | plutusScriptAddr :: IsShelleyBasedEra era => TxPlutusScript -> NetworkId -> AddressInEra era 58 | plutusScriptAddr sc networkId = 59 | let payCred = PaymentCredentialByScript (hashPlutusScript sc) 60 | addr = makeShelleyAddress networkId payCred NoStakeAddress 61 | addrInEra = AddressInEra (ShelleyAddressInEra shelleyBasedEra) addr 62 | in addrInEra 63 | 64 | plutusScriptToScriptAny :: TxPlutusScript -> ScriptInAnyLang 65 | plutusScriptToScriptAny sc = case sc of 66 | TxPlutusScriptV1 ps -> ScriptInAnyLang (PlutusScriptLanguage PlutusScriptV1) (PlutusScript PlutusScriptV1 ps) 67 | TxPlutusScriptV2 ps -> ScriptInAnyLang (PlutusScriptLanguage PlutusScriptV2) (PlutusScript PlutusScriptV2 ps) 68 | TxPlutusScriptV3 ps -> ScriptInAnyLang (PlutusScriptLanguage PlutusScriptV3) (PlutusScript PlutusScriptV3 ps) 69 | 70 | instance IsPlutusVersion ver => IsPlutusScript (PlutusScript ver) where 71 | toTxPlutusScript = toTxPlutusScriptInstance 72 | 73 | -- instance IsPlutusVersion ver => IsPlutusScript (PlutusScript ver) where 74 | -- toTxPlutusScript = toTxPlutusScriptInstance 75 | 76 | instance IsPlutusVersion ver => IsPlutusScript (Script ver) where 77 | toTxPlutusScript (PlutusScript psv ps) = toTxPlutusScript ps 78 | toTxPlutusScript _ = error "Impossible" 79 | 80 | class IsMintingScript sc where 81 | toTxMintingScript :: sc -> TxScript 82 | 83 | txScriptPolicyId :: TxScript -> PolicyId 84 | txScriptPolicyId sc = PolicyId (hashTxScript sc) 85 | 86 | txScriptAddress :: IsShelleyBasedEra era => TxScript -> NetworkId -> StakeAddressReference -> AddressInEra era 87 | txScriptAddress sc net = makeShelleyAddressInEra shelleyBasedEra net (PaymentCredentialByScript $ txScriptHash sc) 88 | 89 | txScriptHash :: TxScript -> ScriptHash 90 | txScriptHash = hashTxScript 91 | 92 | hashTxScript :: TxScript -> ScriptHash 93 | hashTxScript sc = case sc of 94 | TxScriptSimple ss -> hashScript (SimpleScript ss) 95 | TxScriptPlutus tps -> hashPlutusScript tps 96 | 97 | txScriptToScriptAny :: TxScript -> ScriptInAnyLang 98 | txScriptToScriptAny sc = case sc of 99 | TxScriptSimple ss -> ScriptInAnyLang SimpleScriptLanguage (SimpleScript ss) 100 | TxScriptPlutus tps -> plutusScriptToScriptAny tps 101 | 102 | txScriptFromScriptAny :: ScriptInAnyLang -> TxScript 103 | txScriptFromScriptAny = \case 104 | ScriptInAnyLang sl sc -> case sc of 105 | SimpleScript ss -> TxScriptSimple ss 106 | PlutusScript psv ps -> case psv of 107 | PlutusScriptV1 -> TxScriptPlutus $ toTxPlutusScript ps 108 | PlutusScriptV2 -> TxScriptPlutus $ toTxPlutusScript ps 109 | PlutusScriptV3 -> TxScriptPlutus $ toTxPlutusScript ps 110 | class IsScriptVersion v where 111 | translationFunc :: Script v -> TxScript 112 | 113 | instance IsScriptVersion PlutusScriptV1 where 114 | translationFunc (PlutusScript psv ps) = TxScriptPlutus $ toTxPlutusScript ps 115 | 116 | instance IsScriptVersion PlutusScriptV2 where 117 | translationFunc (PlutusScript psv ps) = TxScriptPlutus $ toTxPlutusScript ps 118 | 119 | instance IsScriptVersion PlutusScriptV3 where 120 | translationFunc (PlutusScript psv ps) = TxScriptPlutus $ toTxPlutusScript ps 121 | 122 | instance (IsPlutusVersion ver) => IsMintingScript (PlutusScript ver) where 123 | toTxMintingScript v = TxScriptPlutus (toTxPlutusScript v) 124 | 125 | txScriptByteSize :: TxScript -> Int 126 | txScriptByteSize txScript = 127 | case txScript of 128 | TxScriptPlutus ps -> case ps of 129 | TxPlutusScriptV1 (PlutusScriptSerialised sc) -> SBS.length sc 130 | TxPlutusScriptV2 (PlutusScriptSerialised sc) -> SBS.length sc 131 | TxPlutusScriptV3 (PlutusScriptSerialised sc) -> SBS.length sc 132 | TxScriptSimple _ss -> 1 -- TODO: what is the script size in case of simple script? -------------------------------------------------------------------------------- /src/Cardano/Kuber/Data/EraUpdate.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | {-# LANGUAGE LambdaCase #-} 3 | {-# OPTIONS_GHC -Wno-incomplete-patterns #-} 4 | {-# LANGUAGE FlexibleContexts #-} 5 | {-# LANGUAGE AllowAmbiguousTypes #-} 6 | 7 | module Cardano.Kuber.Data.EraUpdate where 8 | 9 | import Cardano.Api 10 | import qualified Cardano.Api.Ledger as L 11 | import Cardano.Api.Shelley 12 | import Cardano.Kuber.Core.TxBuilder (IsTxBuilderEra (..)) 13 | import Cardano.Ledger.Api (downgradePParams) 14 | import qualified Data.Map as Map 15 | 16 | updateUtxoEra :: IsTxBuilderEra era => UTxO era1 -> UTxO era 17 | updateUtxoEra (UTxO mp) = UTxO $ updateuMapEra mp 18 | 19 | updateuMapEra :: IsTxBuilderEra era => Map.Map k (TxOut ctx era1) -> Map.Map k (TxOut ctx era) 20 | updateuMapEra = Map.map updateTxOutInEra 21 | 22 | updateTxOutInEra :: IsTxBuilderEra era => TxOut ctx era1 -> TxOut ctx era 23 | updateTxOutInEra txout = case txout of TxOut aie tov tod rs -> TxOut (updateAddressEra aie) (updateToutValue tov) (updateTxOutDatum tod) (updateRefScript rs) 24 | 25 | updateTxOutInEra' :: IsTxBuilderEra era => TxOut CtxTx era1 -> TxOut CtxTx era 26 | updateTxOutInEra' txout = case txout of TxOut aie tov tod rs -> TxOut (updateAddressEra aie) (updateToutValue tov) (updateTxOutDatum' tod) (updateRefScript rs) 27 | 28 | updateRefScript :: IsTxBuilderEra era => ReferenceScript era1 -> ReferenceScript era 29 | updateRefScript = \case 30 | ReferenceScript beo sial -> ReferenceScript bBabbageOnward sial 31 | ReferenceScriptNone -> ReferenceScriptNone 32 | 33 | updateTxOutDatum' :: IsTxBuilderEra era => TxOutDatum CtxTx era1 -> TxOutDatum CtxTx era 34 | updateTxOutDatum' datum = case datum of 35 | TxOutDatumNone -> TxOutDatumNone 36 | TxOutDatumHash aeo ha -> TxOutDatumHash bAlonzoOnward ha 37 | TxOutDatumInline beo hsd -> TxOutDatumInline bBabbageOnward hsd 38 | TxOutDatumInTx aeo hsd -> TxOutDatumInTx bAlonzoOnward hsd 39 | _ -> error "Cardano.Kuber.Core.Data.EraUpdate.updateTxOutDatum' : Impossible" 40 | 41 | updateTxOutDatum :: IsTxBuilderEra era => TxOutDatum ctx era1 -> TxOutDatum ctx era 42 | updateTxOutDatum datum = case datum of 43 | TxOutDatumNone -> TxOutDatumNone 44 | TxOutDatumHash aeo ha -> TxOutDatumHash bAlonzoOnward ha 45 | TxOutDatumInline beo hsd -> TxOutDatumInline bBabbageOnward hsd 46 | _ -> error "Cardano.Kuber.Core.Data.EraUpdate.updateTxOutDatum : Impossible" 47 | 48 | updateToutValue' :: CardanoEra era -> TxOutValue srcEra -> TxOutValue era 49 | updateToutValue' era toutVal = case era of 50 | MaryEra -> TxOutValueShelleyBased ShelleyBasedEraMary (toLedgerValue MaryEraOnwardsMary val) 51 | AlonzoEra -> TxOutValueShelleyBased ShelleyBasedEraAlonzo (toLedgerValue MaryEraOnwardsAlonzo val) 52 | BabbageEra -> TxOutValueShelleyBased ShelleyBasedEraBabbage (toLedgerValue MaryEraOnwardsBabbage val) 53 | ConwayEra -> TxOutValueShelleyBased ShelleyBasedEraConway (toLedgerValue MaryEraOnwardsConway val) 54 | _ -> error "Unexpected" 55 | 56 | where 57 | val=txOutValueToValue toutVal 58 | 59 | updateToutValue :: ( IsTxBuilderEra dstEra) => TxOutValue srcEra -> TxOutValue dstEra 60 | updateToutValue val = updateToutValue' bCardanoEra val 61 | 62 | 63 | -- case val of 64 | -- TxOutValueByron lo -> TxOutValueShelleyBased bShelleyBasedEra (toLedgerValue bMaryOnward (lovelaceToValue lo)) 65 | -- TxOutValueShelleyBased sbe va -> TxOutValueShelleyBased bShelleyBasedEra (toLedgerValue bMaryOnward (fromLedgerValue sbe va)) 66 | 67 | -- TxOutAdaOnly btae lo -> TxOutValue bMaryOnward (lovelaceToValue lo) 68 | -- TxOutValue meo va -> TxOutValue bMaryOnward va 69 | 70 | updateAddressEra :: IsTxBuilderEra era => AddressInEra era1 -> AddressInEra era 71 | updateAddressEra addr = addrInfo 72 | where 73 | addrInfo = case addr of 74 | AddressInEra atie ad -> case atie of 75 | ByronAddressInAnyEra -> AddressInEra ByronAddressInAnyEra ad 76 | ShelleyAddressInEra sbe' -> AddressInEra (ShelleyAddressInEra bShelleyBasedEra) ad 77 | 78 | updatePParamEra :: CardanoEra era -> LedgerProtocolParameters ConwayEra -> LedgerProtocolParameters era 79 | updatePParamEra cera ulP@(LedgerProtocolParameters pparam) = case cera of 80 | BabbageEra -> (LedgerProtocolParameters (downgradePParams () pparam) :: LedgerProtocolParameters BabbageEra) 81 | ConwayEra -> ulP 82 | _ -> error "Unexpected" 83 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Error.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE LambdaCase #-} 3 | module Cardano.Kuber.Error 4 | where 5 | 6 | import GHC.Exception.Type (Exception) 7 | import Data.Aeson (object, ToJSON (toJSON), KeyValue ((.=)), FromJSON (parseJSON)) 8 | import Control.Exception (throw) 9 | import Cardano.Api 10 | import PlutusLedgerApi.V1 (EvaluationError(..)) 11 | import qualified Data.Map as Map 12 | import qualified Data.Text as T 13 | import qualified Data.ByteString.Char8 as BS8 14 | import Data.List (intercalate) 15 | import qualified Data.Aeson as A 16 | 17 | data ErrorType = ConnectionError 18 | | BalancingError 19 | | InsufficientInput 20 | | EraMisMatch 21 | | NodeQueryError 22 | | LibraryError 23 | | ParserError 24 | | PlutusScriptError -- Plutus Script was executed and it returned error 25 | | PlutusExecutionError -- There was error executing the plutus script . We couldn't execute the script. 26 | | ExUnitCalculationError 27 | | FeatureNotSupported 28 | | TxValidationError 29 | | BadMetadata 30 | | TxSubmissionError 31 | | WrongScriptType deriving (Show,Eq) 32 | 33 | 34 | 35 | data FrameworkError = FrameworkError{ 36 | feType:: ErrorType, 37 | feMessage :: String 38 | } 39 | | FrameworkErrors [FrameworkError] 40 | deriving (Eq) 41 | 42 | instance FromJSON FrameworkError where 43 | parseJSON (A.Object o) = do 44 | eTypeStr <- o A..: "type" 45 | etype <- case eTypeStr ::String of 46 | "ConnectionError" -> pure ConnectionError 47 | "BalancingError" -> pure BalancingError 48 | "InsufficientInput" -> pure InsufficientInput 49 | "EraMisMatch" -> pure EraMisMatch 50 | "NodeQueryError" -> pure NodeQueryError 51 | "LibraryError" -> pure LibraryError 52 | "ParserError" -> pure ParserError 53 | "PlutusScriptError" -> pure PlutusScriptError 54 | "PlutusExecutionError" -> pure PlutusExecutionError 55 | "ExUnitCalculationError" -> pure ExUnitCalculationError 56 | "FeatureNotSupported" -> pure FeatureNotSupported 57 | "TxValidationError" -> pure TxValidationError 58 | "BadMetadata" -> pure BadMetadata 59 | "TxSubmissionError" -> pure TxSubmissionError 60 | "WrongScriptType" -> pure WrongScriptType 61 | _ -> fail "Invalid error type" 62 | message <- o A..: "message" 63 | pure $ FrameworkError etype message 64 | parseJSON _ = fail "Expected FrameworkError object" 65 | 66 | 67 | instance Show FrameworkError where 68 | show (FrameworkError t m)= "FrameworkError: "++show t ++ ": "++show m 69 | show (FrameworkErrors errs)= "FrameworkErrors: [" ++ concatMap (\(FrameworkError t m) -> show errs ++ ": "++show m) errs ++ "]" 70 | 71 | showsPrec _ (FrameworkError t m) = 72 | showString "FrameworkError: " . 73 | shows t . 74 | showString ": " . 75 | shows m 76 | 77 | showsPrec _ (FrameworkErrors []) = 78 | showString "FrameworkErrors: []" 79 | 80 | showsPrec p (FrameworkErrors errs) = 81 | showParen (p > 10) $ 82 | showString $ "FrameworkErrors: [" ++ foldr (\err -> (++) (shows err ", ")) "" errs++ "]" 83 | 84 | 85 | instance ToJSON FrameworkError where 86 | toJSON (FrameworkError t m) = object ["type" .= show t, "message" .= m] 87 | toJSON (FrameworkErrors errs) = object [ "messages" .= errs] 88 | 89 | 90 | instance Exception FrameworkError 91 | 92 | 93 | throwFrameworkError :: Applicative m => Either FrameworkError a-> m a 94 | throwFrameworkError = \case 95 | Left e -> throw e 96 | Right v -> pure v 97 | 98 | 99 | fromScriptExecutionError :: ScriptExecutionError -> TxBody era -> FrameworkError 100 | fromScriptExecutionError see _txbody= case see of 101 | ScriptErrorMissingTxIn ti -> makeErr ("Input Missing : " ++ T.unpack (renderTxIn ti)) 102 | ScriptErrorTxInWithoutDatum ti -> makeErr ("Input doesn't have datum " ++ T.unpack (renderTxIn ti)) 103 | ScriptErrorWrongDatum ha -> makeErr ("Worng datum provided for hash " ++ BS8.unpack (serialiseToRawBytesHex ha)) 104 | ScriptErrorEvaluationFailed ee txts -> case ee of 105 | CekError ewc -> mkPlutusErr ("CekError : " ++ show ewc ++ " : " ++ show txts) 106 | DeBruijnError fve -> mkPlutusErr ("DeBruijnError : " ++ show fve ++ " : " ++ show txts) 107 | CodecError df -> mkPlutusErr ("CodecError Deserialization : " ++ show df ++ " : " ++ show txts) 108 | CostModelParameterMismatch -> mkPlutusErr "Unexpected costModel Parameter Mismatch" 109 | InvalidReturnValue -> mkPlutusErr "Script returned invalid type" 110 | ScriptErrorExecutionUnitsOverflow -> makeErr "Execution Units Overflowed " 111 | ScriptErrorNotPlutusWitnessedTxIn _swi sh -> makeErr $ "Trying to execute non-plutus script : " ++ BS8.unpack (serialiseToRawBytesHex sh) 112 | ScriptErrorRedeemerPointsToUnknownScriptHash swi -> makeErr $ "Unknown scriptHash for " ++ (case swi of 113 | ScriptWitnessIndexTxIn wo -> "Redeeming script at index : " ++ show wo 114 | ScriptWitnessIndexMint wo -> "Minting Script at index: " ++ show wo 115 | ScriptWitnessIndexCertificate wo -> "Certificate Script at index : " ++ show wo 116 | ScriptWitnessIndexWithdrawal wo -> "Withdrawal Script at index : " ++ show wo --TODO Show script Hash too 117 | ScriptWitnessIndexProposing wo -> "Proposal Script at index : " ++ show wo --TODO Show script Hash too 118 | ScriptWitnessIndexVoting wo -> "Voting Script at index : " ++ show wo) --TODO Show script Hash too 119 | ScriptErrorMissingScript _rp (ResolvablePointers _era pointerMp) -> makeErr $ "Missing script : " ++ show pointerMp 120 | ScriptErrorMissingCostModel _lan -> makeErr "Unexpected costModel Parameter Mismatch" 121 | ScriptErrorTranslationError contextErr -> makeErr $ "ContextTranslation Error" ++ show contextErr 122 | where 123 | makeErr = FrameworkError PlutusExecutionError 124 | mkPlutusErr = FrameworkError PlutusScriptError 125 | 126 | renderResolvablePointers mp = intercalate ", " $ map (\ (_ , (sp, mScript,scHash)) -> show sp ++ show scHash) $ Map.toList mp 127 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Http/MediaType.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE OverloadedStrings#-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE MultiParamTypeClasses #-} 4 | {-# LANGUAGE MonoLocalBinds #-} 5 | {-# LANGUAGE InstanceSigs #-} 6 | {-# LANGUAGE FlexibleContexts #-} 7 | {-# LANGUAGE RankNTypes #-} 8 | {-# LANGUAGE GADTs #-} 9 | 10 | module Cardano.Kuber.Http.MediaType 11 | where 12 | 13 | import Network.HTTP.Media ((//), (/:), MediaType) 14 | import Servant.API.ContentTypes (Accept (contentTypes), MimeUnrender (mimeUnrender), MimeRender (mimeRender)) 15 | import GHC.Base (NonEmpty ((:|))) 16 | import qualified Data.ByteString.Lazy as BSL 17 | import qualified Data.ByteString.Char8 as BS8 18 | import qualified Data.Text as T 19 | import qualified Data.Text.Encoding as T 20 | 21 | import qualified Data.Text.Lazy as TL 22 | import qualified Data.Text.Lazy.Encoding as TL 23 | import Data.Text.Conversions (Base16(Base16), convertText) 24 | import Data.Data (Proxy (Proxy)) 25 | import qualified Data.ByteString.Lazy 26 | import Cardano.Kuber.Data.Models (SubmitTxModal(SubmitTxModal), TxModal (TxModal)) 27 | import Data.ByteString.Lazy (fromStrict) 28 | import Cardano.Kuber.Core.TxBuilder (IsTxBuilderEra (bAsEra)) 29 | import Cardano.Api.Shelley (AsType(..)) 30 | import Cardano.Api (InAnyCardanoEra(..), Tx, SerialiseAsCBOR (deserialiseFromCBOR, serialiseToCBOR), CardanoEra (..)) 31 | import qualified Data.ByteString.Lazy as BS 32 | import Cardano.Kuber.Data.Parsers (parseRawTxInAnyEra) 33 | 34 | data AnyTextType = AnyTextType 35 | 36 | instance Accept AnyTextType where 37 | contentTypes _ = "text" // "plain" :| [ "text" // "*", "text" // "plain" /: ("charset", "utf-8") ] 38 | 39 | instance MimeUnrender AnyTextType String where 40 | mimeUnrender _ bs = Right $ BS8.unpack $ BSL.toStrict bs 41 | 42 | instance MimeUnrender AnyTextType TL.Text where 43 | mimeUnrender _ bs = Right $ TL.decodeUtf8 bs 44 | 45 | instance MimeUnrender AnyTextType T.Text where 46 | mimeUnrender :: Proxy AnyTextType -> Data.ByteString.Lazy.ByteString -> Either String T.Text 47 | mimeUnrender _ bs = Right $ T.decodeUtf8 $ BSL.toStrict bs 48 | 49 | data CBORBinary =CBORBinary 50 | 51 | instance Accept CBORBinary where 52 | contentTypes _ = "octet" // "stream" :| [ "application" // "cbor"] 53 | 54 | instance IsTxBuilderEra era => MimeUnrender CBORBinary ( Tx era ) where 55 | mimeUnrender _ bs = case deserialiseFromCBOR (AsTx bAsEra) ( Data.ByteString.Lazy.toStrict bs) of 56 | Left e -> Left $ "Tx string: Invalid CBOR format : " ++ show e 57 | Right tx -> pure tx 58 | 59 | instance MimeUnrender CBORBinary TxModal where 60 | mimeUnrender proxy bs = do 61 | case parseRawTxInAnyEra ( BS.toStrict bs) of 62 | Just result -> pure $ TxModal result 63 | Nothing -> Left "Tx string: Invalid CBOR format " 64 | 65 | instance MimeRender CBORBinary TxModal where 66 | mimeRender p (TxModal (InAnyCardanoEra era tx)) = case era of 67 | ShelleyEra -> fromStrict $ serialiseToCBOR tx 68 | AllegraEra -> fromStrict $ serialiseToCBOR tx 69 | MaryEra -> fromStrict $ serialiseToCBOR tx 70 | AlonzoEra -> fromStrict $ serialiseToCBOR tx 71 | BabbageEra -> fromStrict $ serialiseToCBOR tx 72 | ConwayEra -> fromStrict $ serialiseToCBOR tx 73 | -- fromStrict $ serialiseToCBOR tx 74 | 75 | 76 | 77 | instance MimeUnrender CBORBinary SubmitTxModal where 78 | mimeUnrender proxy bs = do 79 | (TxModal result) <- mimeUnrender proxy bs 80 | pure $ SubmitTxModal result Nothing 81 | 82 | 83 | data CBORText =CBORText 84 | 85 | instance Accept CBORText where 86 | contentTypes :: Proxy CBORText -> NonEmpty MediaType 87 | contentTypes _ = "text" // "plain" :| [ "text" // "cbor" , "text" //"hex", "text" // "plain" /: ("charset", "utf-8") ] 88 | 89 | instance (MimeUnrender CBORBinary a) => MimeUnrender CBORText a where 90 | mimeUnrender _ bs = case convertText (BS8.unpack $ BSL.toStrict bs) of 91 | Nothing -> Left "Expected hex encoded CBOR string" 92 | Just (Base16 bs) -> mimeUnrender (Proxy :: (Proxy CBORBinary)) bs 93 | 94 | instance (MimeRender CBORBinary a) => MimeRender CBORText a where 95 | mimeRender :: MimeRender CBORBinary a => Proxy CBORText -> a -> Data.ByteString.Lazy.ByteString 96 | mimeRender proxy obj= mimeRender (Proxy :: (Proxy CBORBinary)) obj -------------------------------------------------------------------------------- /src/Cardano/Kuber/Http/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE TypeOperators #-} 3 | 4 | module Cardano.Kuber.Http.Spec 5 | ( KuberServerApi, 6 | QueryApi, 7 | CardanoApi, 8 | KuberApi, 9 | UtilityApi, 10 | kuberApiServerProxy, 11 | CBORBinary, 12 | CBORText, 13 | AnyTextType, 14 | ) 15 | where 16 | 17 | import Cardano.Api.Ledger (Coin) 18 | import Cardano.Api.Shelley 19 | import Cardano.Kuber.Core.TxBuilder (TxBuilder_) 20 | import Cardano.Kuber.Data.Models 21 | import Cardano.Kuber.Http.MediaType 22 | import Data.Data (Proxy (Proxy)) 23 | import Data.Text 24 | import Servant.API 25 | 26 | type KuberServerApi era = 27 | "api" :> "v3" :> QueryApi era 28 | :<|> "api" :> "v3" :> CardanoApi era 29 | :<|> "api" 30 | :> "v1" 31 | :> ( KuberApi era 32 | :<|> UtilityApi 33 | ) 34 | 35 | type QueryApi era = 36 | "protocol-params" :> Get '[JSON] (LedgerProtocolParameters era) 37 | :<|> "chain-point" :> Get '[JSON] ChainPointModal 38 | :<|> "utxo" :> QueryParams "address" Text :> QueryParams "txin" Text :> Get '[JSON] (UtxoModal ConwayEra) 39 | 40 | type CardanoApi era = 41 | "system-start" :> Get '[JSON] SystemStartModal 42 | :<|> "current-era" :> Get '[JSON] AnyCardanoEraModal 43 | :<|> "genesis-params" :> Get '[JSON] (GenesisParamModal ShelleyEra) 44 | :<|> "health" :> Get '[JSON] HealthStatusModal 45 | 46 | 47 | type KuberApi era = 48 | "tx" :> QueryParam "submit" Bool :> ReqBody '[JSON] (TxBuilder_ era) :> Post '[JSON] TxModal 49 | :<|> "tx" :> "submit" :> ReqBody '[JSON] SubmitTxModal :> Post '[JSON] TxModal 50 | :<|> "time" :> Get '[JSON] TranslationResponse 51 | :<|> "time" :> "toslot" :> ReqBody '[JSON] TimeTranslationReq :> Post '[JSON] TranslationResponse 52 | :<|> "time" :> "fromSlot" :> ReqBody '[JSON] SlotTranslationReq :> Post '[JSON] TranslationResponse 53 | 54 | type UtilityApi = 55 | -- "tx" :> "fee" :> QueryParams "shelleyWitCount" :> QueryParams "ByronWitCount" :> ReqBody '[ CBORBinary,CBORText,JSON ] TxModal :> Post '[JSON] Lovelace 56 | "tx" :> "fee" :> ReqBody '[CBORBinary, CBORText, JSON] TxModal :> Post '[JSON] Coin 57 | :<|> "tx" :> "exUnits" :> ReqBody '[CBORBinary, CBORText, CBORText] TxModal :> Post '[JSON] ExUnitsResponseModal 58 | 59 | kuberApiServerProxy :: Proxy (KuberServerApi era) 60 | kuberApiServerProxy = Proxy -------------------------------------------------------------------------------- /src/Cardano/Kuber/Util.hs: -------------------------------------------------------------------------------- 1 | module Cardano.Kuber.Util 2 | ( -- TypeCast/Conversion Utilities (Address/Pkh/SignKey) 3 | skeyToAddr, 4 | skeyToAddrInEra, 5 | sKeyToPkh, 6 | fromLedgerAddress, 7 | addressInEraToAddressAny, 8 | pkhToMaybeAddr, 9 | addrToMaybePkh, 10 | addrInEraToPkh, 11 | addressInEraToPaymentKeyHash, 12 | addressNetworkId, 13 | -- TypeCast/Conversion Utilities (PlutusTypes) 14 | dataToScriptData, 15 | toPlutusAssetClass, 16 | fromPlutusData, 17 | fromPlutusAddress, 18 | toPlutusAddress, 19 | toPlutusCredential, 20 | plutusAssetClassToAssetId, 21 | addrInEraToPlutusAddress, 22 | addressToPlutusCredential, 23 | fromPlutusV1Script, 24 | fromPlutusValue, 25 | toPlutusValue, 26 | fromPlutusV2Script, 27 | toPlutusTxOutRef, 28 | -- Value utility and utxoto Value 29 | isNullValue, 30 | valueLte, 31 | isPositiveValue, 32 | filterNegativeQuantity, 33 | utxoListSum, 34 | utxoSum, 35 | utxoMapSum, 36 | txoutListSum, 37 | -- calculation functions 38 | calculateTxoutMinLovelaceOrErr, 39 | calculateTxoutMinLovelace, 40 | evaluateFee, 41 | txoutMinLovelace, 42 | -- query helpers 43 | queryUtxos, 44 | queryTxins, 45 | queryAddressInEraUtxos, 46 | -- metadata utility 47 | splitMetadataStrings, 48 | -- wallet utilities 49 | readSignKey, 50 | getDefaultSignKey, 51 | -- text utilities 52 | toHexString, 53 | unHex, 54 | unHexLazy, 55 | getDefaultConnection, 56 | -- time conversion utility 57 | timestampToSlot, 58 | slotToTimestamp, 59 | ) 60 | where 61 | 62 | import Cardano.Api 63 | -- import Shelley.Spec.Ledger.API (Credential(ScriptHashObj, KeyHashObj), KeyHash (KeyHash), StakeReference (StakeRefNull)) 64 | 65 | import Cardano.Api.Shelley (Address (ShelleyAddress), TxBody (ShelleyTxBody), fromPlutusData, fromShelleyAddr, fromShelleyPaymentCredential, fromShelleyScriptHash, fromShelleyStakeCredential, fromShelleyStakeReference, fromShelleyTxIn, toShelleyAddr, toShelleyTxOut) 66 | import qualified Cardano.Api.Shelley as Shelley 67 | import qualified Cardano.Binary as Cborg 68 | import Cardano.Kuber.Core.ChainInfo 69 | import Cardano.Kuber.Error (ErrorType (ExUnitCalculationError, FeatureNotSupported, PlutusScriptError, WrongScriptType), FrameworkError (FrameworkError)) 70 | import Cardano.Kuber.Utility.ChainInfoUtil 71 | import Cardano.Kuber.Utility.DataTransformation 72 | -- import qualified Cardano.Ledger.Mary.Value as Ledger 73 | 74 | import Cardano.Kuber.Utility.Misc 75 | import Cardano.Kuber.Utility.QueryHelper 76 | import Cardano.Kuber.Utility.ScriptUtil (fromPlutusV1Script, fromPlutusV2Script) 77 | import Cardano.Kuber.Utility.Text 78 | import Cardano.Kuber.Utility.WalletUtil (getDefaultSignKey, readSignKey) 79 | import qualified Cardano.Ledger.Alonzo as Alonzo 80 | import qualified Cardano.Ledger.Babbage.Tx as LedgerBody 81 | import Cardano.Ledger.Babbage.TxBody (btbInputs, mint') 82 | import Cardano.Ledger.Crypto (StandardCrypto) 83 | import Cardano.Ledger.Shelley.API (Credential (KeyHashObj, ScriptHashObj), KeyHash (KeyHash), StakeReference (StakeRefNull)) 84 | import qualified Cardano.Ledger.Shelley.API as Ledger 85 | import qualified Cardano.Ledger.Shelley.API.Wallet as Shelley 86 | import Cardano.Ledger.Shelley.UTxO (txins) 87 | import Cardano.Slotting.Time (RelativeTime, SystemStart, fromRelativeTime, toRelativeTime) 88 | import qualified Codec.CBOR.Encoding as Cborg 89 | import qualified Codec.CBOR.Write as Cborg 90 | import Codec.Serialise (serialise) 91 | import Control.Exception (throw, try) 92 | import qualified Data.Aeson as A 93 | import qualified Data.Aeson as JSON 94 | import qualified Data.Aeson.KeyMap as A 95 | import Data.Bifunctor (Bifunctor (bimap)) 96 | import Data.ByteString (ByteString, readFile) 97 | import Data.ByteString.Builder (charUtf8) 98 | import qualified Data.ByteString.Builder as BSL 99 | import qualified Data.ByteString.Builder as Builder 100 | import Data.ByteString.Lazy (fromStrict, toStrict) 101 | import qualified Data.ByteString.Lazy as LBS 102 | import qualified Data.ByteString.Short as SBS 103 | import Data.Char (toLower) 104 | import qualified Data.Char as C 105 | import Data.Either (partitionEithers) 106 | import Data.Functor ((<&>)) 107 | import qualified Data.HashMap.Strict as HashMap 108 | import Data.Int 109 | import Data.List (intercalate) 110 | import Data.Map (Map) 111 | import qualified Data.Map as Map 112 | import Data.Set (Set) 113 | import qualified Data.Set as Set 114 | import Data.String (fromString) 115 | import qualified Data.Text as T 116 | import Data.Text.Conversions (Base16 (Base16, unBase16), FromText (fromText), ToText (toText), convertText) 117 | import Data.Text.Encoding 118 | import qualified Data.Text.Encoding as TSE 119 | import qualified Data.Text.IO as TextIO 120 | import Data.Time (NominalDiffTime, UTCTime) 121 | import Data.Time.Clock.POSIX (POSIXTime, posixSecondsToUTCTime, utcTimeToPOSIXSeconds) 122 | import qualified Data.Vector as Vector 123 | import Data.Word (Word64) 124 | import qualified Debug.Trace as Debug 125 | import Ouroboros.Consensus.HardFork.History (unsafeExtendSafeZone) 126 | import qualified Ouroboros.Consensus.HardFork.History as Qry 127 | import Ouroboros.Network.Protocol.LocalTxSubmission.Client (SubmitResult (SubmitFail, SubmitSuccess)) 128 | import PlutusLedgerApi.V2 (Address, CurrencySymbol (CurrencySymbol), PubKeyHash (PubKeyHash), ToData, TokenName (TokenName), fromBuiltin, toBuiltin, toData) 129 | import System.Directory (doesFileExist) 130 | import System.Environment (getEnv) 131 | import System.FilePath (joinPath) 132 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Utility/ChainInfoUtil.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | 3 | module Cardano.Kuber.Utility.ChainInfoUtil where 4 | 5 | import Cardano.Api (ConsensusModeParams (CardanoModeParams), EpochSlots (EpochSlots), File (File), LocalNodeConnectInfo (LocalNodeConnectInfo), NetworkId (Mainnet, Testnet), NetworkMagic (NetworkMagic), SocketPath) 6 | import Cardano.Kuber.Core.LocalNodeChainApi (ChainConnectInfo (..)) 7 | import Cardano.Kuber.Error (ErrorType (ParserError), FrameworkError (FrameworkError)) 8 | import Control.Exception (throw, try) 9 | import Data.Char (toLower) 10 | import System.Directory (doesFileExist) 11 | import System.Environment (getEnv) 12 | import System.FilePath (joinPath) 13 | import Text.Read (readMaybe) 14 | 15 | -- type wrapper for EnvironmentVariable String 16 | type EnvVariable = String 17 | 18 | -- Helper function to get Node conenction information 19 | localNodeConnInfo :: NetworkId -> SocketPath -> LocalNodeConnectInfo 20 | localNodeConnInfo = LocalNodeConnectInfo (CardanoModeParams (EpochSlots 21600)) 21 | 22 | -- Using Environment variables get mainnet's ConnectInfo 23 | -- If CARDANO_NODE_SOCKET_PATH environment variable is set, the socket path is set to it's value 24 | -- Otherwise CARDANO_HOME or "$HOME/.cardano" is used and the socket path becomes "$CARDANO_HOME/.cardano/mainnet/node.socket" 25 | chainInfoMainnet :: IO ChainConnectInfo 26 | chainInfoMainnet = do 27 | conn <- getDefaultConnection "mainnet" Mainnet 28 | pure $ ChainConnectInfo conn 29 | 30 | -- Using Environment variables get testnet's ConnectInfo 31 | -- If CARDANO_NODE_SOCKET_PATH environment variable is set, the socket path is set to it's value 32 | -- Otherwise CARDANO_HOME or "$HOME/.cardano" is used and the socket path becomes "$CARDANO_HOME/.cardano/testnet/node.socket" 33 | chainInfoTestnet :: IO ChainConnectInfo 34 | chainInfoTestnet = do 35 | let network = Testnet (NetworkMagic 1097911063) 36 | conn <- getDefaultConnection "testnet" network 37 | pure $ ChainConnectInfo conn 38 | 39 | chainInfoPreprod :: IO ChainConnectInfo 40 | chainInfoPreprod = do 41 | let network = Testnet (NetworkMagic 1) 42 | conn <- getDefaultConnection "preprod" network 43 | pure $ ChainConnectInfo conn 44 | 45 | chainInfoPreview :: IO ChainConnectInfo 46 | chainInfoPreview = do 47 | let network = Testnet (NetworkMagic 2) 48 | conn <- getDefaultConnection "preview" network 49 | pure $ ChainConnectInfo conn 50 | 51 | -- | Using Environment variables determine the NETWORK. 52 | -- NETWORK can be "mainnet", "testnet" or "networkMagic number". 53 | -- If CARDANO_NODE_SOCKET_PATH environment variable is set, the socket path is set to it's value 54 | -- Otherwise CARDANO_HOME or "$HOME/.cardano" is used and the socket path becomes "$CARDANO_HOME/node.socket" 55 | chainInfoFromEnv :: IO ChainConnectInfo 56 | chainInfoFromEnv = chainInfoFromEnv' "NETWORK" 57 | 58 | -- | Read Network value from the environment variable and then determine connection info 59 | -- If CARDANO_NODE_SOCKET_PATH environment variable is set, the socket path is set to it's value 60 | -- Otherwise CARDANO_HOME or "$HOME/.cardano" is used and the socket path becomes "$CARDANO_HOME/node.socket" 61 | chainInfoFromEnv' :: EnvVariable -> IO ChainConnectInfo 62 | chainInfoFromEnv' envKey = do 63 | (name, network) <- getNetworkFromEnv envKey 64 | conn <- getDefaultConnection name network 65 | pure $ ChainConnectInfo conn 66 | 67 | -- | If CARDANO_NODE_SOCKET_PATH environment variable is set, return ConnectInfo instance with the path 68 | -- Otherwise CARDANO_HOME or "$HOME/.cardano" is used and the socket path becomes "$CARDANO_HOME/node.socket" 69 | getDefaultConnection :: String -> NetworkId -> IO LocalNodeConnectInfo 70 | getDefaultConnection networkName networkId = do 71 | sockEnv <- try $ getEnv "CARDANO_NODE_SOCKET_PATH" 72 | socketPath <- case sockEnv of 73 | Left (e :: IOError) -> do 74 | defaultSockPath <- getWorkPath (if null networkName then ["node.socket"] else [networkName, "node.socket"]) 75 | exists <- doesFileExist defaultSockPath 76 | if exists then return defaultSockPath else error $ "Socket File is Missing: " ++ defaultSockPath ++ "\n\tSet environment variable CARDANO_NODE_SOCKET_PATH to use different path" 77 | Right s -> pure s 78 | pure (localNodeConnInfo networkId (File socketPath)) 79 | 80 | -- | Given environment variable key, read the environmet variable and return network Id. The value maybe network name or network magic. 81 | getNetworkFromEnv :: EnvVariable -> IO (String, NetworkId) 82 | getNetworkFromEnv envKey = do 83 | networkEnv <- try $ getEnv envKey 84 | case networkEnv of 85 | Left (e :: IOError) -> do 86 | pure ("preprod", Testnet (NetworkMagic 1)) 87 | Right s -> pure $ case map toLower s of 88 | "mainnet" -> ("mainnet", Mainnet) 89 | "testnet" -> ("testnet", Testnet (NetworkMagic 1097911063)) 90 | "preprod" -> ("preprod", Testnet (NetworkMagic 1)) 91 | "preview" -> ("preview", Testnet (NetworkMagic 2)) 92 | "sancho" -> pure (Testnet $ NetworkMagic 4) 93 | "sanchonet" -> pure (Testnet $ NetworkMagic 4) 94 | val -> do 95 | case readMaybe s of 96 | Just v -> ("", Testnet (NetworkMagic v)) 97 | _ -> throw $ FrameworkError ParserError ("Invalid network id : " ++ val) 98 | 99 | -- | get absolute path given a directory or file path relative to work directory. 100 | -- the absolute path is "CARANO_HOME/...paths" value to the path 101 | getWorkPath :: [FilePath] -> IO FilePath 102 | getWorkPath paths = do 103 | f <- getWorkPathFunc 104 | pure $ f paths 105 | 106 | -- | Get WrokPath calculatin Function 107 | -- getWorkPath function can throw errors. The error is only during initialization 108 | -- So if the function succeeds, it returns pure function to calculate filepath which can be reused. 109 | getWorkPathFunc :: IO ([FilePath] -> FilePath) 110 | getWorkPathFunc = do 111 | eitherHome <- try $ getEnv "HOME" 112 | eitherCardanoHome <- try $ getEnv "CARDANO_HOME" 113 | case eitherCardanoHome of 114 | Left (e :: IOError) -> case eitherHome of 115 | Left (e :: IOError) -> error "Can't get Home directory. Missing HOME and CARDANO_HOME" 116 | Right home -> pure $ f [home, ".cardano"] 117 | Right home -> pure $ f [home] 118 | where 119 | f a b = joinPath $ a ++ b 120 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Utility/ScriptUtil.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TypeFamilies #-} 2 | module Cardano.Kuber.Utility.ScriptUtil where 3 | import Cardano.Api 4 | import Cardano.Kuber.Error 5 | import Cardano.Api.Shelley (PlutusScriptOrReferenceInput (PScript, PReferenceScript), SimpleScriptOrReferenceInput (..), PlutusScript (PlutusScriptSerialised)) 6 | import PlutusTx (CompiledCode, BuiltinData) 7 | import PlutusLedgerApi.Common (serialiseCompiledCode) 8 | import Cardano.Kuber.Core.TxScript 9 | import Data.Functor ((<&>)) 10 | import PlutusTx.Prelude (BuiltinUnit) 11 | 12 | 13 | type TxScriptParsed witctx era = Either 14 | ( ScriptDatum witctx 15 | -> ScriptRedeemer 16 | -> ExecutionUnits 17 | -> ScriptWitness witctx era) 18 | (ScriptWitness witctx era) 19 | validateScriptSupportedInEra' :: ShelleyBasedEra era -> ScriptInAnyLang -> Either FrameworkError (ScriptInEra era) 20 | validateScriptSupportedInEra' era script@(ScriptInAnyLang lang _) = 21 | case toScriptInEra era script of 22 | Nothing -> Left $ FrameworkError WrongScriptType (show lang ++ " not supported in " ++ show era ++ " era") 23 | Just script' -> pure script' 24 | 25 | makeTxPlutusScriptWitness :: 26 | ShelleyBasedEra era -> 27 | TxPlutusScript -> 28 | Maybe TxIn -> 29 | Either FrameworkError (ScriptDatum witctx -> ScriptRedeemer -> ExecutionUnits -> ScriptWitness witctx era) 30 | makeTxPlutusScriptWitness era script mtxIn = case script of 31 | TxPlutusScriptV1 ps -> do 32 | langInEra <- validatePv1 33 | pure $ PlutusScriptWitness langInEra PlutusScriptV1 (toWitnessPScript ps ) 34 | TxPlutusScriptV2 ps -> do 35 | langInEra <- validatePv2 36 | pure $ PlutusScriptWitness langInEra PlutusScriptV2 (toWitnessPScript ps ) 37 | TxPlutusScriptV3 ps -> do 38 | langInEra <- validatePv3 39 | pure $ PlutusScriptWitness langInEra PlutusScriptV3 (toWitnessPScript ps ) 40 | where 41 | toWitnessPScript :: PlutusScript lang-> PlutusScriptOrReferenceInput lang 42 | toWitnessPScript ps = case mtxIn of 43 | Nothing -> PScript ps 44 | Just ti -> PReferenceScript ti Nothing 45 | validatePv1 = validateLang era (PlutusScriptLanguage PlutusScriptV1) $ "PlutusScriptV1 not supported in " ++ show era 46 | validatePv2 = validateLang era (PlutusScriptLanguage PlutusScriptV2) $ "PlutusScriptV2 not supported in " ++ show era 47 | validatePv3 = validateLang era (PlutusScriptLanguage PlutusScriptV3) $ "PlutusScriptV3 not supported in " ++ show era 48 | 49 | makeTxSimpleScriptWitness :: ShelleyBasedEra era -> SimpleScript -> Maybe TxIn -> Either FrameworkError (ScriptWitness witctx era) 50 | makeTxSimpleScriptWitness cera simpleSc mtxIn = do 51 | lang <- validateSimpleScript 52 | pure $ SimpleScriptWitness lang (case mtxIn of 53 | Nothing -> SScript simpleSc 54 | Just ti -> SReferenceScript ti Nothing) 55 | 56 | where 57 | validateSimpleScript = validateLang cera SimpleScriptLanguage ("Simple Script not supported for this era" ++ show cera) 58 | 59 | 60 | makeTxScriptWitness :: ShelleyBasedEra era -> TxScript -> Maybe TxIn -> Either FrameworkError (TxScriptParsed witctx era) 61 | makeTxScriptWitness era script mtxIn = case script of 62 | TxScriptSimple tss -> do 63 | lang <- validateSimpleScript 64 | pure$ Right $ SimpleScriptWitness lang (case mtxIn of 65 | Nothing -> SScript tss 66 | Just ti -> SReferenceScript ti Nothing) 67 | -- pure $ ScriptInEra lang (SimpleScript tss) 68 | TxScriptPlutus tps -> makeTxPlutusScriptWitness era tps mtxIn <&> Left 69 | where 70 | toWitnessPScript :: PlutusScript lang-> PlutusScriptOrReferenceInput lang 71 | toWitnessPScript ps = case mtxIn of 72 | Nothing -> PScript ps 73 | Just ti -> PReferenceScript ti Nothing 74 | validateSimpleScript = validateLang era SimpleScriptLanguage ("Simple script not supported for this era" ++ show era) 75 | 76 | 77 | validateLang :: ShelleyBasedEra era2 -> ScriptLanguage lang-> String-> Either FrameworkError (ScriptLanguageInEra lang era2) 78 | validateLang era lang msg = case scriptLanguageSupportedInEra era lang of 79 | Nothing -> Left $ FrameworkError FeatureNotSupported msg 80 | Just scInEra -> pure scInEra 81 | 82 | fromPlutusV3Script :: CompiledCode (BuiltinData -> BuiltinUnit) -> Script PlutusScriptV3 83 | fromPlutusV3Script plutusScript = PlutusScript PlutusScriptV3 $ PlutusScriptSerialised $ serialiseCompiledCode plutusScript 84 | 85 | fromPlutusV2Script :: CompiledCode (BuiltinData -> BuiltinData -> BuiltinData -> BuiltinUnit) -> Script PlutusScriptV2 86 | fromPlutusV2Script plutusScript = PlutusScript PlutusScriptV2 $ PlutusScriptSerialised $ serialiseCompiledCode plutusScript 87 | 88 | fromPlutusV1Script :: CompiledCode (BuiltinData -> BuiltinData -> BuiltinData-> BuiltinUnit) -> Script PlutusScriptV1 89 | fromPlutusV1Script plutusScript = PlutusScript PlutusScriptV1 $ PlutusScriptSerialised $ serialiseCompiledCode plutusScript 90 | -------------------------------------------------------------------------------- /src/Cardano/Kuber/Utility/Text.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | module Cardano.Kuber.Utility.Text where 3 | import Data.Text.Conversions (FromText (fromText), ToText (toText), Base16 (Base16, unBase16), convertText) 4 | import Data.ByteString ( ByteString ) 5 | import Data.Functor ((<&>)) 6 | import qualified Data.ByteString.Lazy as LBS 7 | 8 | 9 | toHexString :: (FromText a1, ToText (Base16 a2)) => a2 -> a1 10 | toHexString bs = fromText $ toText (Base16 bs ) 11 | 12 | 13 | unHexLazy :: ToText a => a -> Maybe LBS.ByteString 14 | unHexLazy v = convertText (toText v) <&> unBase16 15 | 16 | unHexStrict :: ToText a => a -> Maybe ByteString 17 | unHexStrict v = convertText (toText v) <&> unBase16 18 | 19 | unHex :: (Functor f, FromText (f (Base16 b)), ToText a) => a -> f b 20 | unHex v = convertText (toText v) <&> unBase16 -------------------------------------------------------------------------------- /src/Cardano/Kuber/Utility/WalletUtil.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | module Cardano.Kuber.Utility.WalletUtil where 3 | import Cardano.Api (SigningKey, PaymentKey, TxBody, ConwayEra, Tx, makeSignedTransaction, makeShelleyKeyWitness, ShelleyWitnessSigningKey (WitnessPaymentKey), IsShelleyBasedEra (shelleyBasedEra)) 4 | import qualified Data.Text as T 5 | import qualified Data.Text.IO as TextIO 6 | import System.Directory ( doesFileExist ) 7 | import Cardano.Kuber.Data.Parsers ( parseSignKey ) 8 | import Cardano.Kuber.Utility.ChainInfoUtil ( getWorkPath ) 9 | import Control.Exception (try) 10 | 11 | 12 | -- readSignKey relative to CARDANO_HOME path 13 | -- this funciton can throw errors 14 | readSignKey :: FilePath -> IO (SigningKey PaymentKey) 15 | readSignKey file = do 16 | eitherSkey<-try readSkeyFromFile 17 | case eitherSkey of 18 | Left (e::IOError )-> fail "There was error reading skey file" 19 | Right sk -> pure sk 20 | where 21 | readSkeyFromFile=do 22 | exists<-doesFileExist file 23 | if exists then pure () else fail $ file ++ " doesn't exist" 24 | content <-readBs file 25 | parseSignKey content 26 | 27 | readBs:: FilePath -> IO T.Text 28 | readBs = TextIO.readFile 29 | 30 | 31 | -- read the default.skey frole from CARDANO_HOME directory. 32 | -- the file contains the filename of the which is to be read. 33 | getDefaultSignKey :: IO (SigningKey PaymentKey) 34 | getDefaultSignKey= getWorkPath ["default.skey"] >>= readSignKey 35 | 36 | 37 | signTxBody :: TxBody ConwayEra -> [SigningKey PaymentKey] -> Tx ConwayEra 38 | signTxBody txBody skeys= do 39 | makeSignedTransaction (map toWitness skeys) txBody 40 | where 41 | toWitness skey = makeShelleyKeyWitness shelleyBasedEra txBody (WitnessPaymentKey skey) 42 | -------------------------------------------------------------------------------- /test/Main.hs: -------------------------------------------------------------------------------- 1 | import Cardano.Kuber.Api 2 | import Control.Exception (throw) 3 | import Control.Monad.IO.Class 4 | import Debug.Trace as Debug 5 | import System.Directory (getCurrentDirectory) 6 | import Test.ApiTest 7 | import Test.ChainApiTests 8 | import Test.KuberApiTests 9 | import qualified Test.ParserTest as ParserTest 10 | import Test.Tasty 11 | 12 | main :: IO () 13 | main = defaultMain tests 14 | 15 | tests :: TestTree 16 | tests = testGroup "Tests" [chainApiTests, kuberApiTests] 17 | 18 | chainApiTests :: TestTree 19 | chainApiTests = 20 | testGroup 21 | "ChainAPI" 22 | [ testGetNetworkId, 23 | testQueryProtocolParams, 24 | testQuerySystemStart, 25 | testQueryGenesisParams, 26 | testQueryUtxoByAddress, 27 | testQueryUtxoByTxin, 28 | testQueryChainPoint, 29 | testQueryCurrentEra 30 | ] 31 | 32 | kuberApiTests :: TestTree 33 | kuberApiTests = 34 | testGroup 35 | "KuberAPI" 36 | [ testBuildTxSimplePay, 37 | testBuildTxSimpleMint, 38 | testBuildTxSimpleRedeem, 39 | testBuildTxRedeemFromSmartContract, 40 | testBuildTxSupportMetadata, 41 | testBuildTxSupportDatumInAuxData, 42 | testExUnits 43 | ] 44 | -------------------------------------------------------------------------------- /test/Test/ChainApiTests.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# OPTIONS_GHC -Wno-incomplete-patterns #-} 3 | 4 | module Test.ChainApiTests where 5 | 6 | import Cardano.Api 7 | import Cardano.Api.Shelley 8 | import Cardano.Kuber.Api 9 | import Cardano.Kuber.Data.Parsers (parseAddress, parseAddressBech32, parseTxIn) 10 | import Cardano.Kuber.Util (addressInEraToAddressAny) 11 | import Cardano.Slotting.Time 12 | import Data.Functor.Identity (Identity) 13 | import qualified Data.Set as Set 14 | import qualified Data.Text as T 15 | import Debug.Trace as Debug 16 | import Test.TestGen 17 | 18 | test_kGetNetworkId :: (HasChainQueryAPI api) => Kontract api w FrameworkError NetworkId 19 | test_kGetNetworkId = kGetNetworkId 20 | 21 | test_kQueryProtocolParams :: (HasChainQueryAPI api) => Kontract api w FrameworkError (LedgerProtocolParameters BabbageEra) 22 | test_kQueryProtocolParams = kQueryProtocolParams 23 | 24 | test_kQuerySystemStart :: (HasChainQueryAPI api) => Kontract api w FrameworkError SystemStart 25 | test_kQuerySystemStart = kQuerySystemStart 26 | 27 | test_kQueryGenesisParams :: (HasChainQueryAPI api) => Kontract api w FrameworkError (GenesisParameters ShelleyEra) 28 | test_kQueryGenesisParams = kQueryGenesisParams 29 | 30 | test_kQueryUtxoByAddress :: (HasChainQueryAPI api) => Kontract api w FrameworkError (UTxO BabbageEra) 31 | test_kQueryUtxoByAddress = kQueryUtxoByAddress $ Set.singleton $ addressInEraToAddressAny dummyAddressInBabbageEra 32 | 33 | test_kQueryUtxoByTxin :: (HasChainQueryAPI api) => Kontract api w FrameworkError (UTxO BabbageEra) 34 | test_kQueryUtxoByTxin = kQueryUtxoByTxin $ Set.singleton dummyTxIn 35 | 36 | test_kQueryChainPoint :: (HasChainQueryAPI api) => Kontract api w FrameworkError ChainPoint 37 | test_kQueryChainPoint = kQueryChainPoint 38 | 39 | test_kQueryCurrentEra :: (HasChainQueryAPI api) => Kontract api w FrameworkError AnyCardanoEra 40 | test_kQueryCurrentEra = kQueryCurrentEra 41 | -------------------------------------------------------------------------------- /test/Test/KuberApiTests.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE MonoLocalBinds #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | module Test.KuberApiTests where 6 | 7 | import Cardano.Api 8 | import Cardano.Kuber.Api 9 | import Control.Monad.IO.Class 10 | import qualified Data.Aeson as A 11 | import qualified Data.ByteString.Lazy.Char8 as BS8 12 | import qualified Debug.Trace as Debug 13 | import GHC.IO 14 | import Hedgehog.Gen 15 | import PlutusTx.Trace 16 | import System.Directory 17 | import Test.TestGen 18 | 19 | txMaker :: FilePath -> TxBuilder_ BabbageEra 20 | txMaker txJsonFileName = 21 | case A.decode txJson of 22 | Nothing -> error "Invalid Tx" 23 | Just any -> any :: TxBuilder_ BabbageEra 24 | where 25 | filePath :: FilePath 26 | filePath = unsafePerformIO getCurrentDirectory ++ "/test/Test/TransactionJSON/" ++ txJsonFileName ++ ".json" 27 | 28 | txJson :: BS8.ByteString 29 | txJson = unsafePerformIO $ BS8.readFile filePath 30 | 31 | test_kBuildTx_simplePay :: (HasKuberAPI api, IsTxBuilderEra BabbageEra) => Kontract api w FrameworkError (Tx BabbageEra) 32 | test_kBuildTx_simplePay = kBuildTx $ txMaker "simplePay" 33 | 34 | test_kBuildTx_simpleMint :: (HasKuberAPI api, IsTxBuilderEra BabbageEra) => Kontract api w FrameworkError (Tx BabbageEra) 35 | test_kBuildTx_simpleMint = kBuildTx $ txMaker "simpleMint" 36 | 37 | test_kBuildTx_redeemWithReferenceInput :: (HasKuberAPI api, IsTxBuilderEra BabbageEra) => Kontract api w FrameworkError (Tx BabbageEra) 38 | test_kBuildTx_redeemWithReferenceInput = kBuildTx $ txMaker "redeemWithReferenceInput" 39 | 40 | test_kBuildTx_redeemFromSmartContract :: (HasKuberAPI api, IsTxBuilderEra BabbageEra) => Kontract api w FrameworkError (Tx BabbageEra) 41 | test_kBuildTx_redeemFromSmartContract = kBuildTx $ txMaker "redeemFromSmartContract" 42 | 43 | test_kBuildTx_supportMetadata :: (HasKuberAPI api, IsTxBuilderEra BabbageEra) => Kontract api w FrameworkError (Tx BabbageEra) 44 | test_kBuildTx_supportMetadata = kBuildTx $ txMaker "simplePayWithMetadata" 45 | 46 | test_kBuildTx_supportDatumInAuxData :: (HasKuberAPI api, IsTxBuilderEra BabbageEra) => Kontract api w FrameworkError (Tx BabbageEra) 47 | test_kBuildTx_supportDatumInAuxData = kBuildTx $ txMaker "mintWithDatumInAuxData" 48 | 49 | test_ex_units :: (HasKuberAPI api, IsTxBuilderEra BabbageEra) => Kontract api w FrameworkError (Tx BabbageEra) 50 | test_ex_units = kBuildTx $ txMaker "exUnitsMultisig" -------------------------------------------------------------------------------- /test/Test/MarketPlaceWorkflow.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NumericUnderscores #-} 2 | {-# LANGUAGE TypeApplications #-} 3 | module Test.MarketPlaceWorkflow where 4 | import Cardano.Kuber.Api 5 | import Data.Functor ((<&>)) 6 | import Cardano.Kuber.Data.Parsers (parseSignKey) 7 | import System.Environment (getEnv) 8 | import qualified Data.Text as T 9 | import Cardano.Api 10 | import Cardano.Kuber.Util (skeyToAddrInEra) 11 | import qualified Data.ByteString.Char8 as BS8 12 | import Control.Exception (throw) 13 | import qualified Data.Text.IO as T 14 | 15 | main= do 16 | info <- chainInfoFromEnv >>= withDetails 17 | signKey <- getEnv "SIGN_KEY_PATH" >>= T.readFile >>= parseSignKey 18 | 19 | 20 | let script = RequireSignature @SimpleScriptV2 vkey 21 | mintOp = txMintSimpleScript script [(AssetName $ BS8.pack "one", 1)] 22 | <> txWalletSignKey signKey 23 | network= getNetworkId info 24 | vkey = verificationKeyHash $ getVerificationKey signKey 25 | policy= scriptPolicyId $ SimpleScript SimpleScriptV2 script 26 | 27 | tx <- txBuilderToTxIO info mintOp >>= orThrow 28 | submitTx (getConnectInfo info) tx >>= orThrow 29 | putStrLn $ "[TxSubmit][Mint] "++ binaryToString policy ++".one tx : " ++ BS8.unpack ( serialiseToRawBytesHex $ getTxId $ getTxBody tx) 30 | 31 | where 32 | throOnError x = do 33 | v<-x 34 | case v of 35 | Right v -> pure v 36 | Left e -> throw e 37 | orThrow v = do 38 | case v of 39 | Right v -> pure v 40 | Left e -> throw e 41 | binaryToString v = BS8.unpack ( serialiseToRawBytesHex $ v) 42 | 43 | 44 | --waitConfirmation txId 45 | -------------------------------------------------------------------------------- /test/Test/ParserTest.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE TypeApplications #-} 3 | module Test.ParserTest where 4 | 5 | import qualified Data.Text as T 6 | import Cardano.Api 7 | import Cardano.Kuber.Util hiding (toHexString) 8 | import Data.Text.Conversions 9 | import Data.ByteString (ByteString) 10 | import Test.Tasty (TestTree, testGroup, defaultMain) 11 | import Data.Functor ((<&>)) 12 | import Test.Tasty.HUnit (testCase, (@?=)) 13 | import Cardano.Kuber.Data.Parsers 14 | import qualified Debug.Trace as Debug 15 | import qualified Data.ByteString as BS 16 | import Cardano.Kuber.Console.ConsoleWritable (ConsoleWritable(toConsoleText, toConsoleTextNoPrefix)) 17 | import qualified Data.ByteString.Lazy as LBS 18 | import qualified Data.ByteString.Char8 as BS8 19 | import Cardano.Ledger.Shelley.API (ScriptHash(ScriptHash)) 20 | import PlutusLedgerApi.V1 (PubKeyHash(..), fromBuiltin) 21 | 22 | main :: IO () 23 | main = defaultMain tests 24 | 25 | tests :: TestTree 26 | tests = 27 | testGroup "Utils.addrInEraToPkh" [ 28 | baseAddressToPkh 29 | , parseutxoWithOnlyLovelace 30 | , parseUtxoWithAsset 31 | , parseAssetIdTest 32 | 33 | ] 34 | 35 | 36 | tAddressToPkh :: String -> Maybe String 37 | tAddressToPkh address = deserialiseAddress (AsAddressInEra AsConwayEra) (T.pack address) >>= addrInEraToPkh <&> (\(PubKeyHash pkh ) -> toHexString $ fromBuiltin pkh ) 38 | 39 | 40 | baseAddressToPkh :: TestTree 41 | baseAddressToPkh = testCase "should parse full shelley base address" (tAddressToPkh "addr_test1qrmntnd29t3kpnn8uf7d9asr3fzvw7lnah55h52yvaxnfe4g2v2ge520usmkn0zcl46gy38877hej5cnqe6s602xpkyqtpcsrj" 42 | @?= Just "f735cdaa2ae360ce67e27cd2f6038a44c77bf3ede94bd144674d34e6") 43 | 44 | parseutxoWithOnlyLovelace:: TestTree 45 | parseutxoWithOnlyLovelace=testCase "should parse utxo with only lovelace output" $ 46 | (parseUtxo @BabbageEra (T.pack "828258208d3921f63a5d65c337cf1f462a086e39ffef68a0b0a1576a9ead2525903c05420282583901538d169e31bec9d9903f53cc2fce8fffc6d61b30298976bb20f48970f9c9e87246d2f0373885896ad2804b7229673204cac9208345c1ea5b1a02addd3a") 47 | <&> toConsoleTextNoPrefix ) @?= Just "8d3921f63a5d65c337cf1f462a086e39ffef68a0b0a1576a9ead2525903c0542#2 : 44.948794 Ada" 48 | 49 | parseUtxoWithAsset :: TestTree 50 | parseUtxoWithAsset = testCase "should parse utxo with asset" $ 51 | (parseUtxo @BabbageEra (T.pack "828258208d3921f63a5d65c337cf1f462a086e39ffef68a0b0a1576a9ead2525903c05420682583901538d169e31bec9d9903f53cc2fce8fffc6d61b30298976bb20f48970f9c9e87246d2f0373885896ad2804b7229673204cac9208345c1ea5b821a002c3268a1581c4b36a781645ef8eea2a75687edc16b2d0aa4be3016eeed04f59d3d36a14c466c6f77657279566964656f01") 52 | <&> toConsoleTextNoPrefix ) @?= Just "8d3921f63a5d65c337cf1f462a086e39ffef68a0b0a1576a9ead2525903c0542#6 : 2.896488 Ada +1 4b36a781645ef8eea2a75687edc16b2d0aa4be3016eeed04f59d3d36.FloweryVideo" 53 | 54 | parseAssetIdTest :: TestTree 55 | parseAssetIdTest = testCase " should parse with dot" (parseAssetId (T.pack " 4b36a781645ef8eea2a75687edc16b2d0aa4be3016eeed04f59d3d36.Flowery. Video\n ") 56 | @?= Just (AssetId ( forceRight $ deserialiseFromRawBytesHex AsPolicyId $ BS8.pack "4b36a781645ef8eea2a75687edc16b2d0aa4be3016eeed04f59d3d36") (AssetName $ BS8.pack "Flowery. Video" )) 57 | ) 58 | 59 | parseAssetIdHex :: TestTree 60 | parseAssetIdHex = testCase " should parse assetId Hex" (parseAssetId (T.pack " 4b36a781645ef8eea2a75687edc16b2d0aa4be3016eeed04f59d3d3604f59d3d36\n ") 61 | @?= Just (AssetId ( forceRight $ deserialiseFromRawBytesHex AsPolicyId $ BS8.pack "4b36a781645ef8eea2a75687edc16b2d0aa4be3016eeed04f59d3d36") ( forceRight $ deserialiseFromRawBytesHex AsAssetName $ BS8.pack "04f59d3d36" )) 62 | ) 63 | 64 | 65 | toHexString :: (FromText a1, ToText (Base16 a2)) => a2 -> a1 66 | toHexString bs = fromText $ toText (Base16 bs ) 67 | 68 | 69 | unHexLazy :: ToText a => a -> Maybe LBS.ByteString 70 | unHexLazy v = convertText (toText v) <&> unBase16 71 | 72 | unHexStrict :: ToText a => a -> Maybe ByteString 73 | unHexStrict v = convertText (toText v) <&> unBase16 74 | 75 | unHex :: (Functor f, FromText (f (Base16 b)), ToText a) => a -> f b 76 | unHex v = convertText (toText v) <&> unBase16 77 | 78 | forceJust (Just v) =v 79 | forceRight(Right r) = r 80 | -------------------------------------------------------------------------------- /test/Test/TestGen.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NumericUnderscores #-} 2 | {-# OPTIONS_GHC -Wno-incomplete-patterns #-} 3 | 4 | module Test.TestGen where 5 | 6 | import Cardano.Api 7 | import Cardano.Api.Shelley 8 | import Data.Functor 9 | import Data.Functor.Identity (Identity) 10 | import qualified Data.Map as Map 11 | import Data.Set (Set) 12 | import qualified Data.Set as Set 13 | import qualified Data.Text as T 14 | import qualified Debug.Trace as Debug 15 | import Hedgehog (Gen) 16 | import Hedgehog.Gen (choice, element) 17 | import qualified Hedgehog.Gen as Gen 18 | import Hedgehog.Internal.Gen (GenT) 19 | import qualified Hedgehog.Range as Range 20 | import Test.Gen.Cardano.Api.Typed 21 | import Cardano.Kuber.Api 22 | import Cardano.Kuber.Data.Parsers 23 | import Cardano.Api.Ledger (Coin(..)) 24 | 25 | genSomeWalelt :: NetworkId -> Gen TxBuilder 26 | genSomeWalelt netid = do 27 | vkeys <- Gen.list (Range.linear 2 5) (genVerificationKey AsPaymentKey) 28 | userStake <- genStakeAddressReference 29 | 30 | let addresses = map (\vkey -> makeShelleyAddressInEra ShelleyBasedEraConway netid (PaymentCredentialByKey $ verificationKeyHash vkey) userStake) vkeys 31 | genutxo genVal = do 32 | value <- genVal 33 | txid <- genTxIn 34 | address <- element addresses 35 | pure (txid, TxOut address (TxOutValueShelleyBased ShelleyBasedEraConway value) TxOutDatumNone ReferenceScriptNone) 36 | genAdaUtxo genVal = do 37 | value <- genVal 38 | txid <- genTxIn 39 | address <- element addresses 40 | pure (txid, TxOut address (TxOutValueByron value) TxOutDatumNone ReferenceScriptNone) 41 | genAdaVal = do 42 | Gen.integral (Range.linear 2_000_000 3_000_000_000_000) <&> Coin 43 | genCollateralVal = do 44 | Gen.integral (Range.linear 5_000_000 10_000_000) <&> Coin 45 | utxos <- Gen.list (Range.linear 4 10) (genutxo (genValueForTxOut ShelleyBasedEraConway)) 46 | adaUtxos <- Gen.list (Range.linear 4 10) (genAdaUtxo genAdaVal) 47 | collateralUtxo <- genAdaUtxo genCollateralVal 48 | pure $ txWalletUtxos (UTxO . Map.fromList $ collateralUtxo : utxos <> adaUtxos) 49 | 50 | chainApiNetworkIdTest :: (HasChainQueryAPI api) => Kontract api w FrameworkError NetworkId 51 | chainApiNetworkIdTest = kGetNetworkId 52 | 53 | genPubkeyAddressShelley :: Gen (Address ShelleyAddr) 54 | genPubkeyAddressShelley = do 55 | vKeyPayment <- genVerificationKey AsPaymentKey 56 | makeShelleyAddress 57 | Mainnet 58 | (PaymentCredentialByKey $ verificationKeyHash vKeyPayment) 59 | <$> genStakeAddressReference 60 | 61 | genAddressSet :: GenT Identity (Set AddressAny) 62 | genAddressSet = do 63 | shellyAddress <- genPubkeyAddressShelley 64 | let sets = Set.fromList [toAddressAny shellyAddress] 65 | return sets 66 | 67 | dummyAddressInBabbageEra :: AddressInEra BabbageEra 68 | dummyAddressInBabbageEra = case parseAddress (T.pack "addr_test1qpw57k84mzwmpqyx6n9smye79mxt8rqpfpvx7p6chz95sm7a3aw5tgv4fc9weuwme2u29aerv5hk0m2lkpkgasn7gtxqwen0r7") of 69 | Just a -> a 70 | 71 | dummyAddressInConwayEra = do 72 | shellyAddr <- genPubkeyAddressShelley 73 | let res = anyAddressInEra ConwayEra (toAddressAny shellyAddr) 74 | case res of 75 | Right aie -> pure aie 76 | 77 | dummyTxIn :: TxIn 78 | dummyTxIn = case parseTxIn (T.pack "2bc637151310fa30a08d2cf8756b9bf7cef4e378243ee2018a15fd5811d314c7#1") of 79 | Just a -> a -------------------------------------------------------------------------------- /test/Test/TransactionJSON/redeemWithReferenceInput.json: -------------------------------------------------------------------------------- 1 | { 2 | "outputs": [ 3 | { 4 | "address": "addr_test1qr0k5uczwvzdt6g6c2e9l92r24f528dfnmu9qq6lssrc3z56l3lczn30u56k4vlf948etel5d63zj20yg6wymu2gp4gsyum3sz", 5 | "value": "5A" 6 | } 7 | ], 8 | "inputs": [ 9 | { 10 | "utxo": { 11 | "txin": "64fe486447aba2136f665fb5f0d955da260be4021845de1a0aa8bf3676b51720#0", 12 | "address": "addr_test1wzeajezel78ywj8ltx3scx66edw92c8mxqrphg46y0zdckcqr42tx", 13 | "datum": { 14 | "fields": [], 15 | "constructor": 0 16 | }, 17 | "value": { 18 | "lovelace": 10000000 19 | } 20 | }, 21 | "script": { 22 | "type": "PlutusScriptV2", 23 | "description": "", 24 | "cborHex": "5908d15908ce01000033232323233223232323232323232323322323232323232323222223232533532325335533533355300a120013233500c22333500322002002001350012200112330012253350021020100101d2001500115335333355300a1200150092201e500101d101c101d101c101d133573892117496e76616c6964205265666572656e636520496e7075740001c13333553009120015008223535002220012222335016333573466e1cd403488c8c8c00401cc8004d540a488cd400520002235002225335333573466e3c0080240ac0a84c01c0044c01800cc8004d540a088cd400520002235002225335333573466e3c00801c0a80a440044c01800d200202202100535350012200222222222222200b50113333573466e1cd55cea80124000466442466002006004646464646464646464646464646666ae68cdc39aab9d500c480008cccccccccccc88888888888848cccccccccccc00403403002c02802402001c01801401000c008cd4064068d5d0a80619a80c80d1aba1500b33501901b35742a014666aa03aeb94070d5d0a804999aa80ebae501c35742a01066a0320486ae85401cccd54074095d69aba150063232323333573466e1cd55cea801240004664424660020060046464646666ae68cdc39aab9d5002480008cc8848cc00400c008cd40bdd69aba150023030357426ae8940088c98c80d0cd5ce01a81a01909aab9e5001137540026ae854008c8c8c8cccd5cd19b8735573aa004900011991091980080180119a817bad35742a00460606ae84d5d1280111931901a19ab9c035034032135573ca00226ea8004d5d09aba2500223263203033573806206005c26aae7940044dd50009aba1500533501975c6ae854010ccd540740848004d5d0a801999aa80ebae200135742a00460466ae84d5d1280111931901619ab9c02d02c02a135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d55cf280089baa00135742a00460266ae84d5d1280111931900f19ab9c01f01e01c101d13263201d3357389201035054350001d135573ca00226ea80044cd4008888c00cc0080048004c8004d5405c88448894cd40044d400c88004884ccd401488008c010008ccd54c01c4800401401000448848cc00400c00848c88c008dd6000990009aa80b111999aab9f0012500a233500930043574200460066ae880080588c8c8cccd5cd19b8735573aa004900011991091980080180118061aba150023005357426ae8940088c98c8058cd5ce00b80b00a09aab9e5001137540024646464646666ae68cdc39aab9d5004480008cccc888848cccc00401401000c008c8c8c8cccd5cd19b8735573aa0049000119910919800801801180a9aba1500233500f014357426ae8940088c98c806ccd5ce00e00d80c89aab9e5001137540026ae854010ccd54021d728039aba150033232323333573466e1d4005200423212223002004357426aae79400c8cccd5cd19b875002480088c84888c004010dd71aba135573ca00846666ae68cdc3a801a400042444006464c6403a66ae7007807406c0680644d55cea80089baa00135742a00466a016eb8d5d09aba2500223263201733573803002e02a26ae8940044d5d1280089aab9e500113754002266aa002eb9d6889119118011bab00132001355013223233335573e0044a010466a00e66442466002006004600c6aae754008c014d55cf280118021aba200301413574200222440042442446600200800624464646666ae68cdc3a800a40004642446004006600a6ae84d55cf280191999ab9a3370ea0049001109100091931900919ab9c01301201000f135573aa00226ea80048c8c8cccd5cd19b875001480188c848888c010014c01cd5d09aab9e500323333573466e1d400920042321222230020053009357426aae7940108cccd5cd19b875003480088c848888c004014c01cd5d09aab9e500523333573466e1d40112000232122223003005375c6ae84d55cf280311931900919ab9c01301201000f00e00d135573aa00226ea80048c8c8cccd5cd19b8735573aa004900011991091980080180118029aba15002375a6ae84d5d1280111931900719ab9c00f00e00c135573ca00226ea80048c8cccd5cd19b8735573aa002900011bae357426aae7940088c98c8030cd5ce00680600509baa001232323232323333573466e1d4005200c21222222200323333573466e1d4009200a21222222200423333573466e1d400d2008233221222222233001009008375c6ae854014dd69aba135744a00a46666ae68cdc3a8022400c4664424444444660040120106eb8d5d0a8039bae357426ae89401c8cccd5cd19b875005480108cc8848888888cc018024020c030d5d0a8049bae357426ae8940248cccd5cd19b875006480088c848888888c01c020c034d5d09aab9e500b23333573466e1d401d2000232122222223005008300e357426aae7940308c98c8054cd5ce00b00a80980900880800780700689aab9d5004135573ca00626aae7940084d55cf280089baa0012323232323333573466e1d400520022333222122333001005004003375a6ae854010dd69aba15003375a6ae84d5d1280191999ab9a3370ea0049000119091180100198041aba135573ca00c464c6401c66ae7003c03803002c4d55cea80189aba25001135573ca00226ea80048c8c8cccd5cd19b875001480088c8488c00400cdd71aba135573ca00646666ae68cdc3a8012400046424460040066eb8d5d09aab9e500423263200b33573801801601201026aae7540044dd500089119191999ab9a3370ea00290021091100091999ab9a3370ea00490011190911180180218031aba135573ca00846666ae68cdc3a801a400042444004464c6401866ae700340300280240204d55cea80089baa0012323333573466e1d40052002200523333573466e1d40092000200523263200833573801201000c00a26aae74dd5000891001091000a4c24002921035054310011232300100122330033002002001335122335500248811cf9a615f811b6cf77f2b7a77025ee76e2c82708f0c06226a77242708c00488105544f4b454e00112212330010030021120011" 25 | }, 26 | "redeemer": { 27 | "fields": [], 28 | "constructor": 0 29 | } 30 | }, 31 | { 32 | "utxo": { 33 | "address": "addr_test1wqag3rt979nep9g2wtdwu8mr4gz6m4kjdpp5zp705km8wys6t2kla", 34 | "txin": "022b440450e28c33a785e7521edd13c8aa51fa8e0708fb0aff946d646712c9a7#0", 35 | "value": { 36 | "lovelace": 29167525003 37 | } 38 | } 39 | } 40 | ], 41 | "changeAddress": "addr_test1qr0k5uczwvzdt6g6c2e9l92r24f528dfnmu9qq6lssrc3z56l3lczn30u56k4vlf948etel5d63zj20yg6wymu2gp4gsyum3sz", 42 | "collaterals": { 43 | "address": "addr_test1qr0k5uczwvzdt6g6c2e9l92r24f528dfnmu9qq6lssrc3z56l3lczn30u56k4vlf948etel5d63zj20yg6wymu2gp4gsyum3sz", 44 | "txin": "5f40b522ea06816aae59003a995bdf7dede505a1dae9e9b0c9b3d19b80c1aeba#0", 45 | "value": { 46 | "lovelace": 5000000 47 | } 48 | }, 49 | "referenceInputs": [ 50 | { 51 | "address": "addr_test1qr0k5uczwvzdt6g6c2e9l92r24f528dfnmu9qq6lssrc3z56l3lczn30u56k4vlf948etel5d63zj20yg6wymu2gp4gsyum3sz", 52 | "txin": "a43291bc806c65e71ae5f186f34b14c3252cf4a900df4eb8f811eb8a8c8dac25#0", 53 | "value": { 54 | "lovelace": 1150770, 55 | "f9a615f811b6cf77f2b7a77025ee76e2c82708f0c06226a77242708c": { 56 | "544f4b454e": 1 57 | } 58 | } 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /test/Test/TransactionJSON/simpleMint.json: -------------------------------------------------------------------------------- 1 | { 2 | "outputs": [ 3 | { 4 | "address": "addr_test1qpw57k84mzwmpqyx6n9smye79mxt8rqpfpvx7p6chz95sm7a3aw5tgv4fc9weuwme2u29aerv5hk0m2lkpkgasn7gtxqwen0r7", 5 | "value": "10 34e6054e3e74ecf4a9b6eb4e34a865f6b0b5fee85e6a9bfba0d2d025.TestingAsset + 10A", 6 | "datum": { 7 | "constructor": 0, 8 | "fields": [] 9 | } 10 | } 11 | ], 12 | "mint": [ 13 | { 14 | "script": { 15 | "type": "sig", 16 | "keyHash": "5d4f58f5d89db08086d4cb0d933e2eccb38c0148586f0758b88b486f" 17 | }, 18 | "amount": { 19 | "TestingAsset": 10 20 | } 21 | } 22 | ], 23 | "inputs": [ 24 | { 25 | "utxo": { 26 | "address": "addr_test1qpw57k84mzwmpqyx6n9smye79mxt8rqpfpvx7p6chz95sm7a3aw5tgv4fc9weuwme2u29aerv5hk0m2lkpkgasn7gtxqwen0r7", 27 | "txin": "289d78cc6ac3000748060b3c0d48d8b900b05b11c0eba3352d9fe47109584448#5", 28 | "value": { 29 | "lovelace": 29167525003 30 | } 31 | } 32 | } 33 | ], 34 | "selections": [ 35 | { 36 | "address": "addr_test1qpw57k84mzwmpqyx6n9smye79mxt8rqpfpvx7p6chz95sm7a3aw5tgv4fc9weuwme2u29aerv5hk0m2lkpkgasn7gtxqwen0r7", 37 | "txin": "289d78cc6ac3000748060b3c0d48d8b900b05b11c0eba3352d9fe47109584448#5", 38 | "value": { 39 | "lovelace": 29167525003 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /test/Test/TransactionJSON/simplePay.json: -------------------------------------------------------------------------------- 1 | { 2 | "outputs": [ 3 | { 4 | "address": "addr_test1qqtq0qdpkfj5wp7jd7p33ccra9tmsfc8jtej62msts2hxyfn22mqclj4572cnmgtg4jf9h799jlg4dahjhenthyjjp3srjlxp4", 5 | "value": { 6 | "lovelace": 1000000 7 | } 8 | } 9 | ], 10 | "selections": [ 11 | { 12 | "address": "addr_test1vp4zezdmyh8navsyfzmak2fge45gq5qr9yaqk2tpxgztuugmp89sa", 13 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#13121", 14 | "value": { 15 | "lovelace": 503976174260 16 | } 17 | }, 18 | { 19 | "address": "addr_test1vrjpe0zzyyxmp644umze27a6jmxqdzawl88xyz8d2w8dr2spwsep4", 20 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#18890", 21 | "value": { 22 | "lovelace": 570499140107 23 | } 24 | }, 25 | { 26 | "address": "addr_test1vp4zezdmyh8navsyfzmak2fge45gq5qr9yaqk2tpxgztuugmp89sa", 27 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#21801", 28 | "value": { 29 | "lovelace": 6127900 30 | } 31 | }, 32 | { 33 | "address": "addr_test1vp4zezdmyh8navsyfzmak2fge45gq5qr9yaqk2tpxgztuugmp89sa", 34 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#28694", 35 | "value": { 36 | "lovelace": 3 37 | } 38 | }, 39 | { 40 | "address": "addr_test1vp4zezdmyh8navsyfzmak2fge45gq5qr9yaqk2tpxgztuugmp89sa", 41 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#33371", 42 | "value": { 43 | "lovelace": 196268299603 44 | } 45 | }, 46 | { 47 | "address": "addr_test1vrjpe0zzyyxmp644umze27a6jmxqdzawl88xyz8d2w8dr2spwsep4", 48 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#40799", 49 | "value": { 50 | "lovelace": 682055819268 51 | } 52 | }, 53 | { 54 | "address": "addr_test1vrjpe0zzyyxmp644umze27a6jmxqdzawl88xyz8d2w8dr2spwsep4", 55 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#41216", 56 | "value": { 57 | "a0000000000000000000000000000000000000000000000000000000": { 58 | "61": 1 59 | }, 60 | "lovelace": 2 61 | } 62 | }, 63 | { 64 | "address": "addr_test1vrjpe0zzyyxmp644umze27a6jmxqdzawl88xyz8d2w8dr2spwsep4", 65 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#41520", 66 | "value": { 67 | "b0000000000000000000000000000000000000000000000000000000": { 68 | "62": 2 69 | }, 70 | "b556c554568c1929f4390f5311808e93af1debe5d406cb5e6f0541de": { 71 | "61": 1 72 | }, 73 | "lovelace": 4 74 | } 75 | }, 76 | { 77 | "address": "addr_test1vrjpe0zzyyxmp644umze27a6jmxqdzawl88xyz8d2w8dr2spwsep4", 78 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#46983", 79 | "value": { 80 | "lovelace": 237735667683 81 | } 82 | }, 83 | { 84 | "address": "addr_test1vp4zezdmyh8navsyfzmak2fge45gq5qr9yaqk2tpxgztuugmp89sa", 85 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#53436", 86 | "value": { 87 | "a0000000000000000000000000000000000000000000000000000000": { 88 | "61": 2 89 | }, 90 | "lovelace": 3 91 | } 92 | }, 93 | { 94 | "address": "addr_test1vrjpe0zzyyxmp644umze27a6jmxqdzawl88xyz8d2w8dr2spwsep4", 95 | "txin": "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53#60873", 96 | "value": { 97 | "lovelace": 377327595188 98 | } 99 | } 100 | ] 101 | } -------------------------------------------------------------------------------- /test/Test/TransactionJSON/simplePayWithMetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "outputs": [ 3 | { 4 | "address": "addr_test1qr0k5uczwvzdt6g6c2e9l92r24f528dfnmu9qq6lssrc3z56l3lczn30u56k4vlf948etel5d63zj20yg6wymu2gp4gsyum3sz", 5 | "value": "5A" 6 | } 7 | ], 8 | "inputs": [ 9 | { 10 | "utxo": { 11 | "address": "addr_test1wqag3rt979nep9g2wtdwu8mr4gz6m4kjdpp5zp705km8wys6t2kla", 12 | "txin": "022b440450e28c33a785e7521edd13c8aa51fa8e0708fb0aff946d646712c9a7#0", 13 | "value": { 14 | "lovelace": 29167525003 15 | } 16 | } 17 | } 18 | ], 19 | "changeAddress": "addr_test1qr0k5uczwvzdt6g6c2e9l92r24f528dfnmu9qq6lssrc3z56l3lczn30u56k4vlf948etel5d63zj20yg6wymu2gp4gsyum3sz", 20 | "collaterals": { 21 | "address": "addr_test1qr0k5uczwvzdt6g6c2e9l92r24f528dfnmu9qq6lssrc3z56l3lczn30u56k4vlf948etel5d63zj20yg6wymu2gp4gsyum3sz", 22 | "txin": "5f40b522ea06816aae59003a995bdf7dede505a1dae9e9b0c9b3d19b80c1aeba#0", 23 | "value": { 24 | "lovelace": 5000000 25 | } 26 | }, 27 | "selections": [ 28 | { 29 | "address": "addr_test1qpw57k84mzwmpqyx6n9smye79mxt8rqpfpvx7p6chz95sm7a3aw5tgv4fc9weuwme2u29aerv5hk0m2lkpkgasn7gtxqwen0r7", 30 | "txin": "289d78cc6ac3000748060b3c0d48d8b900b05b11c0eba3352d9fe47109584448#5", 31 | "value": { 32 | "lovelace": 29167525003 33 | } 34 | } 35 | ], 36 | "metadata": { 37 | "0": { 38 | "library": "Kuber", 39 | "version": "2.3.2" 40 | }, 41 | "1": "Test example for metadata" 42 | } 43 | } 44 | --------------------------------------------------------------------------------