Initial commit (wip)
This commit is contained in:
commit
5d54d6e5ac
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.attr-cache
|
||||||
|
.cabal-sandbox
|
||||||
|
*.hi
|
||||||
|
*.o
|
||||||
|
cabal.project.local
|
||||||
|
cabal.sandbox.config
|
||||||
|
ctags
|
||||||
|
dist-newstyle/
|
||||||
|
dist/
|
||||||
|
ghcid-output.txt
|
||||||
|
profile/
|
||||||
|
result
|
||||||
|
result-*
|
||||||
|
tags
|
||||||
|
TAGS
|
||||||
|
static.out
|
||||||
|
/.env
|
2
.obelisk/impl/default.nix
Normal file
2
.obelisk/impl/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
.obelisk/impl/github.json
Normal file
8
.obelisk/impl/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "obelisk",
|
||||||
|
"branch": "develop",
|
||||||
|
"private": false,
|
||||||
|
"rev": "56f2ea5ec06d59e08e6111c374dd0142200860ee",
|
||||||
|
"sha256": "1x07av9ayiaj3b3qknk3rd7k6gv19s27a4iz0wmi75xdg788r6x0"
|
||||||
|
}
|
12
.obelisk/impl/thunk.nix
Normal file
12
.obelisk/impl/thunk.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import (builtins.fetchTarball {
|
||||||
|
url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz";
|
||||||
|
sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr";
|
||||||
|
}) {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
74
backend/backend.cabal
Normal file
74
backend/backend.cabal
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
name: backend
|
||||||
|
version: 0.1
|
||||||
|
cabal-version: >= 1.10
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
library
|
||||||
|
hs-source-dirs: src
|
||||||
|
if impl(ghcjs)
|
||||||
|
buildable: False
|
||||||
|
build-depends: base
|
||||||
|
, common
|
||||||
|
, frontend
|
||||||
|
, obelisk-backend
|
||||||
|
, obelisk-route
|
||||||
|
, process
|
||||||
|
, async
|
||||||
|
, which
|
||||||
|
, monad-logger
|
||||||
|
, logging-effect
|
||||||
|
, prettyprinter
|
||||||
|
, string-interpolate
|
||||||
|
, containers
|
||||||
|
, text
|
||||||
|
, witherable
|
||||||
|
, snap-core
|
||||||
|
, websockets-snap
|
||||||
|
, websockets
|
||||||
|
, some
|
||||||
|
, directory
|
||||||
|
, reflex-gadt-api
|
||||||
|
, aeson
|
||||||
|
, bytestring
|
||||||
|
, base64-bytestring
|
||||||
|
, case-insensitive
|
||||||
|
, lens
|
||||||
|
, uuid
|
||||||
|
, http-client
|
||||||
|
, http-conduit
|
||||||
|
, temporary
|
||||||
|
, stm
|
||||||
|
, monad-loops
|
||||||
|
, mtl
|
||||||
|
, transformers
|
||||||
|
, time
|
||||||
|
, time-compat
|
||||||
|
, transformers
|
||||||
|
, obelisk-executable-config-lookup
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
OverloadedStrings
|
||||||
|
LambdaCase
|
||||||
|
GADTs
|
||||||
|
ScopedTypeVariables
|
||||||
|
OverloadedStrings
|
||||||
|
FlexibleContexts
|
||||||
|
QuasiQuotes
|
||||||
|
DeriveGeneric
|
||||||
|
|
||||||
|
exposed-modules:
|
||||||
|
Backend
|
||||||
|
|
||||||
|
ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
|
||||||
|
|
||||||
|
executable backend
|
||||||
|
main-is: main.hs
|
||||||
|
hs-source-dirs: src-bin
|
||||||
|
ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -threaded -fno-show-valid-hole-fits
|
||||||
|
if impl(ghcjs)
|
||||||
|
buildable: False
|
||||||
|
build-depends: base
|
||||||
|
, backend
|
||||||
|
, common
|
||||||
|
, frontend
|
||||||
|
, obelisk-backend
|
6
backend/src-bin/main.hs
Normal file
6
backend/src-bin/main.hs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Backend
|
||||||
|
import Frontend
|
||||||
|
import Obelisk.Backend
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = runBackend backend frontend
|
20
backend/src/Backend.hs
Normal file
20
backend/src/Backend.hs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE PartialTypeSignatures #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
|
||||||
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
||||||
|
|
||||||
|
module Backend
|
||||||
|
|
||||||
|
(backend)
|
||||||
|
|
||||||
|
where
|
||||||
|
|
||||||
|
import Obelisk.Backend
|
||||||
|
|
||||||
|
backend :: Backend BackendRoute FrontendRoute
|
||||||
|
backend = Backend
|
||||||
|
{ _backend_run = \serve -> do
|
||||||
|
pure ()
|
||||||
|
, _backend_routeEncoder = fullRouteEncoder
|
||||||
|
}
|
1
backend/static
Symbolic link
1
backend/static
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../static
|
3
cabal.project
Normal file
3
cabal.project
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
optional-packages:
|
||||||
|
*
|
||||||
|
write-ghc-environment-files: never
|
39
common/common.cabal
Normal file
39
common/common.cabal
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
name: common
|
||||||
|
version: 0.1
|
||||||
|
cabal-version: >= 1.10
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
library
|
||||||
|
hs-source-dirs: src
|
||||||
|
build-depends: base
|
||||||
|
, obelisk-route
|
||||||
|
, mtl
|
||||||
|
, text
|
||||||
|
, reflex-gadt-api
|
||||||
|
, aeson
|
||||||
|
, aeson-gadt-th
|
||||||
|
, constraints-extras
|
||||||
|
, containers
|
||||||
|
, time
|
||||||
|
, lens
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
DeriveGeneric
|
||||||
|
FlexibleContexts
|
||||||
|
FlexibleInstances
|
||||||
|
GADTs
|
||||||
|
LambdaCase
|
||||||
|
MultiParamTypeClasses
|
||||||
|
OverloadedStrings
|
||||||
|
QuantifiedConstraints
|
||||||
|
RecursiveDo
|
||||||
|
ScopedTypeVariables
|
||||||
|
TypeFamilies
|
||||||
|
UndecidableInstances
|
||||||
|
|
||||||
|
exposed-modules:
|
||||||
|
Common.Route
|
||||||
|
Common.Helpers
|
||||||
|
Hydra.Types
|
||||||
|
|
||||||
|
ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
|
10
common/src/Common/Helpers.hs
Normal file
10
common/src/Common/Helpers.hs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- |
|
||||||
|
|
||||||
|
module Common.Helpers where
|
||||||
|
|
||||||
|
headMay :: [a] -> Maybe a
|
||||||
|
headMay (a:_) = Just a
|
||||||
|
headMay _ = Nothing
|
||||||
|
|
||||||
|
seconds :: Int -> Int
|
||||||
|
seconds = (* 1000000)
|
50
common/src/Common/Route.hs
Normal file
50
common/src/Common/Route.hs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{-# LANGUAGE EmptyCase #-}
|
||||||
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
module Common.Route where
|
||||||
|
|
||||||
|
import Prelude hiding (id, (.))
|
||||||
|
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Functor.Identity
|
||||||
|
|
||||||
|
import Obelisk.Route
|
||||||
|
import Obelisk.Route.TH
|
||||||
|
|
||||||
|
|
||||||
|
data BackendRoute :: * -> * where
|
||||||
|
-- | Used to handle unparseable routes.
|
||||||
|
BackendRoute_Missing :: BackendRoute ()
|
||||||
|
BackendRoute_Api :: BackendRoute ()
|
||||||
|
-- You can define any routes that will be handled specially by the backend here.
|
||||||
|
-- i.e. These do not serve the frontend, but do something different, such as serving static files.
|
||||||
|
|
||||||
|
data FrontendRoute :: * -> * where
|
||||||
|
FrontendRoute_Home :: FrontendRoute ()
|
||||||
|
-- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend.
|
||||||
|
|
||||||
|
fullRouteEncoder
|
||||||
|
:: Encoder (Either Text) Identity (R (FullRoute BackendRoute FrontendRoute)) PageName
|
||||||
|
fullRouteEncoder = mkFullRouteEncoder
|
||||||
|
(FullRoute_Backend BackendRoute_Missing :/ ())
|
||||||
|
(\case
|
||||||
|
BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty
|
||||||
|
BackendRoute_Api -> PathSegment "api" $ unitEncoder mempty
|
||||||
|
)
|
||||||
|
(\case
|
||||||
|
FrontendRoute_Home -> PathEnd $ unitEncoder mempty
|
||||||
|
)
|
||||||
|
|
||||||
|
concat <$> mapM deriveRouteComponent
|
||||||
|
[ ''BackendRoute
|
||||||
|
, ''FrontendRoute
|
||||||
|
]
|
81
common/src/Hydra/Types.hs
Normal file
81
common/src/Hydra/Types.hs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
-- |
|
||||||
|
|
||||||
|
module Hydra.Types where
|
||||||
|
|
||||||
|
import GHC.Generics
|
||||||
|
import Data.Aeson
|
||||||
|
|
||||||
|
import Data.Map (Map)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import Numeric.Natural (Natural)
|
||||||
|
|
||||||
|
-- | Cardano address
|
||||||
|
type Address = T.Text
|
||||||
|
|
||||||
|
type Lovelace = Integer
|
||||||
|
|
||||||
|
type NodeId = T.Text
|
||||||
|
|
||||||
|
type TxIn = T.Text
|
||||||
|
type WholeUTXO = Map TxIn TxInInfo
|
||||||
|
|
||||||
|
ada :: Num a => a -> a
|
||||||
|
ada = (* 1000000)
|
||||||
|
|
||||||
|
lovelaceToAda :: Integer -> Float
|
||||||
|
lovelaceToAda n = fromIntegral n / 1000000
|
||||||
|
|
||||||
|
filterOutFuel :: WholeUTXO -> WholeUTXO
|
||||||
|
filterOutFuel = Map.filter (not . isFuel)
|
||||||
|
|
||||||
|
filterFuel :: WholeUTXO -> WholeUTXO
|
||||||
|
filterFuel = Map.filter (isFuel)
|
||||||
|
|
||||||
|
isFuel :: TxInInfo -> Bool
|
||||||
|
isFuel txinfo = datumhash txinfo == Just fuelMarkerDatumHash
|
||||||
|
|
||||||
|
data TxInInfo = TxInInfo
|
||||||
|
{ address :: Address
|
||||||
|
, datumhash :: Maybe T.Text
|
||||||
|
, value :: Map T.Text Int
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance FromJSON TxInInfo
|
||||||
|
instance ToJSON TxInInfo
|
||||||
|
|
||||||
|
fuelMarkerDatumHash :: T.Text
|
||||||
|
fuelMarkerDatumHash = "a654fb60d21c1fed48db2c320aa6df9737ec0204c0ba53b9b94a09fb40e757f3"
|
||||||
|
|
||||||
|
|
||||||
|
-- REVIEW(SN): This is also used in hydra-tui
|
||||||
|
|
||||||
|
-- Below various types copied/adapted from hydra-poc code
|
||||||
|
|
||||||
|
data Host = Host
|
||||||
|
{ hostname :: Text
|
||||||
|
, port :: PortNumber
|
||||||
|
}
|
||||||
|
deriving (Ord, Generic, Eq, Show)
|
||||||
|
|
||||||
|
instance ToJSON Host
|
||||||
|
instance FromJSON Host
|
||||||
|
|
||||||
|
|
||||||
|
newtype Party = Party {vkey :: T.Text}
|
||||||
|
deriving (Eq, Show, Read, Generic)
|
||||||
|
|
||||||
|
instance ToJSON Party
|
||||||
|
instance FromJSON Party
|
||||||
|
|
||||||
|
type UTxOType tx = Value
|
||||||
|
type Snapshot tx = Value
|
||||||
|
type MultiSignature x = Value
|
||||||
|
type PostChainTx tx = Value
|
||||||
|
type PostTxError tx = Value
|
||||||
|
type ValidationError = Value
|
||||||
|
type SnapshotNumber = Natural
|
||||||
|
type PortNumber = Natural
|
||||||
|
type ContestationPeriod = Natural
|
1
config/common/route
Normal file
1
config/common/route
Normal file
@ -0,0 +1 @@
|
|||||||
|
http://localhost:8002
|
9
config/readme.md
Normal file
9
config/readme.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
### Config
|
||||||
|
|
||||||
|
Obelisk projects should contain a config folder with the following subfolders: common, frontend, and backend.
|
||||||
|
|
||||||
|
Things that should never be transmitted to the frontend belong in backend/ (e.g., email credentials)
|
||||||
|
|
||||||
|
Frontend-only configuration belongs in frontend/.
|
||||||
|
|
||||||
|
Shared configuration files (e.g., the route config) belong in common/
|
55
default.nix
Normal file
55
default.nix
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{ system ? builtins.currentSystem
|
||||||
|
# , rpSetup ? import ((import ./.obelisk/impl {}).path + "/dep/reflex-platform") {}
|
||||||
|
# , android-build ? false
|
||||||
|
, obelisk ? import ./.obelisk/impl {
|
||||||
|
inherit system;
|
||||||
|
iosSdkVersion = "13.2";
|
||||||
|
|
||||||
|
# You must accept the Android Software Development Kit License Agreement at
|
||||||
|
# https://developer.android.com/studio/terms in order to build Android apps.
|
||||||
|
# Uncomment and set this to `true` to indicate your acceptance:
|
||||||
|
config.android_sdk.accept_license = true;
|
||||||
|
|
||||||
|
# In order to use Let's Encrypt for HTTPS deployments you must accept
|
||||||
|
# their terms of service at https://letsencrypt.org/repository/.
|
||||||
|
# Uncomment and set this to `true` to indicate your acceptance:
|
||||||
|
terms.security.acme.acceptTerms = true;
|
||||||
|
|
||||||
|
# Override reflex-platform-func in order to get access to haskellOverlaysPre
|
||||||
|
# for adding mobile support.
|
||||||
|
# reflex-platform-func = args@{...}:
|
||||||
|
# import ((import ./.obelisk/impl {}).path + "/dep/reflex-platform") (args // { __useNewerCompiler = true; });
|
||||||
|
}
|
||||||
|
}:
|
||||||
|
with obelisk;
|
||||||
|
let
|
||||||
|
foldExtensions = lib.foldr lib.composeExtensions (_: _: {});
|
||||||
|
deps = obelisk.nixpkgs.thunkSet ./dep;
|
||||||
|
hydra-pay = import deps.hydra-pay {};
|
||||||
|
|
||||||
|
pkgs = obelisk.nixpkgs;
|
||||||
|
# cardano-libs-overlay = import ./cardano-libs.nix { inherit deps; hydra-poc = deps.hydra-poc; lib = pkgs.lib; };
|
||||||
|
in
|
||||||
|
project ./. ({ pkgs, ... }: let
|
||||||
|
haskellLib = pkgs.haskell.lib;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
android.applicationId = "systems.obsidian.obelisk.examples.minimal";
|
||||||
|
android.displayName = "Obelisk Minimal Example";
|
||||||
|
ios.bundleIdentifier = "systems.obsidian.obelisk.examples.minimal";
|
||||||
|
ios.bundleName = "Obelisk Minimal Example";
|
||||||
|
|
||||||
|
overrides = foldExtensions [
|
||||||
|
(self: super: {
|
||||||
|
string-interpolate = haskellLib.doJailbreak (haskellLib.dontCheck super.string-interpolate);
|
||||||
|
|
||||||
|
backend = haskellLib.overrideCabal super.backend (drv: {
|
||||||
|
librarySystemDepends = (drv.librarySystemDepends or []) ++ [
|
||||||
|
pkgs.coreutils
|
||||||
|
hydra-pay.exe
|
||||||
|
];
|
||||||
|
});
|
||||||
|
})
|
||||||
|
];
|
||||||
|
})
|
2
dep/aeson-gadt-th/default.nix
Normal file
2
dep/aeson-gadt-th/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
7
dep/aeson-gadt-th/github.json
Normal file
7
dep/aeson-gadt-th/github.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "aeson-gadt-th",
|
||||||
|
"private": false,
|
||||||
|
"rev": "71a315a4873c2875ad737a3320320849bdcf8a2a",
|
||||||
|
"sha256": "1sjl0a0ig0xfssl4bglakk1b7sj8wqz8hwbg7k2fk10qi7z5hb50"
|
||||||
|
}
|
9
dep/aeson-gadt-th/thunk.nix
Normal file
9
dep/aeson-gadt-th/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/aeson/default.nix
Normal file
2
dep/aeson/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/aeson/github.json
Normal file
8
dep/aeson/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "haskell",
|
||||||
|
"repo": "aeson",
|
||||||
|
"branch": "aeson-1.5",
|
||||||
|
"private": false,
|
||||||
|
"rev": "78e838df44288ac7d7ac2cd77863d2c026d86dbb",
|
||||||
|
"sha256": "181v1nz05jq992wgmz6ns7iwqx0c5w003hv5ki1kvc9zlg5dh3vf"
|
||||||
|
}
|
9
dep/aeson/thunk.nix
Normal file
9
dep/aeson/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/browser-extension/default.nix
Normal file
2
dep/browser-extension/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/browser-extension/git.json
Normal file
8
dep/browser-extension/git.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"url": "ssh://git@code.obsidian.systems/lodewallet/browser-extension",
|
||||||
|
"rev": "ae6f9202e35ce74c630da763a95071eeb337ebc8",
|
||||||
|
"sha256": "0hp1a6a219j1l8jikhp7yya6c55l23rw2360q0vzifgg7fjrd8i3",
|
||||||
|
"private": true,
|
||||||
|
"fetchSubmodules": false,
|
||||||
|
"branch": "aa/createwindow-sim"
|
||||||
|
}
|
14
dep/browser-extension/thunk.nix
Normal file
14
dep/browser-extension/thunk.nix
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = {url, rev, branch ? null, sha256 ? null, fetchSubmodules ? false, private ? false, ...}:
|
||||||
|
let realUrl = let firstChar = builtins.substring 0 1 url; in
|
||||||
|
if firstChar == "/" then /. + url
|
||||||
|
else if firstChar == "." then ./. + url
|
||||||
|
else url;
|
||||||
|
in if !fetchSubmodules && private then builtins.fetchGit {
|
||||||
|
url = realUrl; inherit rev;
|
||||||
|
${if branch == null then null else "ref"} = branch;
|
||||||
|
} else (import <nixpkgs> {}).fetchgit {
|
||||||
|
url = realUrl; inherit rev sha256;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./git.json);
|
||||||
|
in fetch json
|
2
dep/cardano-node/default.nix
Normal file
2
dep/cardano-node/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
7
dep/cardano-node/github.json
Normal file
7
dep/cardano-node/github.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"owner": "input-output-hk",
|
||||||
|
"repo": "cardano-node",
|
||||||
|
"private": false,
|
||||||
|
"rev": "ebc7be471b30e5931b35f9bbc236d21c375b91bb",
|
||||||
|
"sha256": "1j01m2cp2vdcl26zx9xmipr551v3b2rz9kfn9ik8byfwj1z7652r"
|
||||||
|
}
|
9
dep/cardano-node/thunk.nix
Normal file
9
dep/cardano-node/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/constraints-extras/default.nix
Normal file
2
dep/constraints-extras/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/constraints-extras/github.json
Normal file
8
dep/constraints-extras/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "constraints-extras",
|
||||||
|
"branch": "release/0.3.2.0",
|
||||||
|
"private": false,
|
||||||
|
"rev": "42835fd9e1b4b3c4a72cd1237c04789f01c92dd0",
|
||||||
|
"sha256": "0z7yfxxi4jywzhlkphs8ss3hd7fll8c90bbl6nr2bj63c87jx6sw"
|
||||||
|
}
|
9
dep/constraints-extras/thunk.nix
Normal file
9
dep/constraints-extras/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/entropy/default.nix
Normal file
2
dep/entropy/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/entropy/github.json
Normal file
8
dep/entropy/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "entropy",
|
||||||
|
"branch": "aa/nonwindow-contexts",
|
||||||
|
"private": false,
|
||||||
|
"rev": "ddda007c44390d0ec8cad58810710864dee60ddd",
|
||||||
|
"sha256": "00b7cqdqbjf953r5gmwmkn2mn36zdxqwx90xih925cni94vi4vw9"
|
||||||
|
}
|
9
dep/entropy/thunk.nix
Normal file
9
dep/entropy/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/hspec-webdriver-clone/default.nix
Normal file
2
dep/hspec-webdriver-clone/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/hspec-webdriver-clone/github.json
Normal file
8
dep/hspec-webdriver-clone/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "luigy",
|
||||||
|
"repo": "hspec-webdriver-clone",
|
||||||
|
"branch": "hspec-2.8",
|
||||||
|
"private": false,
|
||||||
|
"rev": "2cbe58872304d2332dfc61efaa9e0432862459d7",
|
||||||
|
"sha256": "1hm8cgw28v6g5xi25i3h1rmac7p4kh340qd5dx0ndjyxjl1lvb0p"
|
||||||
|
}
|
9
dep/hspec-webdriver-clone/thunk.nix
Normal file
9
dep/hspec-webdriver-clone/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/hydra-pay/default.nix
Normal file
2
dep/hydra-pay/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/hydra-pay/github.json
Normal file
8
dep/hydra-pay/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "hydra-pay",
|
||||||
|
"branch": "develop",
|
||||||
|
"private": false,
|
||||||
|
"rev": "9d21eb40673da38b0e57af3ea25d41135872b405",
|
||||||
|
"sha256": "1pqnjq2w0njcw4iicmkm1s3d627lspg3s65dfjlc22g9lxpaxs1q"
|
||||||
|
}
|
9
dep/hydra-pay/thunk.nix
Normal file
9
dep/hydra-pay/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/hydra-poc/default.nix
Normal file
2
dep/hydra-poc/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
7
dep/hydra-poc/github.json
Normal file
7
dep/hydra-poc/github.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"owner": "input-output-hk",
|
||||||
|
"repo": "hydra-poc",
|
||||||
|
"private": false,
|
||||||
|
"rev": "d4f242b68069765117e7c615101df17708de40a2",
|
||||||
|
"sha256": "0h0ymcw6h3lggvsmyxyvj5582mhmkv6d937qmz0px40gwjjcly05"
|
||||||
|
}
|
12
dep/hydra-poc/thunk.nix
Normal file
12
dep/hydra-poc/thunk.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import (builtins.fetchTarball {
|
||||||
|
url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz";
|
||||||
|
sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr";
|
||||||
|
}) {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/juicy.pixels.ghcjs/default.nix
Normal file
2
dep/juicy.pixels.ghcjs/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/juicy.pixels.ghcjs/github.json
Normal file
8
dep/juicy.pixels.ghcjs/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "juicy.pixels.ghcjs",
|
||||||
|
"branch": "main",
|
||||||
|
"private": false,
|
||||||
|
"rev": "a1c516493c674dba64e8eadb8a65b829477f302d",
|
||||||
|
"sha256": "06dm7kw5p1df0j0mf2wp9yyqxxzx9lih1y3l2ym8dcl1cklrnx57"
|
||||||
|
}
|
9
dep/juicy.pixels.ghcjs/thunk.nix
Normal file
9
dep/juicy.pixels.ghcjs/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/qrcode/default.nix
Normal file
2
dep/qrcode/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/qrcode/github.json
Normal file
8
dep/qrcode/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "qrcode",
|
||||||
|
"branch": "master",
|
||||||
|
"private": false,
|
||||||
|
"rev": "f33abef0451982ae7769b24e9031753ef2c4d826",
|
||||||
|
"sha256": "044bi6fnrzpzam0zyd6yijpkphjnn3z751nnxb3zz2w93wzi6nkd"
|
||||||
|
}
|
9
dep/qrcode/thunk.nix
Normal file
9
dep/qrcode/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/reflex-gadt-api/default.nix
Normal file
2
dep/reflex-gadt-api/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/reflex-gadt-api/github.json
Normal file
8
dep/reflex-gadt-api/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "reflex-frp",
|
||||||
|
"repo": "reflex-gadt-api",
|
||||||
|
"branch": "develop",
|
||||||
|
"private": false,
|
||||||
|
"rev": "69aca72b57a74fbf0d06a1b2ac1d247ce9aa207f",
|
||||||
|
"sha256": "116c6lna8pkas95lclm4halagc4f6rxv5hmf7rnl10lxqi9m48qw"
|
||||||
|
}
|
9
dep/reflex-gadt-api/thunk.nix
Normal file
9
dep/reflex-gadt-api/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/reflex/default.nix
Normal file
2
dep/reflex/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/reflex/github.json
Normal file
8
dep/reflex/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "reflex-frp",
|
||||||
|
"repo": "reflex",
|
||||||
|
"branch": "release/0.8.2.0",
|
||||||
|
"private": false,
|
||||||
|
"rev": "823afd9424234cbe0134051f09a6710e54509cec",
|
||||||
|
"sha256": "1zh8v28issyh8sww9aw6y4dqd2qql25nrxxzxg9ky0vrwmhw295c"
|
||||||
|
}
|
9
dep/reflex/thunk.nix
Normal file
9
dep/reflex/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/secp256k1-haskell/default.nix
Normal file
2
dep/secp256k1-haskell/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/secp256k1-haskell/github.json
Normal file
8
dep/secp256k1-haskell/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "haskoin",
|
||||||
|
"repo": "secp256k1-haskell",
|
||||||
|
"branch": "master",
|
||||||
|
"private": false,
|
||||||
|
"rev": "3df963ab6ae14ec122691a97af09a7331511a387",
|
||||||
|
"sha256": "1phnj7wzprrgdhji80rh16savmqq1z9q3z5yi72x8w5v42lf5f2y"
|
||||||
|
}
|
9
dep/secp256k1-haskell/thunk.nix
Normal file
9
dep/secp256k1-haskell/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/snap-core/default.nix
Normal file
2
dep/snap-core/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/snap-core/github.json
Normal file
8
dep/snap-core/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "snapframework",
|
||||||
|
"repo": "snap-core",
|
||||||
|
"branch": "master",
|
||||||
|
"private": false,
|
||||||
|
"rev": "2dc0cab7d4ec48cc690898700c7e9f9db8d1d54d",
|
||||||
|
"sha256": "1k5gwhgf489c44mv38wawv3nwl7dxr7zrsg1112s0adbavsjdbxn"
|
||||||
|
}
|
9
dep/snap-core/thunk.nix
Normal file
9
dep/snap-core/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/vessel/default.nix
Normal file
2
dep/vessel/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
7
dep/vessel/github.json
Normal file
7
dep/vessel/github.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "vessel",
|
||||||
|
"private": false,
|
||||||
|
"rev": "03b1465abeb2dea16d32feb0963f11d0ed00f2f8",
|
||||||
|
"sha256": "18vyh4ds2mng9lmn435dpbf9rj3fh9z1rimk4f2s3war9j4j5wnq"
|
||||||
|
}
|
9
dep/vessel/thunk.nix
Normal file
9
dep/vessel/thunk.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import <nixpkgs> {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
59
frontend/frontend.cabal
Normal file
59
frontend/frontend.cabal
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
name: frontend
|
||||||
|
version: 0.1
|
||||||
|
cabal-version: >= 1.10
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
library
|
||||||
|
hs-source-dirs: src
|
||||||
|
build-depends: base
|
||||||
|
, common
|
||||||
|
, obelisk-frontend
|
||||||
|
, obelisk-route
|
||||||
|
, jsaddle
|
||||||
|
, reflex-dom-core
|
||||||
|
, obelisk-executable-config-lookup
|
||||||
|
, obelisk-generated-static
|
||||||
|
, text
|
||||||
|
, string-interpolate
|
||||||
|
, containers
|
||||||
|
, aeson
|
||||||
|
, bytestring
|
||||||
|
, witherable
|
||||||
|
, transformers
|
||||||
|
, time
|
||||||
|
, lens
|
||||||
|
, random
|
||||||
|
default-extensions:
|
||||||
|
RecursiveDo
|
||||||
|
RankNTypes
|
||||||
|
FlexibleContexts
|
||||||
|
PartialTypeSignatures
|
||||||
|
ScopedTypeVariables
|
||||||
|
OverloadedStrings
|
||||||
|
TypeFamilies
|
||||||
|
RecursiveDo
|
||||||
|
LambdaCase
|
||||||
|
DeriveGeneric
|
||||||
|
exposed-modules:
|
||||||
|
Frontend
|
||||||
|
other-modules:
|
||||||
|
HydraPay.Config
|
||||||
|
HydraPay.Api
|
||||||
|
ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
|
||||||
|
|
||||||
|
executable frontend
|
||||||
|
main-is: main.hs
|
||||||
|
hs-source-dirs: src-bin
|
||||||
|
build-depends: base
|
||||||
|
, common
|
||||||
|
, obelisk-frontend
|
||||||
|
, obelisk-route
|
||||||
|
, reflex-dom
|
||||||
|
, obelisk-generated-static
|
||||||
|
, frontend
|
||||||
|
ghc-options: -threaded -O -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -fno-show-valid-hole-fits
|
||||||
|
if impl(ghcjs)
|
||||||
|
ghc-options: -dedupe
|
||||||
|
cpp-options: -DGHCJS_BROWSER
|
||||||
|
if os(darwin)
|
||||||
|
ghc-options: -dynamic
|
10
frontend/src-bin/main.hs
Normal file
10
frontend/src-bin/main.hs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import Frontend
|
||||||
|
import Common.Route
|
||||||
|
import Obelisk.Frontend
|
||||||
|
import Obelisk.Route.Frontend
|
||||||
|
import Reflex.Dom
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
let Right validFullEncoder = checkEncoder fullRouteEncoder
|
||||||
|
run $ runFrontend validFullEncoder frontend
|
890
frontend/src/Frontend.hs
Normal file
890
frontend/src/Frontend.hs
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE ExtendedDefaultRules #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-unused-local-binds #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-type-defaults #-}
|
||||||
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
{-# LANGUAGE RecursiveDo #-}
|
||||||
|
|
||||||
|
module Frontend
|
||||||
|
( frontend
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude hiding (filter)
|
||||||
|
|
||||||
|
import Hydra.Types
|
||||||
|
import HydraPay.Api as HydraPayApi
|
||||||
|
import Text.Read (readMaybe)
|
||||||
|
import Text.Printf (printf)
|
||||||
|
import System.Random
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import Data.Fixed
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Obelisk.Frontend
|
||||||
|
import Obelisk.Route
|
||||||
|
import Obelisk.Route.Frontend
|
||||||
|
import Reflex.Dom.Core
|
||||||
|
import Common.Route
|
||||||
|
import Control.Lens
|
||||||
|
import Control.Monad.Fix
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
-- import Common.Api as ChannelsApi
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
|
||||||
|
import Language.Javascript.JSaddle (jsg, js, liftJSM, fromJSValUnchecked)
|
||||||
|
import Data.Int (Int64)
|
||||||
|
import Data.Monoid
|
||||||
|
import Data.Witherable (catMaybes)
|
||||||
|
import Data.Maybe (isJust)
|
||||||
|
|
||||||
|
default (T.Text)
|
||||||
|
|
||||||
|
-- This runs in a monad that can be run on the client or the server.
|
||||||
|
-- To run code in a pure client or pure server context, use one of the
|
||||||
|
-- `prerender` functions.
|
||||||
|
frontend :: Frontend (R FrontendRoute)
|
||||||
|
frontend = Frontend
|
||||||
|
{ _frontend_head = do
|
||||||
|
el "title" $ text "Hydra Pay Payment Channel Demo"
|
||||||
|
|
||||||
|
elAttr "meta" ("name"=:"viewport" <> "content"=:"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no") blank
|
||||||
|
elAttr "link" ("rel" =: "preconnect" <> "href" =: "https://fonts.googleapis.com") blank
|
||||||
|
elAttr "link" ("rel" =: "preconnect" <> "href" =: "https://fonts.gstatic.com") blank
|
||||||
|
elAttr "link" ("href"=:"https://fonts.googleapis.com/css2?family=Inria+Sans:wght@300&family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Krona+One&family=Rajdhani:wght@300;400;500;600;700&display=swap" <> "rel"=:"stylesheet") blank
|
||||||
|
elAttr "link" ("rel"=:"stylesheet" <> "href"=:"https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,400,0,0") blank
|
||||||
|
elAttr "script" ("src"=:"https://cdn.tailwindcss.com") blank
|
||||||
|
, _frontend_body = elAttr "div" ("class" =: "w-screen h-screen overflow-hidden flex flex-col bg-gray-100" <> "style" =: "font-family: 'Inter', sans-serif;") $ do
|
||||||
|
elClass "div" "flex-shrink-0 px-8 py-4 text-xl font-semibold" $ el "h1" $ text "Hydra Pay"
|
||||||
|
prerender_ (text "Loading Payment Channel Demo") $ elClass "div" "w-full h-full mt-10 flex-grow px-8 overflow-hidden" $ do
|
||||||
|
pb <- getPostBuild
|
||||||
|
endpoint <- liftJSM $ do
|
||||||
|
let
|
||||||
|
webSocketProtocol :: T.Text -> T.Text
|
||||||
|
webSocketProtocol "https:" = "wss:"
|
||||||
|
webSocketProtocol _ = "ws:"
|
||||||
|
|
||||||
|
wsProtocol <- fmap webSocketProtocol $ fromJSValUnchecked =<< jsg "location" ^. js "protocol"
|
||||||
|
|
||||||
|
theHostname <- fromJSValUnchecked =<< jsg "location" ^. js "hostname"
|
||||||
|
let thePort = "8000" -- fromJSValUnchecked =<< jsg "location" ^. js "port"
|
||||||
|
|
||||||
|
pure $ mconcat [ wsProtocol
|
||||||
|
, "//"
|
||||||
|
, theHostname
|
||||||
|
, ":"
|
||||||
|
, thePort
|
||||||
|
, "/hydra/api"
|
||||||
|
]
|
||||||
|
rec
|
||||||
|
lastTagId <- foldDyn (+) 0 $ fromIntegral . length <$> sendToHydraPay
|
||||||
|
|
||||||
|
let
|
||||||
|
sendToHydraPay =
|
||||||
|
attachWith (\tid msgs -> uncurry Tagged <$> zip [tid..] msgs) (current lastTagId) sendMsg
|
||||||
|
|
||||||
|
serverMsg = fmapMaybe id $ rws ^. webSocket_recv
|
||||||
|
|
||||||
|
subscriptions = fmapMaybe (preview _PlainMsg) serverMsg
|
||||||
|
responses = fmapMaybe (preview _TaggedMsg) serverMsg
|
||||||
|
|
||||||
|
authResponse = fmapMaybe (preview (_AuthResult) . tagged_payload) responses
|
||||||
|
|
||||||
|
rws :: RawWebSocket t (Maybe ApiMsg) <- jsonWebSocket endpoint $ def
|
||||||
|
& webSocketConfig_send .~ sendToHydraPay
|
||||||
|
|
||||||
|
(_, sendMsg :: Event t [ClientMsg]) <- runEventWriterT $ elClass "div" "w-full h-full flex flex-row text-gray-700" $ do
|
||||||
|
-- managedDevnetReply <- requester lastTagId responses (GetIsManagedDevnet <$ pb)
|
||||||
|
-- _ <- runWithReplace blank $
|
||||||
|
-- mapMaybe ((\case
|
||||||
|
-- IsManagedDevnet x -> Just (monitorView lastTagId responses x)
|
||||||
|
-- _ -> Nothing)
|
||||||
|
-- . tagged_payload)
|
||||||
|
-- managedDevnetReply
|
||||||
|
elClass "div" "flex-grow" $ elClass "div" "max-w-lg" $ mdo
|
||||||
|
(_, authenticatedE) <-
|
||||||
|
runEventWriterT
|
||||||
|
. runWithReplace (tellEvent =<< lift (authentication lastTagId responses))
|
||||||
|
. ffor authenticatedE $ \() -> do
|
||||||
|
text "You're authenticated"
|
||||||
|
blank
|
||||||
|
runWithReplace blank (fundCreateInit lastTagId responses <$ authenticatedE)
|
||||||
|
pure ()
|
||||||
|
pure ()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
requestPb :: (PostBuild t m, EventWriter t [ClientMsg] m, MonadHold t m) =>
|
||||||
|
Dynamic t Int64
|
||||||
|
-> Event t (Tagged ServerMsg)
|
||||||
|
-> ClientMsg
|
||||||
|
-> m (Event t ServerMsg)
|
||||||
|
requestPb lastId serverMsg msg = do
|
||||||
|
pb <- getPostBuild
|
||||||
|
requester lastId serverMsg (msg <$ pb)
|
||||||
|
|
||||||
|
|
||||||
|
requestPbExpected :: (PostBuild t m, EventWriter t [ClientMsg] m, MonadHold t m) =>
|
||||||
|
Dynamic t Int64
|
||||||
|
-> Event t (Tagged ServerMsg)
|
||||||
|
-> Getting (Data.Monoid.First b) ServerMsg b
|
||||||
|
-> ClientMsg
|
||||||
|
-> m (Event t (Maybe b))
|
||||||
|
requestPbExpected lastId serverMsg sel msg = do
|
||||||
|
fmap (preview sel) <$> requestPb lastId serverMsg msg
|
||||||
|
|
||||||
|
requestPbExact :: (PostBuild t m, EventWriter t [ClientMsg] m, MonadHold t m) =>
|
||||||
|
Dynamic t Int64
|
||||||
|
-> Event t (Tagged ServerMsg)
|
||||||
|
-> Getting (Data.Monoid.First b) ServerMsg b
|
||||||
|
-> ClientMsg
|
||||||
|
-> m (Event t b)
|
||||||
|
requestPbExact lastId serverMsg sel msg = do
|
||||||
|
catMaybes <$> requestPbExpected lastId serverMsg sel msg
|
||||||
|
|
||||||
|
getAddresses :: (PostBuild t m, EventWriter t [ClientMsg] m, MonadHold t m) =>
|
||||||
|
Dynamic t Int64
|
||||||
|
-> Event t (Tagged ServerMsg) -> m (Event t [Address])
|
||||||
|
getAddresses lastId serverMsg =
|
||||||
|
requestPbExact lastId serverMsg _DevnetAddresses (GetDevnetAddresses 2)
|
||||||
|
|
||||||
|
viewUntil :: _ => Workflow t m (Maybe a) -> m (Event t a)
|
||||||
|
viewUntil = fmap catMaybes . workflowView
|
||||||
|
|
||||||
|
doFundingTx ::
|
||||||
|
( NotReady t m,
|
||||||
|
Adjustable t m,
|
||||||
|
PostBuild t m,
|
||||||
|
EventWriter t [ClientMsg] m,
|
||||||
|
MonadHold t m,
|
||||||
|
MonadFix m
|
||||||
|
) =>
|
||||||
|
Dynamic t Int64 ->
|
||||||
|
Event t (Tagged ServerMsg) ->
|
||||||
|
Lovelace ->
|
||||||
|
TxType ->
|
||||||
|
Address ->
|
||||||
|
m (Event t Bool)
|
||||||
|
doFundingTx lastId serverMsg amnt typ addr = viewUntil $ Workflow $ do
|
||||||
|
fundsTxE <- requestPbExact lastId serverMsg _FundsTx (GetAddTx typ addr amnt)
|
||||||
|
pure . (Nothing,) . ffor fundsTxE $ \tx -> Workflow $ do
|
||||||
|
submitSuccessE <- requestPbExpected lastId serverMsg _OperationSuccess (LiveDocEzSubmitTx tx addr)
|
||||||
|
pure . (Nothing,) . ffor submitSuccessE $ \v -> Workflow . pure $ (Just (isJust v), never)
|
||||||
|
|
||||||
|
-- TODO: Use Either instead of Bool to get the errors/unexpected values reported back
|
||||||
|
andWhen :: _ => m (Event t Bool) -> m (Event t Bool) -> m (Event t Bool)
|
||||||
|
andWhen me mc = viewUntil . Workflow $ do
|
||||||
|
e <- me
|
||||||
|
pure . (Nothing,) . ffor e $ \case
|
||||||
|
False -> Workflow $ pure (Just False, never)
|
||||||
|
True -> Workflow $ do
|
||||||
|
c <- mc
|
||||||
|
pure . (Nothing,) . ffor c $ Workflow . pure . (, never) . Just
|
||||||
|
|
||||||
|
doAllFunding ::
|
||||||
|
( NotReady t m,
|
||||||
|
Adjustable t m,
|
||||||
|
PostBuild t m,
|
||||||
|
EventWriter t [ClientMsg] m,
|
||||||
|
MonadHold t m,
|
||||||
|
MonadFix m, _
|
||||||
|
) =>
|
||||||
|
Dynamic t Int64 ->
|
||||||
|
Event t (Tagged ServerMsg) ->
|
||||||
|
Lovelace ->
|
||||||
|
[Address] ->
|
||||||
|
m (Event t Bool)
|
||||||
|
doAllFunding lastId serverMsg amnt [one, two] =
|
||||||
|
(text "Funding Alice" >> doFundingTx lastId serverMsg amnt Funds one)
|
||||||
|
`andWhen` (text "Fueling Alice" >> doFundingTx lastId serverMsg amnt Fuel one)
|
||||||
|
`andWhen` (text "Funding Bob" >> doFundingTx lastId serverMsg amnt Funds two)
|
||||||
|
`andWhen` (text "Fueling Bob" >> doFundingTx lastId serverMsg amnt Fuel one)
|
||||||
|
|
||||||
|
singletonWorkflow :: _ => a -> Workflow t m a
|
||||||
|
singletonWorkflow = Workflow . pure . (,never)
|
||||||
|
|
||||||
|
createAndInitHead :: _ => _ -> _ -> [_] -> m (Event t Bool)
|
||||||
|
createAndInitHead lastId serverMsg addrs = do
|
||||||
|
((True <$) <$> requestPbExact lastId serverMsg _OperationSuccess (CreateHead (HeadCreate theHeadName addrs)))
|
||||||
|
`andWhen`
|
||||||
|
((True <$) <$> requestPbExact lastId serverMsg _OperationSuccess (InitHead (HeadInit theHeadName 3)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fundCreateInit :: (NotReady t m, Adjustable t m, PostBuild t m,
|
||||||
|
EventWriter t [ClientMsg] m, MonadHold t m, MonadFix m, _) =>
|
||||||
|
Dynamic t Int64 -> Event t (Tagged ServerMsg) -> m (Event t [Address])
|
||||||
|
fundCreateInit lastId serverMsg = do
|
||||||
|
addrE <- getAddresses lastId serverMsg
|
||||||
|
viewUntil . Workflow . pure . (Nothing,) . ffor addrE $ \addrs -> Workflow $ do
|
||||||
|
res <- doAllFunding lastId serverMsg (ada 100) addrs
|
||||||
|
`andWhen` createAndInitHead lastId serverMsg addrs
|
||||||
|
-- TODO: This just assumes all went well, it should check for errors and abort.
|
||||||
|
-- (I.e. check whether res == False.)
|
||||||
|
pure . (Nothing,) $ Workflow (do
|
||||||
|
text "Head created"
|
||||||
|
pure . (,never) . Just $ addrs)
|
||||||
|
<$ res
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- gotAddrs <- fmap switchDyn $ prerender (pure never) $ do
|
||||||
|
-- rec
|
||||||
|
-- jsBuild <- getPostBuild
|
||||||
|
-- -- FIXME: something changed here
|
||||||
|
-- result <- getAndDecode $ "/demo-addresses" <$ leftmost [jsBuild]
|
||||||
|
-- --, () <$ failedToLoad]
|
||||||
|
-- let
|
||||||
|
-- -- If we failed it is likely the server just hot reloaded in development
|
||||||
|
-- -- and we just try again immediately
|
||||||
|
-- failedToLoad = fmapMaybe (preview _Nothing) result
|
||||||
|
-- addrsRecv = fmapMaybe (preview _Just) result
|
||||||
|
-- pure addrsRecv
|
||||||
|
|
||||||
|
-- rec
|
||||||
|
-- addrs <- holdDyn (Nothing :: Maybe [Address]) gotAddrs
|
||||||
|
-- nextId <- foldDyn (+) (0 :: Int) $ 1 <$ newTx
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- insertNewTx = (current $ Map.insert <$> nextId) <@> newTx
|
||||||
|
|
||||||
|
-- latestPopupTxs <- foldDyn ($) mempty $ mergeWith (.) $ [ insertNewTx
|
||||||
|
-- , removeStaleTxs
|
||||||
|
-- ]
|
||||||
|
-- latestTxs <- foldDyn ($) mempty $ mergeWith (.) $ [ insertNewTx
|
||||||
|
-- ]
|
||||||
|
-- let
|
||||||
|
-- totalTxDisplayTime = 4
|
||||||
|
-- -- We assume the first and second index to be Bob and Alice respectively
|
||||||
|
-- bobAddress = ffor addrs $ join . fmap (^? ix 0)
|
||||||
|
-- aliceAddress = ffor addrs $ join . fmap (^? ix 1)
|
||||||
|
|
||||||
|
-- newTxPopup tx = do
|
||||||
|
-- transitionIn <- getPostBuild >>= delay 0.1
|
||||||
|
-- transitionOut <- tickLossyFromPostBuildTime totalTxDisplayTime
|
||||||
|
-- removeThisPopup <- tickLossyFromPostBuildTime $ totalTxDisplayTime + 2
|
||||||
|
-- visible <- holdDyn False $ mergeWith (&&) [True <$ transitionIn, False <$ transitionOut]
|
||||||
|
-- let
|
||||||
|
-- mkClasses b =
|
||||||
|
-- mconcat [ "transition pointer-events-none duration-1000 px-8 py-4 rounded-lg bg-white mb-4 drop-shadow-lg "
|
||||||
|
-- , bool "opacity-0" "opacity-100" b
|
||||||
|
-- ]
|
||||||
|
-- elDynClass "div" (mkClasses <$> visible) $ do
|
||||||
|
-- elClass "div" "text-xs text-gray-500 mb-2" $ text "New Transaction"
|
||||||
|
|
||||||
|
-- elClass "div" "flex flex-row justify-between items-center" $ do
|
||||||
|
-- elClass "div" "flex flex-col mr-16" $ do
|
||||||
|
-- elClass "div" "text-gray-600" $ do
|
||||||
|
-- text "To "
|
||||||
|
-- dynText $ demoTx_to <$> tx
|
||||||
|
-- elClass "div" "text-xl" $ do
|
||||||
|
-- dynText $ T.pack . printf "%.2f" . lovelaceToAda . demoTx_amount <$> tx
|
||||||
|
-- text "ADA"
|
||||||
|
|
||||||
|
-- elClass "div" "flex flex-row text-gray-400" $ do
|
||||||
|
-- elClass "div" "flex flex-col" $ do
|
||||||
|
-- elClass "div" "text-xs" $ text "Time"
|
||||||
|
-- elClass "div" "" $ do
|
||||||
|
-- dynText $ showAsMs . demoTx_time <$> tx
|
||||||
|
-- text "ms"
|
||||||
|
-- pure $ () <$ removeThisPopup
|
||||||
|
|
||||||
|
-- -- When we get a server response, go to open a channel
|
||||||
|
-- newTx <- appView bobAddress aliceAddress latestTxs
|
||||||
|
|
||||||
|
-- removeStaleTxs <- elClass "div" "pointer-events-none absolute top-0 right-0 p-4 h-full overflow-hidden leading-none" $ elClass "div" "flex flex-col-reverse" $ do
|
||||||
|
-- eventList <- listWithKey latestPopupTxs $ \txid tx -> do
|
||||||
|
-- removalEv <- newTxPopup tx
|
||||||
|
-- pure $ Map.delete txid <$ removalEv
|
||||||
|
|
||||||
|
-- pure $ switchDyn $ mergeWith (.) . Map.elems <$> eventList
|
||||||
|
-- subRoute_ $ \case
|
||||||
|
-- _ -> do
|
||||||
|
-- setRoute $ FrontendRoute_OpenChannel :/ () <$ gotAddrs
|
||||||
|
-- setRoute $ FrontendRoute_Setup :/ () <$ postBuild
|
||||||
|
-- setRoute $ FrontendRoute_PaymentChannel :/ () <$ newTx
|
||||||
|
-- pure ()
|
||||||
|
|
||||||
|
data DemoTx = DemoTx
|
||||||
|
{ demoTx_to :: T.Text
|
||||||
|
, demoTx_amount :: Lovelace
|
||||||
|
, demoTx_time :: Pico
|
||||||
|
}
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
showAsMs :: Pico -> T.Text
|
||||||
|
showAsMs = T.pack . printf "%.2f" . (realToFrac :: Pico -> Float) . (*1000)
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO(skylar): Use Requester from reflex
|
||||||
|
requester :: (Reflex t, EventWriter t [ClientMsg] m, MonadHold t m) => Dynamic t Int64 -> Event t (Tagged ServerMsg) -> Event t ClientMsg -> m (Event t ServerMsg)
|
||||||
|
requester lastTagId serverMsg clientMsg = do
|
||||||
|
waitingTag <- holdDyn Nothing $ current (Just <$> lastTagId) <@ clientMsg
|
||||||
|
|
||||||
|
let
|
||||||
|
isWhatWeAreWaitingFor (Just nid) msg@(Tagged nid' _) | nid == nid' = Just msg
|
||||||
|
isWhatWeAreWaitingFor _ _ = Nothing
|
||||||
|
|
||||||
|
properServerMsg = attachWithMaybe isWhatWeAreWaitingFor (current waitingTag) serverMsg
|
||||||
|
|
||||||
|
tellEvent $ pure <$> clientMsg
|
||||||
|
pure (tagged_payload <$> properServerMsg)
|
||||||
|
|
||||||
|
|
||||||
|
authentication ::
|
||||||
|
( PostBuild t m
|
||||||
|
, SetRoute t (R FrontendRoute) m
|
||||||
|
, SetRoute t (R FrontendRoute) m
|
||||||
|
, RouteToUrl (R FrontendRoute) m
|
||||||
|
, MonadIO (Performable m)
|
||||||
|
, PerformEvent t m
|
||||||
|
, TriggerEvent t m
|
||||||
|
, DomBuilder t m
|
||||||
|
, MonadFix m
|
||||||
|
, MonadHold t m
|
||||||
|
, EventWriter t [ClientMsg] m
|
||||||
|
) => Dynamic t Int64 -> Event t (Tagged ServerMsg) -> m (Event t ())
|
||||||
|
authentication lastTagId serverMsg = do
|
||||||
|
apiKeyInput <- inputElement $ def
|
||||||
|
& initialAttributes .~ ("class" =: "pl-4 pr-2 py-1 bg-transparent text-right" <> "placeholder" =: "Hydra Pay API Key")
|
||||||
|
let
|
||||||
|
pressedEnter = keypress Enter apiKeyInput
|
||||||
|
|
||||||
|
authReply <- requester lastTagId serverMsg $
|
||||||
|
Authenticate <$> current (_inputElement_value apiKeyInput) <@ pressedEnter
|
||||||
|
pure . mapMaybe (\case
|
||||||
|
(AuthResult True) -> Just ()
|
||||||
|
_ -> Nothing)
|
||||||
|
$ authReply
|
||||||
|
|
||||||
|
initialBalances :: _ => _ -> _ -> (_,_) -> m ()
|
||||||
|
initialBalances lastTagId serverMsg (bobAddress, aliceAddress)= do
|
||||||
|
-- Page Title
|
||||||
|
elClass "div" "mb-2 font-semibold" $ text "Open a Payment Channel"
|
||||||
|
-- Divider
|
||||||
|
elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- Balances
|
||||||
|
elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "Current Balances"
|
||||||
|
|
||||||
|
elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
balanceWidget lastTagId serverMsg "Bob" bobAddress never L1B
|
||||||
|
balanceWidget lastTagId serverMsg "Alice" aliceAddress never L1B
|
||||||
|
|
||||||
|
-- Divider
|
||||||
|
elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- Open Payment Channel UI
|
||||||
|
elClass "div" "mt-8" $ do
|
||||||
|
elClass "div" "text-sm mb-1" $ text "Amount to Spend"
|
||||||
|
_ :: Dynamic t (Maybe Float) <- el "div" $ elClass "div" "w-fit border-2 border-gray-200 flex flex-row items-center" $ do
|
||||||
|
amountInput <- inputElement $ def
|
||||||
|
& initialAttributes .~ ("class" =: "pl-4 pr-2 py-1 bg-transparent text-right" <> "placeholder" =: "1 ADA" <> "type" =: "number")
|
||||||
|
& inputElementConfig_initialValue .~ "1000"
|
||||||
|
elClass "span" "mx-2 my-1" $ text "ADA"
|
||||||
|
pure $ readMaybe . T.unpack <$> _inputElement_value amountInput
|
||||||
|
elClass "div" "text-xs my-1" $ text "100 ADA in fast payment collateral will also be held in hydra pay"
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
todo_CONTINUE_WITH_THIS_BUTTON <- elClass "button" "rounded mt-4 p-4 text-center w-full bg-gray-800 text-white font-bold" $ text "Open Payment Channel"
|
||||||
|
pure ()
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
|
||||||
|
data BalanceType = L1B | HdB
|
||||||
|
|
||||||
|
theHeadName = "channelsHead"
|
||||||
|
|
||||||
|
balanceWidget :: _ => _ -> _ -> _ -> _ -> Event t () -> _ -> m (Dynamic t _)
|
||||||
|
balanceWidget lastTagId serverMsg name addr refetch btype = do
|
||||||
|
elClass "div" "text-lg flex flex-col mr-10" $ do
|
||||||
|
elClass "div" "font-semibold" $ text name
|
||||||
|
-- NOTE(skylar): We assume we have loaded bobAddress if this is visible, so we don't worry about the outer Nothing
|
||||||
|
fmap (fmap getFirst . snd) . runDynamicWriterT . elClass "div" "font-semibold" $ mdo
|
||||||
|
balanceResult' <- requestPb lastTagId serverMsg ((case btype of
|
||||||
|
L1B -> GetL1Balance
|
||||||
|
HdB -> GetHeadBalance theHeadName)
|
||||||
|
addr)
|
||||||
|
|
||||||
|
let balanceResult = fmap (\case
|
||||||
|
L1Balance b -> Just b
|
||||||
|
HeadBalance b -> Just b
|
||||||
|
_ -> Nothing)
|
||||||
|
balanceResult'
|
||||||
|
let
|
||||||
|
gotBalance = fmapMaybe (preview _Just) balanceResult
|
||||||
|
reqFailed = fmapMaybe (preview _Nothing) balanceResult
|
||||||
|
|
||||||
|
delayedFail <- delay 1 reqFailed
|
||||||
|
mBalance <- holdDyn (Nothing :: Maybe Float) $ Just . lovelaceToAda <$> gotBalance
|
||||||
|
tellDyn (First <$> mBalance)
|
||||||
|
|
||||||
|
dyn_ $ ffor mBalance $ \case
|
||||||
|
Nothing -> elClass "div" "animate-pulse bg-gray-700 w-16 h-4" blank
|
||||||
|
Just balance -> do
|
||||||
|
text $ T.pack . printf "%.2f" $ balance
|
||||||
|
text " ADA"
|
||||||
|
|
||||||
|
pure $ maybe 0 id <$> mBalance
|
||||||
|
|
||||||
|
|
||||||
|
paymentChannel :: _ => _ -> _ -> [Address] -> _ -> m (Event t DemoTx)
|
||||||
|
paymentChannel lastTagId serverMsg [aliceAddress, bobAddress] latestTxs = do
|
||||||
|
-- Page Title
|
||||||
|
elClass "div" "w-full flex flex-row items-baseline justify-between" $ do
|
||||||
|
elClass "div" "mb-2 font-semibold" $ text "Bob & Alice's Payment Channel"
|
||||||
|
todo_CONTINUE_WITH_THIS_BUTTON <- elClass "button" "rounded mt-4 px-6 py-2 text-center bg-gray-800 text-white font-semibold" $ text "Close Payment Channel"
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
-- Divider
|
||||||
|
elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "L1 Balance"
|
||||||
|
|
||||||
|
elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
balanceWidget lastTagId serverMsg "Bob" bobAddress never L1B
|
||||||
|
balanceWidget lastTagId serverMsg "Alice" aliceAddress never L1B
|
||||||
|
|
||||||
|
-- Divider
|
||||||
|
elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- Balances
|
||||||
|
elClass "div" "text-2xl mb-2 font-semibold mt-8" $ do
|
||||||
|
text "Payment Channel Balance"
|
||||||
|
|
||||||
|
rec
|
||||||
|
(bobBalance, aliceBalance) <- elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
bb <- balanceWidget lastTagId serverMsg "Bob" bobAddress autoTx HdB
|
||||||
|
ab <- balanceWidget lastTagId serverMsg "Alice" aliceAddress autoTx HdB
|
||||||
|
pure (bb, ab)
|
||||||
|
|
||||||
|
-- Divider
|
||||||
|
elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- Statistics
|
||||||
|
elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "Statistics"
|
||||||
|
|
||||||
|
elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
elClass "div" "mr-16" $ do
|
||||||
|
elClass "div" "text-lg font-semibold" $ text "Total Transactions"
|
||||||
|
elClass "div" "text-3xl text-gray-600" $ dynText $ T.pack . show . Map.size <$> latestTxs
|
||||||
|
|
||||||
|
elClass "div" "mr-16" $ do
|
||||||
|
elClass "div" "text-lg font-semibold" $ text "Total Time"
|
||||||
|
elClass "div" "text-3xl text-gray-600" $ do
|
||||||
|
dynText $ showAsMs . Map.foldr ((+) . demoTx_time) 0 <$> latestTxs
|
||||||
|
text "ms"
|
||||||
|
|
||||||
|
-- Divider
|
||||||
|
elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- Action buttons: Send & Automate
|
||||||
|
(automateButton, _) <- elClass "div" "" $ do
|
||||||
|
todo_CONTINUE_WITH_THIS_BUTTON <- elClass "button" "rounded mt-4 px-6 py-2 text-center bg-gray-800 text-white font-semibold mr-4" $ text "Send ADA"
|
||||||
|
elClass' "button" "rounded mt-4 px-6 py-2 text-center bg-gray-800 text-white font-semibold" $ dynText $ ffor automating $ \case
|
||||||
|
True -> "Automating"
|
||||||
|
False -> "Automate"
|
||||||
|
|
||||||
|
let
|
||||||
|
toggleAutomate = domEvent Click automateButton
|
||||||
|
|
||||||
|
automating <- foldDyn ($) False $ not <$ toggleAutomate
|
||||||
|
|
||||||
|
fmap (getFirst . snd) . runEventWriterT . dyn $ ffor automating $ \case
|
||||||
|
False -> pure ()
|
||||||
|
True -> do
|
||||||
|
tick <- tickLossyFromPostBuildTime 1
|
||||||
|
randomAmount :: Event t Lovelace <- performEvent $ (liftIO $ ada <$> randomRIO (1,10)) <$ tick
|
||||||
|
let
|
||||||
|
nextToAddr :: Dynamic t Address = ffor2 bobBalance aliceBalance $ \bb ab ->
|
||||||
|
if bb > ab then aliceAddress else bobAddress
|
||||||
|
|
||||||
|
nextFromAddr = ffor2 bobBalance aliceBalance $ \bb ab ->
|
||||||
|
if bb > ab then bobAddress else aliceAddress
|
||||||
|
|
||||||
|
bobIsTo = (fmap (== bobAddress) nextToAddr)
|
||||||
|
toName = ffor bobIsTo $ \case
|
||||||
|
True -> "Bob"
|
||||||
|
False -> "Alice"
|
||||||
|
|
||||||
|
sendAdaResponse <- requester lastTagId serverMsg $
|
||||||
|
(\from to am -> SubmitHeadTx from $ HeadSubmitTx theHeadName to am)
|
||||||
|
<$> current nextFromAddr
|
||||||
|
<*> current nextToAddr
|
||||||
|
<@> randomAmount
|
||||||
|
|
||||||
|
-- picoToLatestTx <- holdDyn Nothing $ Just <$> changeDemoBuildTxFunc
|
||||||
|
|
||||||
|
let
|
||||||
|
sendSuccess = fmapMaybe (preview _TxConfirmed) $ sendAdaResponse
|
||||||
|
|
||||||
|
-- changeDemoBuildTxFunc :: _ = attachWith DemoTx <$> current toName <@> randomAmount
|
||||||
|
|
||||||
|
-- pure $ attachWithMaybe (<*>) (current picoToLatestTx) $ Just <$> sendSuccess
|
||||||
|
tellEvent never -- TODO: put DemoTxes here
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
-- Transaction History
|
||||||
|
elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "History"
|
||||||
|
|
||||||
|
let
|
||||||
|
historyItem tx = do
|
||||||
|
elClass "div" "transition pointer-events-none duration-1000 px-4 py-2 rounded bg-white mb-4 drop-shadow " $ do
|
||||||
|
elClass "div" "flex flex-row justify-between items-center" $ do
|
||||||
|
elClass "div" "flex flex-col mr-16" $ do
|
||||||
|
elClass "div" "text-gray-600" $ do
|
||||||
|
text "To "
|
||||||
|
dynText $ demoTx_to <$> tx
|
||||||
|
elClass "div" "text-xl" $ do
|
||||||
|
dynText $ T.pack . printf "%.2f" . lovelaceToAda . demoTx_amount <$> tx
|
||||||
|
text "ADA"
|
||||||
|
|
||||||
|
elClass "div" "flex flex-row text-gray-400" $ do
|
||||||
|
elClass "div" "flex flex-col" $ do
|
||||||
|
elClass "div" "text-xs" $ text "Time"
|
||||||
|
elClass "div" "" $ do
|
||||||
|
dynText $ showAsMs . demoTx_time <$> tx
|
||||||
|
text "ms"
|
||||||
|
|
||||||
|
let
|
||||||
|
noHistory = Map.null <$> latestTxs
|
||||||
|
|
||||||
|
elDynClass "div" "overflow-y-scroll flex-grow" $ dyn_ $ ffor noHistory $ \case
|
||||||
|
True -> elClass "div" "text-gray-500" $ text "There is no history yet for this payment channel"
|
||||||
|
False -> do
|
||||||
|
_ <- elClass "div" "flex flex-col-reverse w-full" $ listWithKey latestTxs $ \_ tx -> historyItem tx
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
pure never -- TODO: PUT AUTO TX BACK HERE
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- mapMaybe
|
||||||
|
|
||||||
|
-- dyn_ $ ffor mApiKey $ \case
|
||||||
|
-- Nothing -> text "Please enter your API Key"
|
||||||
|
-- Just apiKey -> mdo
|
||||||
|
-- (authEl, _) <- elClass' "button" "rounded mt-4 p-4 text-center w-full bg-gray-800 text-white font-bold" $ text "Authenticate"
|
||||||
|
|
||||||
|
-- lastTagId <- foldDyn (+) 0 $ fromIntegral . length <$> sendToHydraPay
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- authenticate = Authenticate apiKey <$ domEvent Click authEl
|
||||||
|
|
||||||
|
-- sendMsg =
|
||||||
|
-- mergeWith (<>) $ (fmap . fmap) pure $ [authenticate]
|
||||||
|
|
||||||
|
-- sendToHydraPay =
|
||||||
|
-- attachWith (\tid msgs -> fmap (uncurry Tagged) $ zip [tid..] msgs) (current lastTagId) sendMsg
|
||||||
|
|
||||||
|
-- serverMsg = fmapMaybe id $ rws ^. webSocket_recv
|
||||||
|
|
||||||
|
-- appView :: forall t m.
|
||||||
|
-- ( PostBuild t m
|
||||||
|
-- , Prerender t m
|
||||||
|
-- , SetRoute t (R FrontendRoute) (Client m)
|
||||||
|
-- , SetRoute t (R FrontendRoute) m
|
||||||
|
-- , RouteToUrl (R FrontendRoute) m
|
||||||
|
-- , MonadIO (Performable m)
|
||||||
|
-- , PerformEvent t m
|
||||||
|
-- , TriggerEvent t m
|
||||||
|
-- , DomBuilder t m
|
||||||
|
-- , MonadFix m
|
||||||
|
-- , MonadHold t m
|
||||||
|
-- ) => Dynamic t (Maybe T.Text) -> Dynamic t (Maybe T.Text) -> Dynamic t (Map Int DemoTx) -> RoutedT t (R FrontendRoute) m (Event t DemoTx)
|
||||||
|
-- appView bobAddress aliceAddress latestTxs = do
|
||||||
|
-- fmap switchDyn $ elClass "div" "w-full h-full text-gray-700 max-w-4xl mx-auto p-4 rounded flex flex-col" $ subRoute $ \case
|
||||||
|
-- FrontendRoute_Home -> homeView >> pure never
|
||||||
|
-- FrontendRoute_Setup -> do
|
||||||
|
-- elClass "div" "text-3xl flex flex-col justify-center items-center" $ do
|
||||||
|
-- el "div" $ text "Fast Payments Demo"
|
||||||
|
-- elClass "div" "text-lg" $ text "populating addresses..."
|
||||||
|
-- pure never
|
||||||
|
|
||||||
|
-- FrontendRoute_OpeningChannel -> do
|
||||||
|
-- elClass "div" "text-3xl flex flex-col justify-center items-center" $ do
|
||||||
|
-- el "div" $ text "Fast Payments Demo"
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- messages = [ "Sending funds from Bob and Alice into Hydra Pay"
|
||||||
|
-- , "Creating payment channel"
|
||||||
|
-- , "Waiting for payment channel"
|
||||||
|
-- ]
|
||||||
|
|
||||||
|
-- tick <- tickLossyFromPostBuildTime 2
|
||||||
|
-- rec
|
||||||
|
-- currentIndex <- holdDyn (0 :: Int) $ fmap (min (length messages - 1) . (+1)) $ current currentIndex <@ tick
|
||||||
|
-- elClass "div" "text-lg" $ do
|
||||||
|
-- dynText $ (messages !!) <$> currentIndex
|
||||||
|
-- text "..."
|
||||||
|
|
||||||
|
-- prerender_ blank $ mdo
|
||||||
|
-- ws :: RawWebSocket t (Maybe ChannelsApi.ServerMsg) <- lift $ jsonWebSocket "ws://localhost:8000/api" $ (WebSocketConfig @t @ChannelsApi.ClientMsg) wssend never True []
|
||||||
|
-- let wssend = [DemoInit] <$ _webSocket_open ws
|
||||||
|
-- setRoute $ FrontendRoute_PaymentChannel :/ () <$ ffilter (== Just InitDone) (_webSocket_recv ws)
|
||||||
|
-- pure never
|
||||||
|
|
||||||
|
-- FrontendRoute_ClosingChannel -> do
|
||||||
|
-- elClass "div" "text-3xl flex flex-col justify-center items-center" $ do
|
||||||
|
-- el "div" $ text "Fast Payments Demo"
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- messages = [ "Closing payment channel"
|
||||||
|
-- , "Settling payment channel on L1"
|
||||||
|
-- , "Withdrawing funds from payment channel"
|
||||||
|
-- ]
|
||||||
|
|
||||||
|
-- tick <- tickLossyFromPostBuildTime 5
|
||||||
|
-- rec
|
||||||
|
-- currentIndex <- holdDyn (0 :: Int) $ fmap (min (length messages - 1) . (+1)) $ current currentIndex <@ tick
|
||||||
|
-- elClass "div" "text-lg" $ do
|
||||||
|
-- dynText $ (messages !!) <$> currentIndex
|
||||||
|
-- text "..."
|
||||||
|
|
||||||
|
-- prerender_ blank $ mdo
|
||||||
|
-- ws :: RawWebSocket t (Maybe ChannelsApi.ServerMsg) <- lift $ jsonWebSocket "ws://localhost:8000/api" $ (WebSocketConfig @t @ChannelsApi.ClientMsg) wssend never True []
|
||||||
|
-- let wssend = [CloseFanout] <$ _webSocket_open ws
|
||||||
|
-- setRoute $ FrontendRoute_OpenChannel :/ () <$ ffilter (== Just CloseFanoutDone) (_webSocket_recv ws)
|
||||||
|
-- pure never
|
||||||
|
|
||||||
|
-- FrontendRoute_OpenChannel -> do
|
||||||
|
-- -- Page Title
|
||||||
|
-- elClass "div" "mb-2 font-semibold" $ text "Open a Payment Channel"
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- -- Balances
|
||||||
|
-- elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "Current Balances"
|
||||||
|
|
||||||
|
-- elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
-- balanceWidget "Bob" bobAddress never "l1-balance"
|
||||||
|
-- balanceWidget "Alice" aliceAddress never "l1-balance"
|
||||||
|
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- -- Open Payment Channel UI
|
||||||
|
-- elClass "div" "mt-8" $ do
|
||||||
|
-- elClass "div" "text-sm mb-1" $ text "Amount to Spend"
|
||||||
|
-- _ :: Dynamic t (Maybe Float) <- el "div" $ elClass "div" "w-fit border-2 border-gray-200 flex flex-row items-center" $ do
|
||||||
|
-- amountInput <- inputElement $ def
|
||||||
|
-- & initialAttributes .~ ("class" =: "pl-4 pr-2 py-1 bg-transparent text-right" <> "placeholder" =: "1 ADA" <> "type" =: "number")
|
||||||
|
-- & inputElementConfig_initialValue .~ "1000"
|
||||||
|
-- elClass "span" "mx-2 my-1" $ text "ADA"
|
||||||
|
-- pure $ readMaybe . T.unpack <$> _inputElement_value amountInput
|
||||||
|
-- elClass "div" "text-xs my-1" $ text "100 ADA in fast payment collateral will also be held in hydra pay"
|
||||||
|
-- pure ()
|
||||||
|
|
||||||
|
-- routeLink (FrontendRoute_OpeningChannel :/ ()) $ elClass "button" "rounded mt-4 p-4 text-center w-full bg-gray-800 text-white font-bold" $ text "Open Payment Channel"
|
||||||
|
-- pure never
|
||||||
|
|
||||||
|
-- FrontendRoute_SendFunds -> mdo
|
||||||
|
-- -- Page Header
|
||||||
|
-- elClass "div" "relative w-full flex flex-row items-baseline justify-between" $ do
|
||||||
|
-- elClass "div" "mb-2 font-semibold" $ text "Bob & Alice's Payment Channel"
|
||||||
|
-- (backButton, _) <- elClass' "button" "absolute -left-8 rounded w-6 h-6 bg-gray-300 text-center font-semibold" $ text "<"
|
||||||
|
-- -- NOTE(skylar): Route link prevents the absolute-ness being applied properly, so we just use setRoute!
|
||||||
|
-- setRoute $ FrontendRoute_PaymentChannel :/ () <$ domEvent Click backButton
|
||||||
|
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- -- Balances
|
||||||
|
-- elClass "div" "text-2xl mb-2 font-semibold mt-8" $ do
|
||||||
|
-- text "Current Balances "
|
||||||
|
-- elClass "span" "text-sm" $ text " In payment channel"
|
||||||
|
|
||||||
|
-- elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
-- balanceWidget "Bob" bobAddress never "head-balance"
|
||||||
|
-- balanceWidget "Alice" aliceAddress never "head-balance"
|
||||||
|
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- -- Send
|
||||||
|
-- elClass "div" "text-2xl mb-4 font-semibold mt-8" $ text "Send ADA"
|
||||||
|
|
||||||
|
-- -- To/From Selector
|
||||||
|
-- rec
|
||||||
|
-- (selectorButton, _) <- elClass' "button" "w-fit bg-gray-300 px-4 py-2 rounded flex gap-6 flex-row" $ do
|
||||||
|
-- elClass "div" "" $ do
|
||||||
|
-- elClass "div" "text-left text-xs" $ text "From"
|
||||||
|
-- elClass "div" "font-semibold" $ dynText $ ffor bobIsTo $ \case
|
||||||
|
-- True -> "Alice"
|
||||||
|
-- False -> "Bob"
|
||||||
|
|
||||||
|
-- elClass "div" "" $ do
|
||||||
|
-- elClass "div" "text-left text-xs" $ text "To"
|
||||||
|
-- elClass "div" "font-semibold" $ dynText $ toName
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- toName = ffor bobIsTo $ \case
|
||||||
|
-- True -> "Bob"
|
||||||
|
-- False -> "Alice"
|
||||||
|
|
||||||
|
-- bobIsTo = ((==) <$> toAddr <*> bobAddress)
|
||||||
|
-- changeSelection = current nextSelection <@ domEvent Click selectorButton
|
||||||
|
-- fromAddr = ffor3 bobIsTo bobAddress aliceAddress $ \isBob bob alice ->
|
||||||
|
-- if isBob then alice else bob
|
||||||
|
-- nextSelection = fromAddr
|
||||||
|
|
||||||
|
-- sendBuild <- getPostBuild
|
||||||
|
-- toAddr <- holdDyn Nothing $ leftmost [current bobAddress <@ sendBuild, changeSelection]
|
||||||
|
-- pure ()
|
||||||
|
|
||||||
|
-- lovelaceSendAmount :: Dynamic t (Maybe Integer) <- el "div" $ elClass "div" "mt-4 w-full border-2 border-gray-200 flex flex-row items-center" $ do
|
||||||
|
-- amountInput <- inputElement $ def
|
||||||
|
-- & initialAttributes .~ ("class" =: "text-gray-500 w-full px-8 py-6 bg-transparent text-center text-xl" <> "placeholder" =: "1 ADA" <> "type" =: "number")
|
||||||
|
-- & inputElementConfig_initialValue .~ "10"
|
||||||
|
-- elClass "span" "mx-2 my-1" $ text "ADA"
|
||||||
|
-- pure $ fmap ((round :: Float -> Integer) . ada) . readMaybe . T.unpack <$> _inputElement_value amountInput
|
||||||
|
|
||||||
|
-- (sendButton, _) <- elClass' "button" "rounded mt-4 p-4 text-center w-full bg-gray-800 text-white font-bold" $ text "Send ADA"
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- txSendPayload = liftA2 (HeadSubmitTx "demo") <$> toAddr <*> lovelaceSendAmount
|
||||||
|
-- sendAda = fmapMaybe (preview _Just) $ current txSendPayload <@ domEvent Click sendButton
|
||||||
|
-- sendUrl = (fmap ("/submit-tx/"<>) <$> fromAddr)
|
||||||
|
-- sendReq = liftA2 postJson <$> sendUrl <*> txSendPayload
|
||||||
|
|
||||||
|
-- sendSuccess = fmapMaybe (preview _Just) $ (decodeText <=< _xhrResponse_responseText) <$> sendAdaResponse
|
||||||
|
|
||||||
|
-- latestTxFunc = liftA2 DemoTx <$> (Just <$> toName) <*> lovelaceSendAmount
|
||||||
|
|
||||||
|
-- -- Dynamic function to construct the last transaction
|
||||||
|
-- picoToLatestTx <- holdDyn Nothing $ attachPromptlyDynWith const latestTxFunc sendAda
|
||||||
|
|
||||||
|
-- sendAdaResponse <- fmap switchDyn $ prerender (pure never) $ do
|
||||||
|
-- performRequestAsync $ fmapMaybe (preview _Just) $ current sendReq <@ sendAda
|
||||||
|
|
||||||
|
-- pure $ attachWithMaybe (\mf a -> mf <*> Just a) (current picoToLatestTx) sendSuccess
|
||||||
|
|
||||||
|
-- FrontendRoute_PaymentChannel -> do
|
||||||
|
-- -- Page Title
|
||||||
|
-- elClass "div" "w-full flex flex-row items-baseline justify-between" $ do
|
||||||
|
-- elClass "div" "mb-2 font-semibold" $ text "Bob & Alice's Payment Channel"
|
||||||
|
-- routeLink (FrontendRoute_ClosingChannel :/ ()) $ elClass "button" "rounded mt-4 px-6 py-2 text-center bg-gray-800 text-white font-semibold" $ text "Close Payment Channel"
|
||||||
|
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "L1 Balance"
|
||||||
|
|
||||||
|
-- elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
-- balanceWidget "Bob" bobAddress never "l1-balance"
|
||||||
|
-- balanceWidget "Alice" aliceAddress never "l1-balance"
|
||||||
|
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- -- Balances
|
||||||
|
-- elClass "div" "text-2xl mb-2 font-semibold mt-8" $ do
|
||||||
|
-- text "Payment Channel Balance"
|
||||||
|
|
||||||
|
-- rec
|
||||||
|
-- (bobBalance, aliceBalance) <- elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
-- bb <- balanceWidget "Bob" bobAddress autoTx "head-balance"
|
||||||
|
-- ab <- balanceWidget "Alice" aliceAddress autoTx "head-balance"
|
||||||
|
-- pure (bb, ab)
|
||||||
|
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- -- Statistics
|
||||||
|
-- elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "Statistics"
|
||||||
|
|
||||||
|
-- elClass "div" "flex flex-row mt-4" $ do
|
||||||
|
-- elClass "div" "mr-16" $ do
|
||||||
|
-- elClass "div" "text-lg font-semibold" $ text "Total Transactions"
|
||||||
|
-- elClass "div" "text-3xl text-gray-600" $ dynText $ T.pack . show . Map.size <$> latestTxs
|
||||||
|
|
||||||
|
-- elClass "div" "mr-16" $ do
|
||||||
|
-- elClass "div" "text-lg font-semibold" $ text "Total Time"
|
||||||
|
-- elClass "div" "text-3xl text-gray-600" $ do
|
||||||
|
-- dynText $ showAsMs . Map.foldr ((+) . demoTx_time) 0 <$> latestTxs
|
||||||
|
-- text "ms"
|
||||||
|
|
||||||
|
-- -- Divider
|
||||||
|
-- elClass "div" "mt-2 w-full h-px bg-gray-200" blank
|
||||||
|
|
||||||
|
-- -- Action buttons: Send & Automate
|
||||||
|
-- (automateButton, _) <- elClass "div" "" $ do
|
||||||
|
-- routeLink (FrontendRoute_SendFunds :/ ()) $ elClass "button" "rounded mt-4 px-6 py-2 text-center bg-gray-800 text-white font-semibold mr-4" $ text "Send ADA"
|
||||||
|
-- elClass' "button" "rounded mt-4 px-6 py-2 text-center bg-gray-800 text-white font-semibold" $ dynText $ ffor automating $ \case
|
||||||
|
-- True -> "Automating"
|
||||||
|
-- False -> "Automate"
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- toggleAutomate = domEvent Click automateButton
|
||||||
|
|
||||||
|
-- automating <- foldDyn ($) False $ not <$ toggleAutomate
|
||||||
|
|
||||||
|
-- autoTxEv <- dyn $ ffor automating $ \case
|
||||||
|
-- False -> pure never
|
||||||
|
-- True -> fmap switchDyn $ prerender (pure never) $ do
|
||||||
|
-- tick <- tickLossyFromPostBuildTime 1
|
||||||
|
-- randomAmount :: Event t Lovelace <- performEvent $ (liftIO $ ada <$> randomRIO (1,10)) <$ tick
|
||||||
|
-- let
|
||||||
|
-- nextToAddr = join $ ffor2 bobBalance aliceBalance $ \bb ab ->
|
||||||
|
-- if bb > ab then aliceAddress else bobAddress
|
||||||
|
|
||||||
|
-- nextFromAddr = join $ ffor2 bobBalance aliceBalance $ \bb ab ->
|
||||||
|
-- if bb > ab then bobAddress else aliceAddress
|
||||||
|
|
||||||
|
-- sendUrl = (fmap ("/submit-tx/"<>) <$> nextFromAddr)
|
||||||
|
|
||||||
|
-- bobIsTo = ((==) <$> nextToAddr <*> bobAddress)
|
||||||
|
-- toName = ffor bobIsTo $ \case
|
||||||
|
-- True -> "Bob"
|
||||||
|
-- False -> "Alice"
|
||||||
|
|
||||||
|
-- changeDemoBuildTxFunc = attachWith DemoTx (current toName) randomAmount
|
||||||
|
|
||||||
|
-- submitPayload = attachWith (<*>) (current $ fmap (HeadSubmitTx "demo") <$> nextToAddr) $ Just <$> randomAmount
|
||||||
|
-- submitReq = attachWithMaybe (<*>) (current $ fmap postJson <$> sendUrl) submitPayload
|
||||||
|
|
||||||
|
-- picoToLatestTx <- holdDyn Nothing $ Just <$> changeDemoBuildTxFunc
|
||||||
|
-- sendAdaResponse <- performRequestAsync submitReq
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- sendSuccess = fmapMaybe (preview _Just) $ (decodeText <=< _xhrResponse_responseText) <$> sendAdaResponse
|
||||||
|
|
||||||
|
-- pure $ attachWithMaybe (<*>) (current picoToLatestTx) $ Just <$> sendSuccess
|
||||||
|
|
||||||
|
-- autoTx <- switchHold never autoTxEv
|
||||||
|
|
||||||
|
-- -- Transaction History
|
||||||
|
-- elClass "div" "text-2xl mb-2 font-semibold mt-8" $ text "History"
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- historyItem tx = do
|
||||||
|
-- elClass "div" "transition pointer-events-none duration-1000 px-4 py-2 rounded bg-white mb-4 drop-shadow " $ do
|
||||||
|
-- elClass "div" "flex flex-row justify-between items-center" $ do
|
||||||
|
-- elClass "div" "flex flex-col mr-16" $ do
|
||||||
|
-- elClass "div" "text-gray-600" $ do
|
||||||
|
-- text "To "
|
||||||
|
-- dynText $ demoTx_to <$> tx
|
||||||
|
-- elClass "div" "text-xl" $ do
|
||||||
|
-- dynText $ T.pack . printf "%.2f" . lovelaceToAda . demoTx_amount <$> tx
|
||||||
|
-- text "ADA"
|
||||||
|
|
||||||
|
-- elClass "div" "flex flex-row text-gray-400" $ do
|
||||||
|
-- elClass "div" "flex flex-col" $ do
|
||||||
|
-- elClass "div" "text-xs" $ text "Time"
|
||||||
|
-- elClass "div" "" $ do
|
||||||
|
-- dynText $ showAsMs . demoTx_time <$> tx
|
||||||
|
-- text "ms"
|
||||||
|
|
||||||
|
-- let
|
||||||
|
-- noHistory = Map.null <$> latestTxs
|
||||||
|
|
||||||
|
-- elDynClass "div" "overflow-y-scroll flex-grow" $ dyn_ $ ffor noHistory $ \case
|
||||||
|
-- True -> elClass "div" "text-gray-500" $ text "There is no history yet for this payment channel"
|
||||||
|
-- False -> do
|
||||||
|
-- _ <- elClass "div" "flex flex-col-reverse w-full" $ listWithKey latestTxs $ \_ tx -> historyItem tx
|
||||||
|
-- pure ()
|
||||||
|
|
||||||
|
-- pure autoTx
|
252
frontend/src/HydraPay/Api.hs
Normal file
252
frontend/src/HydraPay/Api.hs
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
|
module HydraPay.Api where
|
||||||
|
|
||||||
|
import Data.Map
|
||||||
|
import Data.Int
|
||||||
|
import GHC.Generics
|
||||||
|
import Data.Aeson as Aeson
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Control.Applicative((<|>))
|
||||||
|
|
||||||
|
import Control.Lens.TH
|
||||||
|
|
||||||
|
import Data.Fixed (Pico)
|
||||||
|
import Hydra.Types
|
||||||
|
import qualified HydraPay.Config as Config
|
||||||
|
|
||||||
|
type HeadName = T.Text
|
||||||
|
|
||||||
|
data HydraPayStats = HydraPayStats
|
||||||
|
{ _hydraPayStats_heads :: Integer
|
||||||
|
, _hydraPayStats_nodes :: Integer
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON HydraPayStats
|
||||||
|
instance FromJSON HydraPayStats
|
||||||
|
|
||||||
|
data HeadCreate = HeadCreate
|
||||||
|
{ headCreate_name :: HeadName
|
||||||
|
, headCreate_participants :: [Address]
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON HeadCreate
|
||||||
|
instance FromJSON HeadCreate
|
||||||
|
|
||||||
|
data HeadInit = HeadInit
|
||||||
|
{ headInit_name :: HeadName
|
||||||
|
, headInit_contestation :: Integer
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON HeadInit
|
||||||
|
instance FromJSON HeadInit
|
||||||
|
|
||||||
|
data HeadCommit = HeadCommit
|
||||||
|
{ headCommit_name :: HeadName
|
||||||
|
, headCommit_participant :: Address
|
||||||
|
, headCommit_amount :: Lovelace
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON HeadCommit
|
||||||
|
instance FromJSON HeadCommit
|
||||||
|
|
||||||
|
data HeadSubmitTx = HeadSubmitTx
|
||||||
|
{ headSubmitTx_name :: HeadName
|
||||||
|
, headSubmitTx_toAddr :: Address
|
||||||
|
, amount :: Lovelace
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON HeadSubmitTx
|
||||||
|
instance FromJSON HeadSubmitTx
|
||||||
|
|
||||||
|
-- This is the API json type that we need to send back out
|
||||||
|
data HeadStatus = HeadStatus
|
||||||
|
{ headStatus_name :: HeadName
|
||||||
|
, headStatus_running :: Bool
|
||||||
|
, headStatus_status :: Status
|
||||||
|
, headStatus_balances :: Map Address Lovelace
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON HeadStatus
|
||||||
|
instance FromJSON HeadStatus
|
||||||
|
|
||||||
|
|
||||||
|
data Tagged a = Tagged
|
||||||
|
{ tagged_id :: Int64
|
||||||
|
, tagged_payload :: a
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON a => ToJSON (Tagged a)
|
||||||
|
instance FromJSON a => FromJSON (Tagged a)
|
||||||
|
|
||||||
|
data HydraPayError
|
||||||
|
= InvalidPayload
|
||||||
|
| HeadCreationFailed
|
||||||
|
| NotEnoughParticipants
|
||||||
|
| HeadExists HeadName
|
||||||
|
| HeadDoesn'tExist
|
||||||
|
| NetworkIsn'tRunning
|
||||||
|
| FailedToBuildFundsTx
|
||||||
|
| NodeCommandFailed
|
||||||
|
-- ^ Anytime a command fails
|
||||||
|
| NotAParticipant
|
||||||
|
| ProcessError String
|
||||||
|
| NoValidUTXOToCommit
|
||||||
|
| InsufficientFunds
|
||||||
|
| FanoutNotPossible
|
||||||
|
| TxInvalid {utxo :: WholeUTXO, transaction :: Value, validationError :: ValidationError}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON HydraPayError
|
||||||
|
instance FromJSON HydraPayError
|
||||||
|
|
||||||
|
-- | State the head can be in, progressing linearly though the states.
|
||||||
|
data Status
|
||||||
|
= Status_Pending
|
||||||
|
| Status_Init
|
||||||
|
| Status_Committing
|
||||||
|
| Status_Open
|
||||||
|
| Status_Closed
|
||||||
|
| Status_Fanout
|
||||||
|
| Status_Finalized
|
||||||
|
deriving (Eq, Ord, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON Status
|
||||||
|
instance FromJSON Status
|
||||||
|
|
||||||
|
data ClientMsg
|
||||||
|
= ClientHello
|
||||||
|
| Authenticate T.Text
|
||||||
|
| DoesHeadExist T.Text
|
||||||
|
| CreateHead HeadCreate
|
||||||
|
| InitHead HeadInit
|
||||||
|
| CommitHead HeadCommit
|
||||||
|
| CloseHead HeadName
|
||||||
|
|
||||||
|
| TearDownHead HeadName
|
||||||
|
-- ^ Kills network and removes head
|
||||||
|
|
||||||
|
| CheckFuel Address
|
||||||
|
| Withdraw Address
|
||||||
|
| GetAddTx TxType Address Lovelace
|
||||||
|
|
||||||
|
| SubscribeTo HeadName
|
||||||
|
| SubmitHeadTx Address HeadSubmitTx
|
||||||
|
|
||||||
|
| RestartDevnet
|
||||||
|
| GetStats
|
||||||
|
|
||||||
|
| GetDevnetAddresses Integer -- Amount of addresses
|
||||||
|
|
||||||
|
| GetL1Balance Address
|
||||||
|
| GetHeadBalance HeadName Address
|
||||||
|
|
||||||
|
| LiveDocEzSubmitTx Tx Address
|
||||||
|
| GetIsManagedDevnet
|
||||||
|
| GetHydraPayMode
|
||||||
|
| GetProxyInfo Address
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON ClientMsg
|
||||||
|
instance FromJSON ClientMsg
|
||||||
|
|
||||||
|
type Version = T.Text
|
||||||
|
|
||||||
|
versionStr :: Version
|
||||||
|
versionStr = "0.1.0"
|
||||||
|
|
||||||
|
data ServerMsg
|
||||||
|
= ServerHello Version
|
||||||
|
| OperationSuccess
|
||||||
|
| HeadInfo HeadStatus
|
||||||
|
| TxConfirmed Pico
|
||||||
|
| FundsTx Tx
|
||||||
|
| FuelAmount Lovelace
|
||||||
|
| SubscriptionStarted HeadName
|
||||||
|
| AlreadySubscribed HeadName
|
||||||
|
| InvalidMessage T.Text
|
||||||
|
| UnhandledMessage
|
||||||
|
| HeadExistsResult Bool
|
||||||
|
| DevnetRestarted
|
||||||
|
| ServerError HydraPayError
|
||||||
|
| HeadStatusChanged HeadName Status (Map Address Lovelace)
|
||||||
|
| NodeMessage Value
|
||||||
|
| DevnetAddresses [Address]
|
||||||
|
| CurrentStats HydraPayStats
|
||||||
|
| RequestError T.Text
|
||||||
|
| NotAuthenticated
|
||||||
|
| AuthResult Bool
|
||||||
|
| L1Balance Lovelace
|
||||||
|
| HeadBalance Lovelace
|
||||||
|
| BalanceChange HeadName (Map Address Lovelace)
|
||||||
|
| HeadRemoved HeadName
|
||||||
|
| ApiError T.Text
|
||||||
|
| HydraPayMode Config.HydraPayMode
|
||||||
|
| IsManagedDevnet Bool
|
||||||
|
| ProxyAddressInfo ProxyInfo
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON ServerMsg
|
||||||
|
instance FromJSON ServerMsg
|
||||||
|
|
||||||
|
-- | Information about the managed proxy-address
|
||||||
|
-- for a specific address
|
||||||
|
data ProxyInfo = ProxyInfo
|
||||||
|
{ proxyInfo_address :: Address
|
||||||
|
, proxyInfo_proxyAddress :: Address
|
||||||
|
, proxyInfo_balance :: Lovelace
|
||||||
|
, proxyInfo_fuel :: Lovelace
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON ProxyInfo
|
||||||
|
instance FromJSON ProxyInfo
|
||||||
|
|
||||||
|
data ApiMsg
|
||||||
|
= TaggedMsg (Tagged ServerMsg)
|
||||||
|
| PlainMsg ServerMsg
|
||||||
|
|
||||||
|
instance FromJSON ApiMsg where
|
||||||
|
parseJSON v = (TaggedMsg <$> parseJSON v) <|> (PlainMsg <$> parseJSON v)
|
||||||
|
|
||||||
|
data Tx = Tx
|
||||||
|
{ txType :: T.Text
|
||||||
|
, txDescription :: T.Text
|
||||||
|
, txCborHex :: T.Text
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON Tx where
|
||||||
|
toJSON (Tx t d c) =
|
||||||
|
object [ "type" .= t
|
||||||
|
, "description" .= d
|
||||||
|
, "cborHex" .= c
|
||||||
|
]
|
||||||
|
|
||||||
|
instance FromJSON Tx where
|
||||||
|
parseJSON = withObject "Tx" $ \v -> Tx
|
||||||
|
<$> v .: "type"
|
||||||
|
<*> v .: "description"
|
||||||
|
<*> v .: "cborHex"
|
||||||
|
|
||||||
|
data TxType =
|
||||||
|
Funds | Fuel
|
||||||
|
deriving (Eq, Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON TxType
|
||||||
|
instance FromJSON TxType
|
||||||
|
|
||||||
|
isFuelType :: TxType -> Bool
|
||||||
|
isFuelType Fuel = True
|
||||||
|
isFuelType _ = False
|
||||||
|
|
||||||
|
makeLenses ''HydraPayStats
|
||||||
|
makePrisms ''ApiMsg
|
||||||
|
makePrisms ''ServerMsg
|
45
frontend/src/HydraPay/Config.hs
Normal file
45
frontend/src/HydraPay/Config.hs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
module HydraPay.Config where
|
||||||
|
|
||||||
|
import Data.Aeson.Types (FromJSON, ToJSON)
|
||||||
|
import GHC.Generics (Generic)
|
||||||
|
|
||||||
|
data HydraPayConfig = HydraPayConfig
|
||||||
|
{ _hydraPayMode :: HydraPayMode
|
||||||
|
, _port :: Int
|
||||||
|
, _bind :: String
|
||||||
|
}
|
||||||
|
deriving (Show,Read)
|
||||||
|
|
||||||
|
-- | Configure Cardano (L1) and Hydra networks. This is either an
|
||||||
|
-- explicit configuration for Cardano Node and Hydra Nodes, or the
|
||||||
|
-- default mode which runs a devnet for live documentation.
|
||||||
|
data HydraPayMode
|
||||||
|
= ManagedDevnetMode
|
||||||
|
| ConfiguredMode
|
||||||
|
{ _cardanoNodeParams :: CardanoNodeParams
|
||||||
|
, _hydraNodeParams :: HydraNodeParams
|
||||||
|
}
|
||||||
|
deriving (Eq,Show,Read,Generic)
|
||||||
|
|
||||||
|
instance ToJSON HydraPayMode
|
||||||
|
instance FromJSON HydraPayMode
|
||||||
|
|
||||||
|
data CardanoNodeParams = CardanoNodeParams
|
||||||
|
{ _testnetMagic :: Int
|
||||||
|
, _nodeSocket :: FilePath
|
||||||
|
, _ledgerGenesis :: FilePath
|
||||||
|
}
|
||||||
|
deriving (Eq,Show,Read,Generic)
|
||||||
|
|
||||||
|
instance ToJSON CardanoNodeParams
|
||||||
|
instance FromJSON CardanoNodeParams
|
||||||
|
|
||||||
|
data HydraNodeParams = HydraNodeParams
|
||||||
|
{ _hydraScriptsTxId :: String
|
||||||
|
, _hydraLedgerProtocolParameters :: FilePath
|
||||||
|
, _hydraLedgerGenesis :: FilePath
|
||||||
|
}
|
||||||
|
deriving (Eq,Show,Read,Generic)
|
||||||
|
|
||||||
|
instance ToJSON HydraNodeParams
|
||||||
|
instance FromJSON HydraNodeParams
|
5
release.nix
Normal file
5
release.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
let self = import ./. {};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit (self) exe;
|
||||||
|
}
|
3
static/main.css
Normal file
3
static/main.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
p {
|
||||||
|
color: red;
|
||||||
|
}
|
BIN
static/obelisk.jpg
Normal file
BIN
static/obelisk.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Loading…
Reference in New Issue
Block a user