Compare commits
5 Commits
e2db317b30
...
62b4aa0523
Author | SHA1 | Date |
---|---|---|
![]() |
62b4aa0523 | |
![]() |
194492234e | |
![]() |
712c7cac44 | |
![]() |
c3f39d430d | |
![]() |
b340cfd2f0 |
|
@ -1,6 +1,6 @@
|
||||||
.direnv/
|
.direnv/
|
||||||
|
|
||||||
_site
|
docs/
|
||||||
_cache
|
_cache
|
||||||
|
|
||||||
dist
|
dist
|
||||||
|
|
17
README.md
|
@ -1,16 +1,5 @@
|
||||||
|
# Kompact.io site
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
recompile css
|
Enter devshell, and run `menu` See flake for details.
|
||||||
```sh
|
|
||||||
tailwindcss -i ./content/css/main.css -o ./content/css/mini.css --minify
|
|
||||||
```
|
|
||||||
|
|
||||||
build, serve and watch
|
|
||||||
```sh
|
|
||||||
cabal run site -- watch
|
|
||||||
```
|
|
||||||
|
|
||||||
deploy
|
|
||||||
```sh
|
|
||||||
rsync -r --delete ./_site/* genesis:/var/www/kompactio-landing/
|
|
||||||
```
|
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
/* Set in tailwindconfig */
|
||||||
|
font-family: "jetbrains-mono";
|
||||||
|
src:
|
||||||
|
local("jetbrains-mono"),
|
||||||
|
url("/fonts/JetBrainsMono-Medium.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > :is(pre, p, h1, h2, h3, h4, h5, h6) {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section {
|
||||||
|
font-family:
|
||||||
|
"Lucida" Grande,
|
||||||
|
sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > :is(h1, h2, h3, h4, h5, h6, code) {
|
||||||
|
font-family: "jetbrains-mono";
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > blockquote {
|
||||||
|
padding: 1rem;
|
||||||
|
border-left-width: 4px;
|
||||||
|
border-color: rgb(239 68 68);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h1 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h1::before {
|
||||||
|
content: "# ";
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h2::before {
|
||||||
|
content: "## ";
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h3::before {
|
||||||
|
content: "### ";
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h4 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h4::before {
|
||||||
|
content: "#### ";
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section {
|
||||||
|
margin-top: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article a {
|
||||||
|
text-decoration-color: rgb(239 68 68);
|
||||||
|
text-decoration-thickness: 4px;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
transition-duration: 70ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
article a:hover {
|
||||||
|
text-decoration-thickness: 8px;
|
||||||
|
text-decoration-color: rgb(185 28 28);
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
article ul {
|
||||||
|
margin-left: 1rem;
|
||||||
|
list-style-type: "- ";
|
||||||
|
}
|
||||||
|
|
||||||
|
article ol {
|
||||||
|
margin-left: 1rem;
|
||||||
|
list-style: decimal inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes > ol > li {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes > ol > li > p {
|
||||||
|
display: inline;
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/* PrismJS 1.29.0
|
||||||
|
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+haskell+json+nix+racket+rust+scheme */
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: #000;
|
||||||
|
background: 0 0;
|
||||||
|
text-shadow: 0 1px #fff;
|
||||||
|
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
code[class*="language-"] ::-moz-selection,
|
||||||
|
code[class*="language-"]::-moz-selection,
|
||||||
|
pre[class*="language-"] ::-moz-selection,
|
||||||
|
pre[class*="language-"]::-moz-selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
code[class*="language-"] ::selection,
|
||||||
|
code[class*="language-"]::selection,
|
||||||
|
pre[class*="language-"] ::selection,
|
||||||
|
pre[class*="language-"]::selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
:not(pre) > code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background: #f5f2f0;
|
||||||
|
}
|
||||||
|
:not(pre) > code[class*="language-"] {
|
||||||
|
padding: 0.1em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
.token.cdata,
|
||||||
|
.token.comment,
|
||||||
|
.token.doctype,
|
||||||
|
.token.prolog {
|
||||||
|
color: #708090;
|
||||||
|
}
|
||||||
|
.token.punctuation {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.token.namespace {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.token.boolean,
|
||||||
|
.token.constant,
|
||||||
|
.token.deleted,
|
||||||
|
.token.number,
|
||||||
|
.token.property,
|
||||||
|
.token.symbol,
|
||||||
|
.token.tag {
|
||||||
|
color: #905;
|
||||||
|
}
|
||||||
|
.token.attr-name,
|
||||||
|
.token.builtin,
|
||||||
|
.token.char,
|
||||||
|
.token.inserted,
|
||||||
|
.token.selector,
|
||||||
|
.token.string {
|
||||||
|
color: #690;
|
||||||
|
}
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string,
|
||||||
|
.token.entity,
|
||||||
|
.token.operator,
|
||||||
|
.token.url {
|
||||||
|
color: #9a6e3a;
|
||||||
|
background: hsla(0, 0%, 100%, 0.5);
|
||||||
|
}
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword {
|
||||||
|
color: #07a;
|
||||||
|
}
|
||||||
|
.token.class-name,
|
||||||
|
.token.function {
|
||||||
|
color: #dd4a68;
|
||||||
|
}
|
||||||
|
.token.important,
|
||||||
|
.token.regex,
|
||||||
|
.token.variable {
|
||||||
|
color: #e90;
|
||||||
|
}
|
||||||
|
.token.bold,
|
||||||
|
.token.important {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 672 B After Width: | Height: | Size: 672 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
// Get all "navbar-burger" elements
|
||||||
|
var $navbarBurgers = Array.prototype.slice.call(
|
||||||
|
document.querySelectorAll(".navbar-burger"),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
// Check if there are any navbar burgers
|
||||||
|
if ($navbarBurgers.length > 0) {
|
||||||
|
// Add a click event on each of them
|
||||||
|
$navbarBurgers.forEach(function ($el) {
|
||||||
|
$el.addEventListener("click", function () {
|
||||||
|
// Get the "main-nav" element
|
||||||
|
var $target = document.getElementById("main-nav");
|
||||||
|
// Toggle the class on "main-nav"
|
||||||
|
$target.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -5,7 +5,8 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
/* Set in tailwindconfig */
|
/* Set in tailwindconfig */
|
||||||
font-family: "jetbrains-mono";
|
font-family: "jetbrains-mono";
|
||||||
src: local("jetbrains-mono"),
|
src:
|
||||||
|
local("jetbrains-mono"),
|
||||||
url("/fonts/JetBrainsMono-Medium.woff2") format("woff2");
|
url("/fonts/JetBrainsMono-Medium.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,37 +14,61 @@ article {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section> :is(p, h1, h2, h3, h4, h5, h6) {
|
article > section > :is(pre, p, h1, h2, h3, h4, h5, h6) {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h1 {
|
article > section {
|
||||||
|
font-family:
|
||||||
|
"Lucida" Grande,
|
||||||
|
sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > :is(h1, h2, h3, h4, h5, h6, code) {
|
||||||
|
font-family: "jetbrains-mono";
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > blockquote {
|
||||||
|
padding: 1rem;
|
||||||
|
border-left-width: 4px;
|
||||||
|
border-color: rgb(239 68 68);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h1 {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h1::before {
|
article > section > h1::before {
|
||||||
content: "# ";
|
content: "# ";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h2 {
|
article > section > h2 {
|
||||||
margin-top: 2rem;
|
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h2::before {
|
article > section > h2::before {
|
||||||
content: "## ";
|
content: "## ";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h3::before {
|
article > section > h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h3::before {
|
||||||
content: "### ";
|
content: "### ";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h4::before {
|
article > section > h4 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article > section > h4::before {
|
||||||
content: "#### ";
|
content: "#### ";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section {
|
article > section {
|
||||||
margin-top: 4rem;
|
margin-top: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +86,23 @@ article a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
article ul {
|
article ul {
|
||||||
margin-left: 2rem;
|
margin-left: 1rem;
|
||||||
list-style-type: "❯ ";
|
list-style-type: "- ";
|
||||||
|
}
|
||||||
|
|
||||||
|
article ol {
|
||||||
|
margin-left: 1rem;
|
||||||
|
list-style: decimal inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes > ol > li {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes > ol > li > p {
|
||||||
|
display: inline;
|
||||||
}
|
}
|
|
@ -1,275 +0,0 @@
|
||||||
Aims:
|
|
||||||
|
|
||||||
> Describe the pipeline and components getting from Aiken to Uplc.
|
|
||||||
|
|
||||||
## The Preface
|
|
||||||
|
|
||||||
### Motivations
|
|
||||||
|
|
||||||
The motivation for writing this came from a desire to add additional features to Aiken not yet available.
|
|
||||||
One such feature would evaluate an arbitrary function in Aiken callable from JavaScript.
|
|
||||||
This would help a lot with testing trying to align on and off-chain code.
|
|
||||||
|
|
||||||
Another more pipe dreamy, ad-hoc function extraction - from a span of code, generate a function.
|
|
||||||
A digression to answer _why would this be at all helpful?!_
|
|
||||||
Validator logic often needs a broad context throughout.
|
|
||||||
How then to best factor code?
|
|
||||||
Possible solutions:
|
|
||||||
|
|
||||||
1. Introduce types / structs
|
|
||||||
2. Have functions with lots of arguments
|
|
||||||
3. Don't
|
|
||||||
|
|
||||||
The problems are:
|
|
||||||
|
|
||||||
1. Requires relentless constructing and deconstructing across the function call.
|
|
||||||
And this is adds costs in Aiken.
|
|
||||||
2. Becomes tedious aligning the definition and function call.
|
|
||||||
3. End up with very long validators which are hard to unit test.
|
|
||||||
|
|
||||||
My current preferred way is to accept that validator functions are long.
|
|
||||||
Ad-hoc function extraction would allow for sections of code to be tested without needing to be factored out.
|
|
||||||
|
|
||||||
To do either of these, we need to get to grips with the Aiken compilation pipeline.
|
|
||||||
|
|
||||||
### This won't age well
|
|
||||||
|
|
||||||
Aiken is undergoing active development.
|
|
||||||
This post was started life with Aiken ~v1.14.
|
|
||||||
With Aiken v1.15, there were already reasonably significant changes to the compilation pipeline.
|
|
||||||
The word is that there aren't as big changes in the near future,
|
|
||||||
but this article will undoubtedly begin to diverge from the current code-base even before publishing.
|
|
||||||
|
|
||||||
### Limitations of narrating code
|
|
||||||
|
|
||||||
Narrating code becomes a compromise between being honest and accurate, and being readable and digestible.
|
|
||||||
Following the command `aiken build` covers well in excess of 10,000 LoC.
|
|
||||||
The writing of this post ground slowly to a halt as it progressed deeper into the code
|
|
||||||
with the details seeming to increase in importance.
|
|
||||||
At some point I had to draw a line and resign to fact that some parts will remain black boxes for now.
|
|
||||||
|
|
||||||
## Aiken build
|
|
||||||
|
|
||||||
Tracing `aiken build`, the pipeline is roughly:
|
|
||||||
```
|
|
||||||
. -> Project::read_source_files ->
|
|
||||||
Vec<Source> -> Project::parse_sources ->
|
|
||||||
ParsedModules -> Project::type_check ->
|
|
||||||
CheckedModules -> CodeGenerator::build ->
|
|
||||||
AirTree -> AirTree::to_vec ->
|
|
||||||
Vec<Air> -> CodeGenerator::uplc_code_gen ->
|
|
||||||
Program / Term<Name> -> serialize ->
|
|
||||||
.
|
|
||||||
```
|
|
||||||
We'll pick our way through these steps
|
|
||||||
|
|
||||||
At a high level we are trying to do something straightforward: reformulate Aiken code as Uplc.
|
|
||||||
Some Aiken expressions are relatively easy to handle for example an Aiken `Int` goes to an `Int` in Uplc.
|
|
||||||
Some Aiken expressions require more involved handling, for example an Aiken `If... If Else... Else `
|
|
||||||
must have the branches "nested" in Uplc.
|
|
||||||
Aiken also have lots of nice-to-haves like pattern matching, modules, and generics.
|
|
||||||
Uplc has none of these.
|
|
||||||
|
|
||||||
### The Preamble
|
|
||||||
|
|
||||||
#### Cli handling
|
|
||||||
|
|
||||||
The cli enters at `aiken/src/cmd/mod.rs` which parses the command.
|
|
||||||
With some establishing of context, the program enters `Project::build` (`crates/aiken-project/src/lib.rs`),
|
|
||||||
which in turn calls `Project::compile`.
|
|
||||||
|
|
||||||
#### File crawl
|
|
||||||
|
|
||||||
The program looks for Aiken files in both `./lib` and `./validator` sub-directories.
|
|
||||||
For each it walks over all contents (recursively) looking for `.ak` extensions.
|
|
||||||
It treats these two sets of files a little differently.
|
|
||||||
For example, only validator files can contain the special validator functions.
|
|
||||||
|
|
||||||
#### Parse and Type check
|
|
||||||
|
|
||||||
`Project::parse_sources` parses the module source code.
|
|
||||||
The heavy lifting is done by `aiken_lang::parser::module`, which is evaluated on each file.
|
|
||||||
It produces a `Module` containing a list of parsed definitions of the file: functions, types _etc_,
|
|
||||||
together with metadata like docstrings and the file path.
|
|
||||||
|
|
||||||
`Project::type_check` inspects the parsed modules and, as the name implies, checks the types.
|
|
||||||
It flags type level warnings and errors and constructs a hash map of `CheckedModule`s.
|
|
||||||
|
|
||||||
#### Code generator
|
|
||||||
|
|
||||||
The code generator `CodeGenerator` (`aiken-lang/src/gen_uplc.rs`) is given
|
|
||||||
the definitions found from the previous step,
|
|
||||||
together with the plutus builtins.
|
|
||||||
It has additional fields for things like debugging.
|
|
||||||
|
|
||||||
This is handed over to a `Blueprint` (`aiken-project/src/blueprint/mod.rs`).
|
|
||||||
The blueprint does little more than find the validators on which to run the code gen.
|
|
||||||
The heavy lifting is done by `CodeGenerator::generate`.
|
|
||||||
|
|
||||||
We are now ready to take the source code and create plutus.
|
|
||||||
|
|
||||||
### In the air
|
|
||||||
|
|
||||||
Things become a bit intimidating at this point in terms of sheer lines of code:
|
|
||||||
`gen_uplc.rs` and three modules in `gen_uplc/` totals > 8500 LoC.
|
|
||||||
|
|
||||||
Aiken has its own _intermediate representation_ called `air` (as in Aiken Intermediate Representation).
|
|
||||||
These are common in compiled languages.
|
|
||||||
`Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
|
||||||
Unsurprisingly, it looks little bit like a language between Aiken and plutus.
|
|
||||||
|
|
||||||
In fact, Aiken has another intermediate representation: `AirTree`.
|
|
||||||
This is constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and air.
|
|
||||||
|
|
||||||
#### Climbing the AirTree
|
|
||||||
|
|
||||||
Within `CodeGenerator::generate`, `CodeGenerator::build` is called on the function body.
|
|
||||||
This takes a `TypedExpr` and constructs and returns an `AirTree`.
|
|
||||||
The construction is recursive as it traverses the recursive `TypedExpr` data structure.
|
|
||||||
More on what an airtree is and its construction below.
|
|
||||||
At the same time `self` is treated as `mut`, so we need to keep an eye on this too.
|
|
||||||
The method which is called and uses this mutability of self is `self.assignment`.
|
|
||||||
It does so by
|
|
||||||
```sample
|
|
||||||
self.assignment >> self.expect_type_assign >> self.code_gen_functions.insert
|
|
||||||
```
|
|
||||||
and thus is creating a hashmap of all the functions that appear in the definition.
|
|
||||||
From the call to return of `assign` covers > 600 LoC so we'll leave this as otherwise a black box.
|
|
||||||
(`self.handle_each_clause` is also called with `mut` which in turn calls `self.build` for which `mut` it is needed.)
|
|
||||||
|
|
||||||
Validators in Aiken are boolean functions while in Uplc they are unit-valued (aka void-valued) functions.
|
|
||||||
Thus the air tree is wrapped such that `false` results in an error (`wrap_validator_condition`).
|
|
||||||
I don't know why there is a prevailing thought that boolean functions are preferable than functions
|
|
||||||
that error if anything is wrong - which is what validators are.
|
|
||||||
|
|
||||||
`check_validator_args` again extends the airtree from the previous step,
|
|
||||||
and again calls `self.assignment` mutating self.
|
|
||||||
Something interesting is happening here.
|
|
||||||
Script context is the final argument of a validator - for any script purpose.
|
|
||||||
`check_validator_args` treats the script context like it is an unused argument.
|
|
||||||
The importance of this is not immediate, and I've still yet to appreciate why this happens.
|
|
||||||
|
|
||||||
Let's take a look at what AirTree actually is
|
|
||||||
```rust
|
|
||||||
pub enum AirTree {
|
|
||||||
Statement {
|
|
||||||
statement: AirStatement,
|
|
||||||
hoisted_over: Option<Box<AirTree>>,
|
|
||||||
},
|
|
||||||
Expression(AirExpression),
|
|
||||||
UnhoistedSequence(Vec<AirTree>),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Note that `AirStatement` and `AirExpression` are mutually recursive definitions with `AirTree`.
|
|
||||||
Otherwise, it would be unclear from first inspection how tree-like this really is.
|
|
||||||
|
|
||||||
`AirExpression` has multiple constructors. These include (non-exhaustive)
|
|
||||||
|
|
||||||
- air primitives (including all the ones that appear in plutus)
|
|
||||||
- constructors `Call` and `Fn` to handle anonymous functions
|
|
||||||
- binary and unary operators
|
|
||||||
- handling when and if
|
|
||||||
- handling error and tracing
|
|
||||||
|
|
||||||
`AirStatement` also has multiple constructors. These include
|
|
||||||
|
|
||||||
- let assignments and named function definitions
|
|
||||||
- handling expect assignments
|
|
||||||
- pattern matching
|
|
||||||
- unwrapping data structures
|
|
||||||
|
|
||||||
Note that `AirTree` has many methods that are partial functions,
|
|
||||||
as in there are possible states that are not considered legitimate
|
|
||||||
at different points of its construction and use.
|
|
||||||
For example `hoist_over` will throw an error if called on an `Expression`.
|
|
||||||
As `AirTree` is for internal use only, the scope for potential problems is reasonably contained.
|
|
||||||
It seems likely this is to avoid similar-yet-different IRs between steps.
|
|
||||||
However, the trade off is that it partially obfuscates what is a valid state where.
|
|
||||||
|
|
||||||
What is hoisting? hoisting gives the airtree depth.
|
|
||||||
The motivation is that by the time we hit Uplc it is "generally better"
|
|
||||||
that
|
|
||||||
|
|
||||||
- function definitions appear once rather than being inlined multiple times
|
|
||||||
- the definition appears as close to use as possible
|
|
||||||
|
|
||||||
Hoisting creates tree paths.
|
|
||||||
The final airtree to airtree step is`self.hoist_functions_to_validator` traverses the paths.
|
|
||||||
There is a lot of mutating of self, making it quite hard to keep a handle on things.
|
|
||||||
In all this (several thousand?) LoC, it is essentially ascertaining in which node of the tree
|
|
||||||
to insert each function definition.
|
|
||||||
In a resource constrained environment like plutus, this effort is warranted.
|
|
||||||
|
|
||||||
At the same time this function deals with
|
|
||||||
|
|
||||||
- monomophisation - no more generics
|
|
||||||
- erasing opaque types
|
|
||||||
|
|
||||||
Neither of which exist at the Uplc level.
|
|
||||||
|
|
||||||
#### Into Air
|
|
||||||
|
|
||||||
The `to_vec : AirTree -> Vec<Air>` is much easier to digest.
|
|
||||||
For one, it is not evaluated in the context of the code generator,
|
|
||||||
and two, there is no mutation of the airtree.
|
|
||||||
The function recursively takes nodes of the tree and maps them to entries in a mutable vector.
|
|
||||||
It flattens the tree to a vec.
|
|
||||||
|
|
||||||
### Down to Uplc
|
|
||||||
|
|
||||||
Next we go from `Vec<Air> -> Term<Name>`.
|
|
||||||
This step is a little more involved than the previous.
|
|
||||||
For one, this is executed in the context of the code generator.
|
|
||||||
Moreover, the code generator is treated mutable - ouch.
|
|
||||||
|
|
||||||
On further inspection we see that the only mutation is setting `self.needs_field_access = true`.
|
|
||||||
This flag informs the compiler that, if true, additional terms must be added in one of the final steps
|
|
||||||
(see `CodeGenerator::finalize`).
|
|
||||||
|
|
||||||
As noted above, some of the mappings from air to terms are immediate like `Air::Bool -> Term::bool`.
|
|
||||||
Others are less so.
|
|
||||||
Some examples:
|
|
||||||
|
|
||||||
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
|
||||||
- Lists in air have no immediate analogue in uplc
|
|
||||||
- builtins, as in built-in functions (standard shorthand), have to mediated
|
|
||||||
with some combination of `force` and `delay` in order to behave as they should.
|
|
||||||
- user functions must be "uncurried", ie treated as a sequence of single argument functions,
|
|
||||||
and recursion must be handled
|
|
||||||
- Do some magic in order to efficiently allow "record updates".
|
|
||||||
|
|
||||||
#### Cranking the Optimizer
|
|
||||||
|
|
||||||
There is a sequence of operations performed on the Uplc mapping `Term<Name> -> Term<Name>`.
|
|
||||||
These remove inconsequential parts of the logic which will appear.
|
|
||||||
These include:
|
|
||||||
|
|
||||||
- removing application of the identity function
|
|
||||||
- directly substituting where apply lambda is applied to a constant or builtin
|
|
||||||
- inline or simplify where apply lambda is applied to a parameter that appears once or not at all
|
|
||||||
|
|
||||||
Each of these optimizing methods has a its own relatively narrow focus,
|
|
||||||
and so although there is a fair number of LoC, it's reasonably straightforward to follow.
|
|
||||||
Some are applied multiple times.
|
|
||||||
|
|
||||||
### The End
|
|
||||||
|
|
||||||
The generated program can now be serialized and included in the blueprint.
|
|
||||||
|
|
||||||
### Plutus Core Signposting
|
|
||||||
|
|
||||||
All this fuss is to get us to a point where we can write Uplc - and good Uplc at that.
|
|
||||||
Note that there's many ways to generate code and most of them are bad.
|
|
||||||
The various design decisions and compilation steps make more sense
|
|
||||||
when we have a better understanding of the target language.
|
|
||||||
|
|
||||||
Uplc is a lambda calculus.
|
|
||||||
For a comprehensive definition on Uplc checkout the specification found
|
|
||||||
[here](https://github.com/input-output-hk/plutus/#specifications-and-design) from the plutus GitHub repo.
|
|
||||||
(I imagine this link will be maintained longer than the current actual link.)
|
|
||||||
If you're not at all familiar with lambda calculus I recommend
|
|
||||||
[an unpacking](https://crypto.stanford.edu/~blynn/lambda/) by Ben Lynn.
|
|
||||||
|
|
||||||
### What next?
|
|
||||||
|
|
||||||
I think it would be helpful to have some examples... Watch this space.
|
|
|
@ -1,3 +1,5 @@
|
||||||
---
|
---
|
||||||
title: Kompact.io
|
title: Kompact.io
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Hero
|
||||||
|
|
|
@ -4,73 +4,73 @@ date: 2023-08-07
|
||||||
---
|
---
|
||||||
|
|
||||||
Not so long ago Emurgo announced they were doing a Cardano centered hackathon.
|
Not so long ago Emurgo announced they were doing a Cardano centered hackathon.
|
||||||
It was a welcome prospect - very few similar such events seem to exist in the space.
|
It was a welcome prospect - very few similar such events seem to exist in the
|
||||||
Things went monotonically south ever since the announcement, but that's a different story.
|
space. Things went monotonically south ever since the announcement, but that's a
|
||||||
|
different story.
|
||||||
|
|
||||||
One particularly interesting quirk was that of the three "tracks" of the hackathon,
|
One particularly interesting quirk was that of the three "tracks" of the
|
||||||
one was _Zero Knowledge_ (aka zk).
|
hackathon, one was _Zero Knowledge_ (aka zk). Why particularly interesting
|
||||||
Why particularly interesting quirk? In some sense it is not surprising:
|
quirk? In some sense it is not surprising: zk has been very trendy these last
|
||||||
zk has been very trendy these last few years around blockchains.
|
few years around blockchains. However, building on Cardano is notoriously
|
||||||
However, building on Cardano is notoriously challenging.
|
challenging. Building with zk on a zk-native blockchain is itself a very steep
|
||||||
Building with zk on a zk-native blockchain is itself a very steep learning curve.
|
learning curve. So combining the two, zk on Cardano seemed... a bit mad.
|
||||||
So combining the two, zk on Cardano seemed... a bit mad.
|
|
||||||
|
|
||||||
This post is borne out of a best effort of how far "zk on Cardano" can be pushed.
|
This post is borne out of a best effort of how far "zk on Cardano" can be
|
||||||
|
pushed.
|
||||||
|
|
||||||
## What is zk?
|
## What is zk?
|
||||||
|
|
||||||
There is no shortage of explanations describing what zk is
|
There is no shortage of explanations describing what zk is ( _eg_
|
||||||
( _eg_ [by Vitalik](https://vitalik.ca/general/2021/01/26/snarks.html){target="_blank"} or
|
[by Vitalik](https://vitalik.ca/general/2021/01/26/snarks.html){target="\_blank"}
|
||||||
[a full mooc](https://zk-learning.org/){target="_blank"} ).
|
or [a full mooc](https://zk-learning.org/){target="\_blank"} ). There is also a
|
||||||
There is also a reasonable breath to the field of zk that includes things like distributed compute.
|
reasonable breath to the field of zk that includes things like distributed
|
||||||
Zk involves some really neat maths that lets you do some seemingly magical feats
|
compute. Zk involves some really neat maths that lets you do some seemingly
|
||||||
and pairs well with blockchain in extending what is functionally possible.
|
magical feats and pairs well with blockchain in extending what is functionally
|
||||||
Let's stick to a simple and prototypical example.
|
possible. Let's stick to a simple and prototypical example.
|
||||||
|
|
||||||
Suppose Alice and Bob are playing battleships.
|
Suppose Alice and Bob are playing battleships. The game begins with Alice and
|
||||||
The game begins with Alice and Bob placing their ships within their own coordinate grid.
|
Bob placing their ships within their own coordinate grid. They then take turns
|
||||||
They then take turns picking coordinates to "strike".
|
picking coordinates to "strike". If they hit nothing then their turn ends, but
|
||||||
If they hit nothing then their turn ends, but if they hit a ship then they strike again.
|
if they hit a ship then they strike again. The winner is the first to strike all
|
||||||
The winner is the first to strike all coordinates containing their opponent's ships.
|
coordinates containing their opponent's ships.
|
||||||
|
|
||||||
Alice knows Bob as being a notorious liar; how can she enjoy the game?
|
Alice knows Bob as being a notorious liar; how can she enjoy the game?
|
||||||
Each guess she makes, Bob gleefully shouts "Miss!".
|
Each guess she makes, Bob gleefully shouts "Miss!". She can't ask Bob to show
|
||||||
She can't ask Bob to show he's not lying by revealing the actual locations of the ships.
|
he's not lying by revealing the actual locations of the ships. She could ask
|
||||||
She could ask Charlie to independently verify Bob's not lying,
|
Charlie to independently verify Bob's not lying, but then what if Charlie is
|
||||||
but then what if Charlie is actually on team Bob and also lies.
|
actually on team Bob and also lies. Or Bob might suspect Charlie is actually on
|
||||||
Or Bob might suspect Charlie is actually on team Alice, slyly brought in to give Alice some hints.
|
team Alice, slyly brought in to give Alice some hints.
|
||||||
|
|
||||||
Is there a way that Bob can prove to Alice that each guess is a miss,
|
Is there a way that Bob can prove to Alice that each guess is a miss, but
|
||||||
but without revealing the locations of the ships either to Alice or anyone else?
|
without revealing the locations of the ships either to Alice or anyone else?
|
||||||
|
|
||||||
The answer is yes.
|
The answer is yes. Using zk Bob can produce a proof each time Alice's guess
|
||||||
Using zk Bob can produce a proof each time Alice's guess misses if and only if it honestly does.
|
misses if and only if it honestly does. Alice can inspect each proof and verify
|
||||||
Alice can inspect each proof and verify Bob's response.
|
Bob's response. Alice can interrogate the proof as much as she wants, but she
|
||||||
Alice can interrogate the proof as much as she wants, but she won't learn anything more than
|
won't learn anything more than her guess was a miss.
|
||||||
her guess was a miss.
|
|
||||||
|
|
||||||
There are a multitude of different ways to do this,
|
There are a multitude of different ways to do this, but essentially it involves
|
||||||
but essentially it involves modeling the problem as a bunch of algebra
|
modeling the problem as a bunch of algebra over finite fields - like a lot of
|
||||||
over finite fields - like a lot of cryptography.
|
cryptography.
|
||||||
|
|
||||||
What's the _snark_ of zk-snark?
|
|
||||||
Snark stands for _Succinct Non-Interactive Argument of Knowledge_.
|
|
||||||
And without saying anything more, it means that Alice has to do way less algebra than Bob.
|
|
||||||
In applications this is important because Bob might not be able to lie anymore but he could still waste Alice's time.
|
|
||||||
|
|
||||||
|
What's the _snark_ of zk-snark? Snark stands for _Succinct Non-Interactive
|
||||||
|
Argument of Knowledge_. And without saying anything more, it means that Alice
|
||||||
|
has to do way less algebra than Bob. In applications this is important because
|
||||||
|
Bob might not be able to lie anymore but he could still waste Alice's time.
|
||||||
|
|
||||||
## Sudoku snark
|
## Sudoku snark
|
||||||
|
|
||||||
Sudoku snark was the entrant to Emurgo's hackathon.
|
Sudoku snark was the entrant to Emurgo's hackathon. The summary-pitch-story deck
|
||||||
The summary-pitch-story deck is [here](https://pub.kompact.io/sudoku-snark){target="_blank"}.
|
is [here](https://pub.kompact.io/sudoku-snark){target="\_blank"}. Links to the
|
||||||
Links to the associated repos: [plutus-zk](https://github.com/waalge/plutus-zk){target="_blank"}
|
associated repos:
|
||||||
and [sudoku-snark](https://github.com/waalge/sudoku-snark){target="_blank"}.
|
[plutus-zk](https://github.com/waalge/plutus-zk){target="\_blank"} and
|
||||||
|
[sudoku-snark](https://github.com/waalge/sudoku-snark){target="\_blank"}.
|
||||||
|
|
||||||
Just after the hackathon got underway there was a
|
Just after the hackathon got underway there was a
|
||||||
[large PR merged](https://github.com/input-output-hk/plutus/pull/5231){target="_blank"}
|
[large PR merged](https://github.com/input-output-hk/plutus/pull/5231){target="\_blank"}
|
||||||
into the main branch of plutus.
|
into the main branch of plutus. It's a mammoth culmination of many many months
|
||||||
It's a mammoth culmination of many many months of work.
|
of work. In it were some fundamental primitives needed for running zk
|
||||||
In it were some fundamental primitives needed for running zk algorithms.
|
algorithms.
|
||||||
|
|
||||||
The idea of the project was as follows:
|
The idea of the project was as follows:
|
||||||
|
|
||||||
|
@ -80,48 +80,54 @@ The idea of the project was as follows:
|
||||||
- wrap up in a gui
|
- wrap up in a gui
|
||||||
|
|
||||||
Unsurprisingly to anyone who's hung around the Cardano ecosystem long enough,
|
Unsurprisingly to anyone who's hung around the Cardano ecosystem long enough,
|
||||||
this third part is where things got stuck.
|
this third part is where things got stuck. We did get as far as running a
|
||||||
We did get as far as running a cluster of nodes in the Conway era with the latest version of plutus
|
cluster of nodes in the Conway era with the latest version of plutus but
|
||||||
but unrelated changes seemed to thwart any chance of building transactions here.
|
unrelated changes seemed to thwart any chance of building transactions here.
|
||||||
|
|
||||||
A quick shout-out to the [modulo-p.io](https://modulo-p.io/){target="_blank"} team.
|
A quick shout-out to the [modulo-p.io](https://modulo-p.io/){target="\_blank"}
|
||||||
They had a different approach and managed to implement a zk algorithm with the existing plutus primitives.
|
team. They had a different approach and managed to implement a zk algorithm with
|
||||||
This spared the need to play the foolhardy dependency bumping game with the Cardano node.
|
the existing plutus primitives. This spared the need to play the foolhardy
|
||||||
However, because zk is so arithmetically intense,
|
dependency bumping game with the Cardano node. However, because zk is so
|
||||||
the app wont run outside a hydra head and with very generous max unit budgets (afaics).
|
arithmetically intense, the app wont run outside a hydra head and with very
|
||||||
This approach won't be necessary when we have the new version of plutus available.
|
generous max unit budgets (afaics). This approach won't be necessary when we
|
||||||
Nonetheless, it's very neat to see it done and they packaged it very nicely.
|
have the new version of plutus available. Nonetheless, it's very neat to see it
|
||||||
|
done and they packaged it very nicely.
|
||||||
|
|
||||||
The validator in Sudoku snark uses [groth16](https://eprint.iacr.org/2016/260.pdf).
|
The validator in Sudoku snark uses
|
||||||
In part because this was already mostly available from the plutus repo itself.
|
[groth16](https://eprint.iacr.org/2016/260.pdf). In part because this was
|
||||||
It is also the most obvious candidate to begin with.
|
already mostly available from the plutus repo itself. It is also the most
|
||||||
It's relatively mature, relatively simple, can be implemented from the new primitives,
|
obvious candidate to begin with. It's relatively mature, relatively simple, can
|
||||||
and importantly in Cardano land has small proof size.
|
be implemented from the new primitives, and importantly in Cardano land has
|
||||||
(As far as I know, the smallest of comparable algorithms.)
|
small proof size. (As far as I know, the smallest of comparable algorithms.)
|
||||||
|
|
||||||
The program to generate the setup and proofs uses the Arkworks framework.
|
The program to generate the setup and proofs uses the Arkworks framework. Again
|
||||||
Again this choice was initially inspired by a script from the IOG team,
|
this choice was initially inspired by a script from the IOG team, but again it
|
||||||
but again it seems like a smart choice.
|
seems like a smart choice. Arkworks is a well conceived, highly modular
|
||||||
Arkworks is a well conceived, highly modular framework for zk,
|
framework for zk, which makes it easy to pull in the bits we need to perform our
|
||||||
which makes it easy to pull in the bits we need to perform our off-chain logic.
|
off-chain logic.
|
||||||
|
|
||||||
The choice of game, sudoku, was in turn inspired by an arkworks example.
|
The choice of game, sudoku, was in turn inspired by an arkworks example. It's
|
||||||
It's not the most compelling of choices, but it's simple and it did for now.
|
not the most compelling of choices, but it's simple and it did for now.
|
||||||
Battleships would have been more compelling or mastermind as the modulo-p team used.
|
Battleships would have been more compelling or mastermind as the modulo-p team
|
||||||
|
used.
|
||||||
|
|
||||||
The intended game play involved locking Ada at a utxo correspondinig to a sudoku puzzle,
|
The intended game play involved locking Ada at a utxo correspondinig to a sudoku
|
||||||
and spendable only if a player could provide proof they knew the solution.
|
puzzle, and spendable only if a player could provide proof they knew the
|
||||||
Through the magic of zk they'd not disclose to the other competitors the solution itself.
|
solution. Through the magic of zk they'd not disclose to the other competitors
|
||||||
Other details were TBC: is it first and second prizes? are players whitelisted? _etc_.
|
the solution itself. Other details were TBC: is it first and second prizes? are
|
||||||
|
players whitelisted? _etc_.
|
||||||
|
|
||||||
## So are we zk-Cardano yet?
|
## So are we zk-Cardano yet?
|
||||||
|
|
||||||
We're close.
|
We're close.
|
||||||
|
|
||||||
There is potentially still quite a while before these new primitives in plutus reach mainnet.
|
There is potentially still quite a while before these new primitives in plutus
|
||||||
The word on the street is that it might happen before the end of 2023.
|
reach mainnet. The word on the street is that it might happen before the end
|
||||||
|
of 2023.
|
||||||
|
|
||||||
Even sooner, there will be versions of the Cardano node available with the new primitives,
|
Even sooner, there will be versions of the Cardano node available with the new
|
||||||
and so possibly plumb-able into hydra without causing oneself an aneurysm.
|
primitives, and so possibly plumb-able into hydra without causing oneself an
|
||||||
|
aneurysm.
|
||||||
|
|
||||||
In development time that's not so long: we can start thinking about what to build with zk on Cardano.
|
In development time that's not so long: we can start thinking about what to
|
||||||
|
build with zk on Cardano.
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
---
|
|
||||||
title: Are we zk-Cardano yet?
|
|
||||||
date: 2023-08-07
|
|
||||||
---
|
|
||||||
|
|
||||||
Not so long ago Emurgo announced they were doing a Cardano centered hackathon.
|
|
||||||
It was a welcome prospect - very few similar such events seem to exist in the space.
|
|
||||||
Things went monotonically south ever since the announcement, but that's a different story.
|
|
||||||
|
|
||||||
One particularly interesting quirk was that of the three "tracks" of the hackathon,
|
|
||||||
one was _Zero Knowledge_ (aka zk).
|
|
||||||
Why particularly interesting quirk? In some sense it is not suprising:
|
|
||||||
zK has been very trendy these last few years around blockchains.
|
|
||||||
However, building on Cardano is notoriously challenging.
|
|
||||||
Building with zk on a zk-native blockchain is itself a very steep learning curve.
|
|
||||||
So combining the two, zk on Cardano seemed... a bit mad.
|
|
||||||
|
|
||||||
This post is bourne out of a best effort of how far "zk on cardano" can be pushed.
|
|
||||||
|
|
||||||
## What is zk?
|
|
||||||
|
|
||||||
There is no shortage of explanations describing what zk is [TODO: Links].
|
|
||||||
There is also a reasonable breath to the field of zk that includes things like distributed compute.
|
|
||||||
Zk involves some really neat maths that lets you do some seemingly magical feats,
|
|
||||||
and pairs well with blockchain in extending what is functionally possible.
|
|
||||||
Let's stick to a simple and prototypical example.
|
|
||||||
|
|
||||||
Suppose Alice and Bob are playing battleships.
|
|
||||||
The game begins with Alice and Bob placing their ships within their own coordinate grid.
|
|
||||||
They then take in terms picking coordinates to "bomb".
|
|
||||||
If they hit nothing, then their turn ends, but if they hit a ship then they guess again.
|
|
||||||
The winner is the first to sink all their oponents ships.
|
|
||||||
|
|
||||||
Alice knows Bob has a reputation of being a notorious liar; how can she enjoy the game?
|
|
||||||
Each guess she makes, Bob says gleefully shouts "Miss!".
|
|
||||||
She can't ask Bob to show he's not lying by revealing the actual locations of the ships.
|
|
||||||
She could ask Charlie to independently verify Bob's not lying,
|
|
||||||
but then what if Charlie is actually on team Bob and also lies.
|
|
||||||
Or Bob might suspect Charlie is actually on team Alice, slyly brought in to give could Alice some hints.
|
|
||||||
|
|
||||||
Is there a way that Bob can prove to Alice that each guess is a miss,
|
|
||||||
but without revealing the locations of the ships either to Alice or anyone else?
|
|
||||||
|
|
||||||
The answer is yes.
|
|
||||||
Using zk Bob can produce a proof each time Alice's guess misses if and only if it honestly does.
|
|
||||||
Alice can inspect each proof and verify Bob's response.
|
|
||||||
Alice can interogate the proof as much as she wants, but she won't learn anything more than
|
|
||||||
her guess was a miss.
|
|
||||||
|
|
||||||
There are multiplitude of different ways to do this,
|
|
||||||
but essentially it involves modelling the problem as a bunch of algebra
|
|
||||||
over finite fields - like a lot of cryptography.
|
|
||||||
|
|
||||||
What's the snark of zk-snark?
|
|
||||||
Snark stands for _Succinct Non-Interactive Argument of Knowledge_.
|
|
||||||
And without saying anything more: it means that Alice has to do way less algebra than Bob.
|
|
||||||
In applications this is important, because Bob might not be able to lie anymore,
|
|
||||||
but he could still waste Alice's time.
|
|
||||||
|
|
||||||
|
|
||||||
## Sudoku snark
|
|
||||||
|
|
||||||
Sudoku snark was the entrant to Emurgo hackathon.
|
|
||||||
The summary/ pitch/ story deck is [here](https://pub.kompact.io/sudoku-snark).
|
|
||||||
Links to associated repos [plutus-zk]() and [sudoku-snark]().
|
|
||||||
|
|
||||||
Just after the hackathon got underway there was a large PR merged into the main branch of plutus.
|
|
||||||
It's a mammoth PR that is the culmination of many many months of work.
|
|
||||||
In it were some fundamental primitives needed for running zk algos.
|
|
||||||
|
|
||||||
The idea of the project was as follows:
|
|
||||||
|
|
||||||
- write a validator implementing a zk algorithm with the new primitives
|
|
||||||
- write a programme to generate the setup and proofs
|
|
||||||
- try to get a version of hydra running this newest version of plutus.
|
|
||||||
|
|
||||||
Unsurprisingly to anyone who's hung around the Cardano repos long enough,
|
|
||||||
this final part is where things got stuck.
|
|
||||||
Things got as far as running a cluster of nodes in the conway era supposedly with the latest plutus
|
|
||||||
but some unrelated changes seemed to thwart any chance of building transactions.
|
|
||||||
|
|
||||||
The validator uses [groth16].
|
|
||||||
In part because this was already mostly available from the plutus repo itself.
|
|
||||||
It is also the most obvious candidate to begin with.
|
|
||||||
It's relatively mature, relatively simple, can be implemented from the new primitives,
|
|
||||||
and, importantly in cardano land, has small proof size.
|
|
||||||
(As far as I know, the smallest of comparable algos.)
|
|
||||||
|
|
||||||
The program to generate the setup and proofs uses the arkworks framework.
|
|
||||||
Again this was initially inspired by a script from the IOG team.
|
|
||||||
|
|
||||||
The choice of game, sudoku, was in turn inspired by an arkworks example.
|
|
||||||
It's not the most compelling of choices, but it did for now.
|
|
||||||
|
|
||||||
The intended game play involved locking ada at a utxo
|
|
||||||
spendable only if a player could provide proof you knew the solution.
|
|
||||||
And through the magic of zk, not disclosing to the competition the solution itself.
|
|
||||||
Other details were TBC: is it first and second prizes? are players whitelisted?
|
|
||||||
|
|
||||||
## So are we zk-Cardano yet?
|
|
||||||
|
|
||||||
We're close.
|
|
||||||
|
|
||||||
There is potentially still quite a stretch between being in the plutus repo and being run on-chain.
|
|
||||||
The word on the street is that it might happen before the end of 2023.
|
|
||||||
|
|
||||||
Before it's available on mainnet there will be versions the cardano node available,
|
|
||||||
and so possibly plumbable into hydra without causing oneself an aneurysm.
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
---
|
||||||
|
title: "Hydra is cool: You don't need Hydra"
|
||||||
|
date: 2023-09-20
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hydra is cool
|
||||||
|
|
||||||
|
Hydra[^1] is a very cool project. It is a layer 2 for Cardano that is
|
||||||
|
_isomorphic_ to the L1. Here isomorphic means that Plutus runs in Hydra just
|
||||||
|
like it does on the L1. That dapp you've just toiled over for months to run on
|
||||||
|
the L1 can be put in Hydra and 'just work'.
|
||||||
|
|
||||||
|
[^1]:
|
||||||
|
This post does not distinguish between Hydra and Hydra Head referring to
|
||||||
|
both as Hydra. If you want to know more about Hydra, then check out their
|
||||||
|
[explainers](https://hydra.family/head-protocol/core-concepts).
|
||||||
|
|
||||||
|
## Hydra's compromise
|
||||||
|
|
||||||
|
Hydra boasts it can achieve higher throughput and lower transaction fees
|
||||||
|
compared to the Cardano L1 as well as near instant settling and no roll-backs.
|
||||||
|
You may be asking _If my dapp just works on Hydra and it's better in all key
|
||||||
|
respects, then why don't we all just use Hydra?_. The answer is because these
|
||||||
|
improvements come at a cost. Consensus in Hydra differs from that on the L1.
|
||||||
|
Hydra doesn't use ouroboros. Instead all participating hydra nodes must sign-off
|
||||||
|
on all updates to the chain state. Practically speaking, far fewer nodes can
|
||||||
|
participate in Hydra and one quiet node stops the whole Hydra chain updating.
|
||||||
|
Not great for an L1.
|
||||||
|
|
||||||
|
## You don't need Hydra
|
||||||
|
|
||||||
|
Hydra is an example of a way to do state channels. A state channel relies on the
|
||||||
|
integrity of the L1, while accumulating state separately from it (L2). At some
|
||||||
|
point the layers are brought into sync. This is when funds on the L1 can be
|
||||||
|
unlocked, and/or the state of the L2 updated.
|
||||||
|
|
||||||
|
Hydra could be thought to be providing some future-proofing. It is possible for
|
||||||
|
a Hydra instance to run indefinitely and Plutus scripts not yet written will be
|
||||||
|
executable in some already running instance. However, because Hydra's consensus
|
||||||
|
is so brittle the longevity of an instance is not something to depend on. Each
|
||||||
|
and any transaction may be its last.
|
||||||
|
|
||||||
|
A key question when considering Hydra is _Do I need isomorphic-ness?_. If you
|
||||||
|
know all your business logic before instantiation then the answer is **no, you
|
||||||
|
don't care for isomorphic-ness**. Instead, you can roll-your-own L2. It depends
|
||||||
|
on your use case as to how much work that ends up being. It can be very simple.
|
||||||
|
|
||||||
|
## You don't want Hydra
|
||||||
|
|
||||||
|
In Hydra, the latest agreed state in the L2 is the one that the L1 will accept
|
||||||
|
as the most legitimate. This is a sensible default.
|
||||||
|
|
||||||
|
Suppose however you have a game of poker where one player learns that they've
|
||||||
|
lost and rage quits. From the game's perspective, that final transaction should
|
||||||
|
be forced through - the player's loss is inevitable. At present this isn't
|
||||||
|
possible with Hydra. If a party doesn't sign, then a state isn't valid.
|
||||||
|
|
||||||
|
In another use case, suppose there is some particularly intense on-chain
|
||||||
|
verification that would be prohibitive on the L1 but that you'd like the results
|
||||||
|
of which to persist onto the L1 and/or be recovered in future L2 instances. This
|
||||||
|
could be done with validity tokens but anything minted in the L2 won't persist
|
||||||
|
onto the L1.
|
||||||
|
|
||||||
|
Another key question then is _What is the right way to sync the L1 and L2
|
||||||
|
states?_. Hydra has a way of it doing it which might or might not be appropriate
|
||||||
|
for your use case. Rolling your own L2 means that the sync logic can fit your
|
||||||
|
business needs. Both the cases above are resolvable with custom sync logic.
|
||||||
|
|
||||||
|
## An Example: Subbit.xyz
|
||||||
|
|
||||||
|
Probably the simplest, non-trivial example using state channels is
|
||||||
|
[Subbit.xyz](https://subbit.xyz). Subbit.xyz is premised on the observation that
|
||||||
|
subscription is a very common use case: there are two parties where one pays the
|
||||||
|
other incrementally. It sacrifices generality to gain absolutely minimal
|
||||||
|
overhead for both parties.
|
||||||
|
|
||||||
|
In Subbit.xyz, Alice, a consumer, subscribes to some service of Bob, a provider.
|
||||||
|
Alice instantiates the channel by locking funds, similar to Hydra. There are
|
||||||
|
only two mechanisms for unlocking - one for Alice and the other for Bob. All
|
||||||
|
logic is known at instantiation.
|
||||||
|
|
||||||
|
A consumer needs only to keep track of their account balance, ascertain the cost
|
||||||
|
of each outgoing request, and produce valid signatures for a few dozen bytes of
|
||||||
|
data at a time. They don't need to watch the L1 and it's a non-chatty protocol.
|
||||||
|
The low resource needs opens it up to applications on intermittently connected
|
||||||
|
user devices such as laptops and mobile, and even micro-controllers. High
|
||||||
|
throughput remains achievable.
|
||||||
|
|
||||||
|
A provider must track each subscriber's account, and periodically check the
|
||||||
|
state of the L1. This could conceivably be as little as once a week or once a
|
||||||
|
month. The low resource needs for a provider means they have the ability to
|
||||||
|
serve more with less.
|
||||||
|
|
||||||
|
## Hydra for QoL
|
||||||
|
|
||||||
|
When Hydra reaches a point of maturity that it's plug and play, it's potentially
|
||||||
|
far easier to deploy with Hydra then roll-your-own L2. Isomorphic-ness gives
|
||||||
|
Hydra incredible flexibility and generality. You don't need isomorphic-ness but
|
||||||
|
because of it, Hydra could be an easy and convenient solution.
|
||||||
|
|
||||||
|
As for custom sync logic, it is surely the case that there is a tranche of
|
||||||
|
interesting applications where it's far easier and more effective to reuse Hydra
|
||||||
|
infra and modify it than creating your own L2 from scratch.
|
|
@ -0,0 +1,272 @@
|
||||||
|
---
|
||||||
|
title: "Principles of dapp design"
|
||||||
|
date: 2024-11-26
|
||||||
|
status: published
|
||||||
|
---
|
||||||
|
|
||||||
|
There are a collection of disparate thoughts on what makes for good design that
|
||||||
|
inform the decisions we make when designing dapps.
|
||||||
|
We thought it helpful to articulate them for ourselves, for any future
|
||||||
|
collaborators, and any wider audience interested.
|
||||||
|
These thoughts are expressed as diktats but are intended as perspective
|
||||||
|
from which to pick through there shortcomings.
|
||||||
|
|
||||||
|
These "principles" don't fit neatly into some SOLID like framework. Some are
|
||||||
|
high-level general security concious software dev stuff, others are quite
|
||||||
|
specifically reflecting on a "building an L2 on cardano". Some implications
|
||||||
|
manifest at the software architecture level, while others inform the development
|
||||||
|
process.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Before unpacking the principles, it's worth reflecting on _devving in
|
||||||
|
Cardanoland_.
|
||||||
|
|
||||||
|
The Cardano ledger is a lean by design: it is to provide a kernel of integrity
|
||||||
|
for larger applications. It was never to, say, host and execute all the
|
||||||
|
components of an application. It is even a little too lean in places (eg using
|
||||||
|
key hashes over keys in the script context).
|
||||||
|
|
||||||
|
On-chain code is the code executed in the ledger. All integrity guarantees of an
|
||||||
|
application built over ledger ultimately rely on the on-chain code. On-chain
|
||||||
|
code is purely discriminatory: show it a transaction and it will succeed or
|
||||||
|
fail. It cannot generate valid transactions. That is the responsibility for
|
||||||
|
other, off-chain code. Off-chain code is a necessary part of any such
|
||||||
|
application, but does not provide integrity guarantees.
|
||||||
|
|
||||||
|
We'll call applications, structured such that their integrity is backed by the
|
||||||
|
ledger, "dapps". Typically the term has other assumptions: a decentralised
|
||||||
|
application should be run-able by anyone with internet access, reasonable
|
||||||
|
hardware, and without need to seek permission from some authority.
|
||||||
|
|
||||||
|
On-chain code takes form as Plutus validators. Writing validators as
|
||||||
|
**extreme programming**.
|
||||||
|
Lets immediately disambiguate with the [XP methedology][xp-wiki].
|
||||||
|
It's extreme in the sense that it is highly unusual.
|
||||||
|
|
||||||
|
[xp-wiki]: https://en.wikipedia.org/wiki/Extreme_programming
|
||||||
|
|
||||||
|
It's extreme in the following ways:
|
||||||
|
|
||||||
|
- Highly constrained execution environment, which has multiple implications
|
||||||
|
- Diminishing returns of code re-use
|
||||||
|
- Conflicting motivation on factorization
|
||||||
|
- Un-patch-ablity
|
||||||
|
- High cost of failure
|
||||||
|
- Functions that do nothing but fail
|
||||||
|
|
||||||
|
Resource limitations at runtime are incredibly restrictive. Moreover, all
|
||||||
|
resources used have to be paid for. This is a concern is shared to some extent
|
||||||
|
with low level programming but even there is relatively niche on modern
|
||||||
|
hardware. There are many implications of this. A key one is that implementation
|
||||||
|
matter.
|
||||||
|
|
||||||
|
Libraries have limited use. As noted, the efficiency of an implementation
|
||||||
|
matters. If one needs to aggregate multiple values over a list of inputs, it is
|
||||||
|
generally cheaper to do this in a single fold, and without
|
||||||
|
constructing/deconstructing across a function boundary. Implementation details
|
||||||
|
cannot be abstracted away in simple one-size-fits-all manner, at least at zero
|
||||||
|
cost (cf plutus apps and the bloated outputs it would return). In real world
|
||||||
|
examples this cost is significant enough, that the use of stock library methods
|
||||||
|
must be considered. One saving grace is that this is manageable. The resource
|
||||||
|
limitations mean that anything over a couple thousand lines of code risks not
|
||||||
|
being usable in a transaction anyway.
|
||||||
|
|
||||||
|
Factorizing code in way that communicates purpose to a reader should be a strong
|
||||||
|
consideration. However, we already have another, possibly conflicting,
|
||||||
|
consideration: implementation efficiency. Some might say this is a compiler
|
||||||
|
problem, and that we should have clever compilers. To which the most immediate
|
||||||
|
answer is "yes, but right now we don't". It is also not a full panacea. A clever
|
||||||
|
compiler is harder to reason about, harder to verify its correctness, and it can
|
||||||
|
become more obscure to prod the compiler into pathways known to be more
|
||||||
|
efficient.
|
||||||
|
|
||||||
|
Validators are, a priori, not patchable. Depending on the validator, once
|
||||||
|
'deployed' it may be impossible to update. There is no way to bump or patch a
|
||||||
|
validator once it is in use, without such functionality being designed in. This
|
||||||
|
not unique. It was standard in pre-internet software where rolling updates were
|
||||||
|
infeasible, and still exists for devices that aren't internet enabled. However,
|
||||||
|
it is now far more the niche than the mainstream.
|
||||||
|
|
||||||
|
The correctness of a validator is high stakes. This is a property shared with
|
||||||
|
any security critical software. It is not the same league as, say, aviation
|
||||||
|
software, but it is much closer to that than a game or web app. If there is a
|
||||||
|
bug, then user funds are at risk. This compounds being not patchable. Great
|
||||||
|
efforts must be spent verifying validator correctness.
|
||||||
|
|
||||||
|
Validators are very unusual functions particularly in a functional paradigm.
|
||||||
|
They take as input a very restricted set of args, and either fail or return
|
||||||
|
unit. That's all we care about: discriminating acceptable transactions from
|
||||||
|
unacceptable transactions. Sure, this akin to writing a test, or an assert
|
||||||
|
condition - but these are commonly auxiliary rather than the culmination of the
|
||||||
|
code base. Writing Plutus is not akin to, say, some web based API or an ETL
|
||||||
|
pipeline. There is the potential for the code to be utilized by third parties if
|
||||||
|
they desired to build their own transactions that involve the validators.
|
||||||
|
However this use is generally secondary to optimizing for the intended set of
|
||||||
|
transaction builders.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
### On-chain code is to keep participants safe from others
|
||||||
|
|
||||||
|
On-chain code **is** responsible for keeping the user safe from others. It is
|
||||||
|
its fundamental responsibility.
|
||||||
|
|
||||||
|
On-chain code is **not** responsible for keeping the user safe from themselves.
|
||||||
|
A user can compromise themselves completely and totally by, say, publishing their signing key,
|
||||||
|
voiding any guarantees provided by on-chain code.
|
||||||
|
Thus such guarantees are generally superfluous.
|
||||||
|
|
||||||
|
Off-chain code is responsible for keeping the user safe, and it is off-chain
|
||||||
|
code that should make it hard for them to shoot themselves in the foot.
|
||||||
|
|
||||||
|
On-chain code is also not there to facilitate politeness. Good and bad behaviour
|
||||||
|
is a binary distinction, discriminated thoroughly by a validator. A partner may
|
||||||
|
stop responding for legitimate or malicious reasons. We do not need to
|
||||||
|
speculate; we need only ensure that the other partner's funds are safe and can
|
||||||
|
be recovered.
|
||||||
|
|
||||||
|
Suppose there is a dapp that requires the depositing of funds, with a pathway in which the funds
|
||||||
|
may be returned. Further, that this return is verified by a signature belonging
|
||||||
|
associated to key provided by the depositor.
|
||||||
|
|
||||||
|
Some may wish on-chain code to check everything it is possible to check.
|
||||||
|
This includes keeping the user safe from themself.
|
||||||
|
From this perspective the validator should
|
||||||
|
have a constraint that this key has signed the tx, verifying that the depositor
|
||||||
|
has the associated signing key.
|
||||||
|
|
||||||
|
According to this principle, the validator should not.
|
||||||
|
The larger code base should.
|
||||||
|
The documentation should be crystal clear what this key is for and how it should be managed.
|
||||||
|
But the on-chain code should be solely focused on keeping the user safe from others, and other from the user.
|
||||||
|
|
||||||
|
There is a loose sense of the [streetlight effect](https://en.wikipedia.org/wiki/Streetlight_effect) bourne out in code.
|
||||||
|
|
||||||
|
### Simplicity invites security
|
||||||
|
|
||||||
|
Particularly for on-chain code, err on the side of simplicity. Design and build
|
||||||
|
such that reasoning around scenarios is straightforward, and answering "what if
|
||||||
|
..." questions are easy.
|
||||||
|
|
||||||
|
This should not be at the expense of being feature complete, although the
|
||||||
|
principle then becomes a little grey on application. In places it translates to:
|
||||||
|
develop an abstraction in which the features become simple. In other places, we
|
||||||
|
will have to find the happy compromise between simple-ness and feature-ful-ness.
|
||||||
|
For example, in the case of designing [Cardano Lightning](cardano-lightning.org)
|
||||||
|
can a partner close two of their channels in a single transaction?
|
||||||
|
This might be important but perhaps could be handled by an abstraction in the application
|
||||||
|
hiding this detail to the user.
|
||||||
|
|
||||||
|
There is overlap here with the previous principle.
|
||||||
|
By having the on-chain code laser focused, we don't have nice-to-haves,
|
||||||
|
cluttering the code base, possibly obscuring our view from otherwise glaring bug.
|
||||||
|
|
||||||
|
The current principle further justifies excluding logic in the validator
|
||||||
|
that a self interested participant would be motivated from ensuring themselves.
|
||||||
|
For example: a validator must check that a thread token never leaves a script address;
|
||||||
|
it need not check that a participant has paid themselves their due funds.
|
||||||
|
|
||||||
|
### Prioritise user autonomy
|
||||||
|
|
||||||
|
Deprioritise collaborative actions.
|
||||||
|
|
||||||
|
This principle is only employable only in specific circumstances.
|
||||||
|
|
||||||
|
In Cardano Lightning a user is responsible for their own funds, and only their
|
||||||
|
own funds. They cannot spend their partners funds. For example, when winding
|
||||||
|
down a channel, it requires each user to submit evidence of what they are owed.
|
||||||
|
The pathway could have enforced that one partner left an address to which the
|
||||||
|
other partner's tx would output the correct funds. It would potentially save a
|
||||||
|
transaction in the winding down process. However, it also invites questions of
|
||||||
|
double satisfaction, and resolutions to this make it harder to reason about.
|
||||||
|
|
||||||
|
Instead, following this principle,
|
||||||
|
the design prioritises multi-channel management.
|
||||||
|
A fundamental participant type in a healthy Lightning network is the Gateway.
|
||||||
|
A gateway participant is highly
|
||||||
|
connected within the network and is (financially) motivated to route payments
|
||||||
|
between participants. A gateway node in particular needs to manage their
|
||||||
|
channels, and manage their capital amongst channels as efficiently as possible.
|
||||||
|
This in the whole network's interest.
|
||||||
|
|
||||||
|
Cardano Lightning does permit mutual agreed actions.
|
||||||
|
However, such actions is considered as a secondary
|
||||||
|
pathway. Any channel has (at most) the funds of only the two partners of the
|
||||||
|
channel. Mutual actions are verified by the presence of both partners'
|
||||||
|
signatures on a transaction. As we assume that any participant will act in a
|
||||||
|
self interested manner, and is responsible for keeping themselves safe, few
|
||||||
|
checks are done beyond this.
|
||||||
|
|
||||||
|
### Distinguish safety from convenience
|
||||||
|
|
||||||
|
Safety comes first, but we also need things to be practical and preferably even
|
||||||
|
convenient. Make explicit when a feature has been included for safety, or is to
|
||||||
|
do with convenience.
|
||||||
|
|
||||||
|
In some respects, this is restating of the first and or second principles.
|
||||||
|
The on-chain code is precisely what keeps users safe.
|
||||||
|
The small distinction is that this extends to off-code too.
|
||||||
|
There are aspects of off-chain code that are integral, and other parts that are convenient.
|
||||||
|
|
||||||
|
### Spec first, implement second
|
||||||
|
|
||||||
|
A spec:
|
||||||
|
|
||||||
|
- Bridges from intent to code
|
||||||
|
- Expels ambiguity early
|
||||||
|
- Says no to feature creep
|
||||||
|
|
||||||
|
A spec bridges the design intentions to the implementation. Code is halfway
|
||||||
|
house between what a human can parse and what a machine can parse. Where that
|
||||||
|
halfway falls is a question for language design(ers), and there is a full
|
||||||
|
spectrum of positions available with all the possible languages. Very roughly,
|
||||||
|
the closer it is to human readable, the less efficient it ends up being executed
|
||||||
|
by the machine.
|
||||||
|
|
||||||
|
Regardless of a language's expressiveness, it doesn't replace the need for
|
||||||
|
additional documentation.
|
||||||
|
"Self-describing code" is a nice idea and not without strong merits. Naming
|
||||||
|
should follow conventions, and should not be knowingly obscure. In software at
|
||||||
|
large, the problem of separate documentation falling out of sync is observed ad
|
||||||
|
nauseum. But. The idea that descriptive names and some inline comments are
|
||||||
|
sufficient to communicate design and intent is not credible. [I agree with
|
||||||
|
Jef][jef-raskin-blog].
|
||||||
|
|
||||||
|
[jef-raskin-blog]: https://queue.acm.org/detail.cfm?id=1053354
|
||||||
|
|
||||||
|
The above is especially acute in the context of the extreme programming paradigm
|
||||||
|
that is Plutus development. We can and should demand more attention from a dev
|
||||||
|
engaging with the application. They should not expect to "intuitively"
|
||||||
|
understand how to interface with the code. Again - not to be justify any
|
||||||
|
obscurity of design or code, but a validator is not simply just another library
|
||||||
|
they'd be interfacing with. The stakes are too high. They must read the spec.
|
||||||
|
|
||||||
|
We suffer less from docs/code divergence than is experienced in "normal" development.
|
||||||
|
For the same reason that we have un-patch-ablity we have a fixed feature set.
|
||||||
|
It is in evolving software and its code base, by adding new features or modifying existing ones,
|
||||||
|
when code diverges from docs.
|
||||||
|
|
||||||
|
A spec helps expel ambiguity early. It provides an opportunity to check that
|
||||||
|
everyone is on the same page before any lines of code are written, and without
|
||||||
|
having to unpick lines of code after the fact.
|
||||||
|
|
||||||
|
A spec helps make the implementation stage straightforward and intentional. It
|
||||||
|
greatly reduces the required bandwidth since each part of the code has a
|
||||||
|
prescribed purpose. This is also reduces the cost of writing wrong things,
|
||||||
|
before settling on something acceptable.
|
||||||
|
|
||||||
|
Having a spec combats feature creep. Adding feature requirements part way
|
||||||
|
through implementation can lead to convoluted and design and code, and
|
||||||
|
ultimately greatly increase the chance of bugs. As discussed for on-chain code,
|
||||||
|
rolling updates are not generally possible. We need to make sure from the start
|
||||||
|
what the feature requirements are (and as importantly what they aren't).
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The principles have been arrived up over numerous projects,
|
||||||
|
most explicitly and recently while working on Cardano Lightning.
|
||||||
|
As alluded to in the introduction, these principles should ...
|
||||||
|
well, be treated more like [guidelines](https://youtu.be/k9ojK9Q_ARE).
|
||||||
|
|
||||||
|
If you comments, questions, suggested, or criticisms, please get in touch.
|
|
@ -0,0 +1,295 @@
|
||||||
|
---
|
||||||
|
title: Tracing Aiken Build
|
||||||
|
date: 2023-09-02
|
||||||
|
---
|
||||||
|
|
||||||
|
Aims:
|
||||||
|
|
||||||
|
> Describe the pipeline and components getting from Aiken to Uplc.
|
||||||
|
|
||||||
|
## The Preface
|
||||||
|
|
||||||
|
### Motivations
|
||||||
|
|
||||||
|
The motivation for writing this came from a desire to add additional features to
|
||||||
|
Aiken not yet available. One such feature would evaluate an arbitrary function
|
||||||
|
in Aiken callable from JavaScript. This would help a lot with testing and when
|
||||||
|
trying to align on and off-chain code.
|
||||||
|
|
||||||
|
Another more pipe dreamy, ad-hoc function extraction - from a span of code,
|
||||||
|
generate a function. A digression to answer _why would this be at all helpful?!_
|
||||||
|
Validator logic often needs a broad context throughout. How then to best factor
|
||||||
|
code? Possible solutions:
|
||||||
|
|
||||||
|
1. Introduce types / structs
|
||||||
|
2. Have functions with lots of arguments
|
||||||
|
3. Don't
|
||||||
|
|
||||||
|
The problems are:
|
||||||
|
|
||||||
|
1. Requires relentless constructing and deconstructing across the function call.
|
||||||
|
This adds costs.
|
||||||
|
2. Becomes tedious aligning the definition and function call.
|
||||||
|
3. Ends up with very long validators which are hard to unit test.
|
||||||
|
|
||||||
|
My current preferred way is to accept that validator functions are long. Ad-hoc
|
||||||
|
function extraction would allow for sections of code to be tested without
|
||||||
|
needing to be factored out.
|
||||||
|
|
||||||
|
To do either of these, we need to get to grips with the Aiken compilation
|
||||||
|
pipeline.
|
||||||
|
|
||||||
|
### This won't age well
|
||||||
|
|
||||||
|
Aiken is undergoing active development. This post started life with Aiken
|
||||||
|
~v1.14. Aiken v1.15 introduced reasonably significant changes to the compilation
|
||||||
|
pipeline. The word is that there aren't any more big changes in the near future,
|
||||||
|
but this article will undoubtedly begin to diverge from the current code-base
|
||||||
|
even before publishing.
|
||||||
|
|
||||||
|
### Limitations of narrating code
|
||||||
|
|
||||||
|
Narrating code becomes a compromise between being honest and accurate, and being
|
||||||
|
readable and digestible. The command `aiken build` covers well in excess of
|
||||||
|
10,000 LoC. The writing of this post ground to a halt as it reached deeper into
|
||||||
|
the code-base. To redeem it, some (possibly large) sections remain black boxes.
|
||||||
|
|
||||||
|
## Aiken build
|
||||||
|
|
||||||
|
Tracing `aiken build`, the pipeline is roughly:
|
||||||
|
|
||||||
|
```sample
|
||||||
|
. -> Project::read_source_files ->
|
||||||
|
Vec<Source> -> Project::parse_sources ->
|
||||||
|
ParsedModules -> Project::type_check ->
|
||||||
|
CheckedModules -> CodeGenerator::build ->
|
||||||
|
AirTree -> AirTree::to_vec ->
|
||||||
|
Vec<Air> -> CodeGenerator::uplc_code_gen ->
|
||||||
|
Program / Term<Name> -> serialize ->
|
||||||
|
.
|
||||||
|
```
|
||||||
|
|
||||||
|
We'll pick our way through these steps
|
||||||
|
|
||||||
|
At a high level we are trying to do something straightforward: reformulate Aiken
|
||||||
|
code as Uplc. Some Aiken expressions are relatively easy to handle for example
|
||||||
|
an Aiken `Int` goes to an `Int` in Uplc. Some Aiken expressions require more
|
||||||
|
involved handling, for example an Aiken `If... If Else... Else ` must have the
|
||||||
|
branches "nested" in Uplc. Aiken has lots of nice-to-haves like pattern
|
||||||
|
matching, modules, and generics; Uplc has none of these.
|
||||||
|
|
||||||
|
### The Preamble
|
||||||
|
|
||||||
|
#### Cli handling
|
||||||
|
|
||||||
|
The cli enters at `aiken/src/cmd/mod.rs` which parses the command. With some
|
||||||
|
establishing of context, the program enters `Project::build`
|
||||||
|
(`crates/aiken-project/src/lib.rs`), which in turn calls `Project::compile`.
|
||||||
|
|
||||||
|
#### File crawl
|
||||||
|
|
||||||
|
The program looks for Aiken files in both `./lib` and `./validator`
|
||||||
|
sub-directories. For each it walks over all contents (recursively) looking for
|
||||||
|
`.ak` extensions. It treats these two sets of files a little differently. For
|
||||||
|
example, only validator files can contain the special validator functions.
|
||||||
|
|
||||||
|
#### Parse and Type check
|
||||||
|
|
||||||
|
`Project::parse_sources` parses the module source code. The heavy lifting is
|
||||||
|
done by `aiken_lang::parser::module`, which is evaluated on each file. It
|
||||||
|
produces a `Module` containing a list of parsed definitions of the file:
|
||||||
|
functions, types _etc_, together with metadata like docstrings and the file
|
||||||
|
path.
|
||||||
|
|
||||||
|
`Project::type_check` inspects the parsed modules and, as the name implies,
|
||||||
|
checks the types. It flags type level warnings and errors and constructs a hash
|
||||||
|
map of `CheckedModule`s.
|
||||||
|
|
||||||
|
#### Code generator
|
||||||
|
|
||||||
|
The code generator `CodeGenerator` (`aiken-lang/src/gen_uplc.rs`) is given the
|
||||||
|
definitions found from the previous step, together with the plutus builtins. It
|
||||||
|
has additional fields for things like debugging.
|
||||||
|
|
||||||
|
This is handed over to a `Blueprint` (`aiken-project/src/blueprint/mod.rs`). The
|
||||||
|
blueprint does little more than find the validators on which to run the code
|
||||||
|
gen. The heavy lifting is done by `CodeGenerator::generate`.
|
||||||
|
|
||||||
|
We are now ready to take the source code and create plutus.
|
||||||
|
|
||||||
|
### In the air
|
||||||
|
|
||||||
|
Things become a bit intimidating at this point in terms of sheer lines of code:
|
||||||
|
`gen_uplc.rs` and three modules in `gen_uplc/` totals > 8500 LoC.
|
||||||
|
|
||||||
|
Aiken has its own _intermediate representation_ called `air` (as in Aiken
|
||||||
|
Intermediate Representation). Intermediate representations are common in
|
||||||
|
compiled languages. `Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
||||||
|
Unsurprisingly, it looks a little bit like a language between Aiken and plutus.
|
||||||
|
|
||||||
|
In fact, Aiken has another intermediate representation: `AirTree`. This is
|
||||||
|
constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and
|
||||||
|
air.
|
||||||
|
|
||||||
|
#### Climbing the AirTree
|
||||||
|
|
||||||
|
Within `CodeGenerator::generate`, `CodeGenerator::build` is called on the
|
||||||
|
function body. This takes a `TypedExpr` and constructs and returns an `AirTree`.
|
||||||
|
The construction is recursive as it traverses the recursive `TypedExpr` data
|
||||||
|
structure. More on what an airtree is and its construction below. At the same
|
||||||
|
time `self` is treated as `mut`, so we need to keep an eye on this too. The
|
||||||
|
method which is called and uses this mutability of self is `self.assignment`. It
|
||||||
|
does so by
|
||||||
|
|
||||||
|
```sample
|
||||||
|
- self.assignment
|
||||||
|
└ self.expect_type_assign
|
||||||
|
└ self.code_gen_functions.insert
|
||||||
|
```
|
||||||
|
|
||||||
|
and thus is creating a hashmap of all the functions that appear in the
|
||||||
|
definition. From the call to return of `assign` covers > 600 LoC so we'll leave
|
||||||
|
this as a black box. (`self.handle_each_clause` is also called with `mut` which
|
||||||
|
in turn calls `self.build` for which `mut` it is needed.)
|
||||||
|
|
||||||
|
Validators in Aiken are boolean functions while in Uplc they are unit-valued
|
||||||
|
(aka void-valued) functions. Thus the air tree is wrapped such that `false`
|
||||||
|
results in an error (`wrap_validator_condition`). I don't know why there is a
|
||||||
|
prevailing thought that boolean functions are preferable to functions that error
|
||||||
|
if anything is wrong - which is what validators are.
|
||||||
|
|
||||||
|
`check_validator_args` again extends the airtree from the previous step, and
|
||||||
|
again calls `self.assignment` mutating self. Something interesting is happening
|
||||||
|
here. Script context is the final argument of a validator - for any script
|
||||||
|
purpose. `check_validator_args` treats the script context like it is an unused
|
||||||
|
argument. The importance of this is not immediate, and I've still yet to
|
||||||
|
appreciate why this happens.
|
||||||
|
|
||||||
|
Let's take a look at what AirTree actually is
|
||||||
|
|
||||||
|
```language-rust
|
||||||
|
pub enum AirTree {
|
||||||
|
Statement {
|
||||||
|
statement: AirStatement,
|
||||||
|
hoisted_over: Option<Box<AirTree>>,
|
||||||
|
},
|
||||||
|
Expression(AirExpression),
|
||||||
|
UnhoistedSequence(Vec<AirTree>),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `AirStatement` and `AirExpression` are mutually recursive definitions
|
||||||
|
with `AirTree`. Otherwise, it would be unclear from first inspection how
|
||||||
|
tree-like this really is.
|
||||||
|
|
||||||
|
`AirExpression` has multiple constructors. These include (non-exhaustive)
|
||||||
|
|
||||||
|
- air primitives (including all the ones that appear in plutus)
|
||||||
|
- constructors `Call` and `Fn` to handle anonymous functions
|
||||||
|
- binary and unary operators
|
||||||
|
- handling when and if
|
||||||
|
- handling error and tracing
|
||||||
|
|
||||||
|
`AirStatement` also has multiple constructors. These include
|
||||||
|
|
||||||
|
- let assignments and named function definitions
|
||||||
|
- handling expect assignments
|
||||||
|
- pattern matching
|
||||||
|
- unwrapping data structures
|
||||||
|
|
||||||
|
Note that `AirTree` has many methods that are partial functions, as in there are
|
||||||
|
possible states that are not considered legitimate at different points of its
|
||||||
|
construction and use. For example `hoist_over` will throw an error if called on
|
||||||
|
an `Expression`. As `AirTree` is for internal use only, the scope for potential
|
||||||
|
problems is reasonably contained. It seems likely this is to avoid
|
||||||
|
similar-yet-different IRs between steps. However, the trade off is that it
|
||||||
|
partially obfuscates what is a valid state where.
|
||||||
|
|
||||||
|
What is hoisting? Hoisting gives the airtree depth. The motivation is that by
|
||||||
|
the time we hit Uplc it is "generally better" that
|
||||||
|
|
||||||
|
- function definitions appear once rather than being inlined multiple times
|
||||||
|
- the definition appears as close to use as possible
|
||||||
|
|
||||||
|
Hoisting creates tree paths. The final airtree to airtree step,
|
||||||
|
`self.hoist_functions_to_validator`, traverses these paths. There is a lot of
|
||||||
|
mutating of self, making it quite hard to keep a handle on things. In all this
|
||||||
|
(several thousand?) LoC, it is essentially ascertaining in which node of the
|
||||||
|
tree to insert each function definition. In a resource constrained environment
|
||||||
|
like plutus, this effort is warranted.
|
||||||
|
|
||||||
|
At the same time this function deals with
|
||||||
|
|
||||||
|
- monomophisation - no more generics
|
||||||
|
- erasing opaque types
|
||||||
|
|
||||||
|
Neither of which exist at the Uplc level.
|
||||||
|
|
||||||
|
#### Into Air
|
||||||
|
|
||||||
|
The `to_vec : AirTree -> Vec<Air>` is much easier to digest. For one, it is not
|
||||||
|
evaluated in the context of the code generator, and two, there is no mutation of
|
||||||
|
the airtree. The function recursively takes nodes of the tree and maps them to
|
||||||
|
entries in a mutable vector. It flattens the tree to a vec.
|
||||||
|
|
||||||
|
### Down to Uplc
|
||||||
|
|
||||||
|
Next we go from `Vec<Air> -> Term<Name>`. This step is a little more involved
|
||||||
|
than the previous. For one, this is executed in the context of the code
|
||||||
|
generator. Moreover, the code generator is treated as mutable - ouch.
|
||||||
|
|
||||||
|
On further inspection we see that the only mutation is setting
|
||||||
|
`self.needs_field_access = true`. This flag informs the compiler that, if true,
|
||||||
|
additional terms must be added in one of the final steps (see
|
||||||
|
`CodeGenerator::finalize`).
|
||||||
|
|
||||||
|
As noted above, some of the mappings from air to terms are immediate like
|
||||||
|
`Air::Bool -> Term::bool`.
|
||||||
|
Others are less so. Some examples:
|
||||||
|
|
||||||
|
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
||||||
|
- Lists in air have no immediate analogue in uplc
|
||||||
|
- builtins, as in built-in functions (standard shorthand), have to be mediated
|
||||||
|
with some combination of `force` and `delay` in order to behave as they
|
||||||
|
should.
|
||||||
|
- user functions must be "uncurried", ie treated as a sequence of single
|
||||||
|
argument functions, and recursion must be handled
|
||||||
|
- Do some magic in order to efficiently allow "record updates".
|
||||||
|
|
||||||
|
#### Cranking the Optimizer
|
||||||
|
|
||||||
|
There is a sequence of operations performed on the Uplc, mapping
|
||||||
|
`Term<Name> -> Term<Name>`. This removes inconsequential parts of the logic
|
||||||
|
which have been generated, including:
|
||||||
|
|
||||||
|
- removing application of the identity function
|
||||||
|
- directly substituting where apply lambda is applied to a constant or builtin
|
||||||
|
- inline or simplify where apply lambda is applied to a parameter that appears
|
||||||
|
once or not at all
|
||||||
|
|
||||||
|
Each of these optimizing methods has a its own relatively narrow focus, and so
|
||||||
|
although there is a fair number of LoC, it's reasonably straightforward to
|
||||||
|
follow. Some are applied multiple times.
|
||||||
|
|
||||||
|
### The End
|
||||||
|
|
||||||
|
The generated program can now be serialized and included in the blueprint.
|
||||||
|
|
||||||
|
### Plutus Core Signposting
|
||||||
|
|
||||||
|
All this fuss is to get us to a point where we can write Uplc - and good Uplc at
|
||||||
|
that. Note that there are many ways to generate code and most of them are bad.
|
||||||
|
The various design decisions and compilation steps make more sense when we have
|
||||||
|
a better understanding of the target language.
|
||||||
|
|
||||||
|
Uplc is a lambda calculus. For a comprehensive definition on Uplc checkout the
|
||||||
|
specification found
|
||||||
|
[here](https://github.com/input-output-hk/plutus/#specifications-and-design)
|
||||||
|
from the plutus GitHub repo. (I imagine this link will be maintained longer than
|
||||||
|
the current actual link.) If you're not at all familiar with lambda calculus I
|
||||||
|
recommend [an unpacking](https://crypto.stanford.edu/~blynn/lambda/) by Ben
|
||||||
|
Lynn.
|
||||||
|
|
||||||
|
### What next?
|
||||||
|
|
||||||
|
I think it would be helpful to have some examples... Watch this space.
|
|
@ -0,0 +1,60 @@
|
||||||
|
---
|
||||||
|
title: why is building txs hard?
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is a dapp?
|
||||||
|
|
||||||
|
A typical dapp has a number of components:
|
||||||
|
|
||||||
|
- Validators: also called the _on-chain_ part.
|
||||||
|
The decentralized network of nodes that maintain the chain run this code as part of the process of deciding
|
||||||
|
whether a tx is to be added to the chain or rejected.
|
||||||
|
- Chain indexing: watches the chain and records the data relevant to the dapp.
|
||||||
|
- Pretty front-end: typically how a user interacts with a dapp.
|
||||||
|
- Tx-building code: A component of the frontend.
|
||||||
|
It takes data from the user, the user's wallet, the chain-indexer and possibly elsewhere
|
||||||
|
to construct txs to be submitted to the chain that the validators will deem acceptable.
|
||||||
|
|
||||||
|
Here we have really described a browser-based plutus dapp.
|
||||||
|
Considering the term _dapp_ more generally, the Daedalus wallet is a dapp which is neither browser based nor involves any Plutus.
|
||||||
|
Cli-based dapps also exist, such as multi-sig using native scripts.
|
||||||
|
|
||||||
|
## What is a tx?
|
||||||
|
|
||||||
|
At its core, the chain is a list of transaction outputs.
|
||||||
|
The chain is changed by submitting a tx which "spends" existing unspent transaction outputs (utxos) and appending new ones.
|
||||||
|
(There's other possible modifications too, like minting native assets and staking _etc_, but the key part is spending.)
|
||||||
|
|
||||||
|
The on-chain part is where the dapp has its integrity, but users can only interact with the on-chain part of the dapp by submitting txs.
|
||||||
|
|
||||||
|
The on-chain part is relatively simple.
|
||||||
|
It inspects each tx that it is concerned with, and if it does not like what it sees, it fails.
|
||||||
|
Cardano is, in this sense, a lean chain.
|
||||||
|
|
||||||
|
The off-chain part is relatively complex.
|
||||||
|
There may be no, one, or many potential valid txs that would satisfy the user's intent.
|
||||||
|
|
||||||
|
At its core, a transaction is a list of inputs and outputs.
|
||||||
|
The inputs are spent, and the outputs created.
|
||||||
|
It must also contain the necessary signatures.
|
||||||
|
|
||||||
|
## Some history
|
||||||
|
|
||||||
|
When Plutus was first dreamed up, it wasn't just a language.
|
||||||
|
It was whole environment in which dapps would be engaged with.
|
||||||
|
The on-chain and off-chain code were coupled into a single framework with seamless extensive testing.
|
||||||
|
Dapps ran in the _PAB_, Plutus Application Backend.
|
||||||
|
Everything was great.
|
||||||
|
|
||||||
|
Except that it didn't work.
|
||||||
|
The chain-indexer would periodically fall-over,
|
||||||
|
it required users had to maintain a full node,
|
||||||
|
and the api never matured in to something stable, complete, and bug-free.
|
||||||
|
In addition, the validators it produced were bloated and un-optimized and would quickly hit the constraints of the cardano blockchain.
|
||||||
|
|
||||||
|
As a result teams turned to coming up with alternatives.
|
||||||
|
One of the first was MLabs and co in creating pluto and plutarch.
|
||||||
|
Later Aiken appeared to meet similar needs.
|
||||||
|
These began resolving issues with the on-chain part.
|
||||||
|
This left the off-chain part wanting.
|
|
@ -1,16 +0,0 @@
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
// Get all "navbar-burger" elements
|
|
||||||
var $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
|
||||||
// Check if there are any navbar burgers
|
|
||||||
if ($navbarBurgers.length > 0) {
|
|
||||||
// Add a click event on each of them
|
|
||||||
$navbarBurgers.forEach(function ($el) {
|
|
||||||
$el.addEventListener('click', function () {
|
|
||||||
// Get the "main-nav" element
|
|
||||||
var $target = document.getElementById('main-nav');
|
|
||||||
// Toggle the class on "main-nav"
|
|
||||||
$target.classList.toggle('hidden');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
86
flake.lock
|
@ -2,15 +2,14 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"devshell": {
|
"devshell": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"systems": "systems"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688380630,
|
"lastModified": 1735644329,
|
||||||
"narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=",
|
"narHash": "sha256-tO3HrHriyLvipc4xr+Ewtdlo7wM1OjXNjlWRgmM7peY=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "devshell",
|
"repo": "devshell",
|
||||||
"rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205",
|
"rev": "f7795ede5b02664b57035b3b757876703e2c3eac",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -24,11 +23,11 @@
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1683560683,
|
"lastModified": 1738453229,
|
||||||
"narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
|
"narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "006c75898cf814ef9497252b022e91c946ba8e17",
|
"rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -38,11 +37,11 @@
|
||||||
},
|
},
|
||||||
"flake-root": {
|
"flake-root": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680964220,
|
"lastModified": 1723604017,
|
||||||
"narHash": "sha256-dIdTYcf+KW9a4pKHsEbddvLVSfR1yiAJynzg2x0nfWg=",
|
"narHash": "sha256-rBtQ8gg+Dn4Sx/s+pvjdq3CB2wQNzx9XGFq/JVGCB6k=",
|
||||||
"owner": "srid",
|
"owner": "srid",
|
||||||
"repo": "flake-root",
|
"repo": "flake-root",
|
||||||
"rev": "f1c0b93d05bdbea6c011136ba1a135c80c5b326c",
|
"rev": "b759a56851e10cb13f6b8e5698af7b59c44be26e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -53,11 +52,11 @@
|
||||||
},
|
},
|
||||||
"haskell-flake": {
|
"haskell-flake": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1684180957,
|
"lastModified": 1739669127,
|
||||||
"narHash": "sha256-qtEZf4gcmQU5ePbFtltqpAS0PajWLURVC7nuoS46dSk=",
|
"narHash": "sha256-2s3wYTqKq7aBa41VHWg/G2XAOii8MW+WAMtLdgy1cek=",
|
||||||
"owner": "srid",
|
"owner": "srid",
|
||||||
"repo": "haskell-flake",
|
"repo": "haskell-flake",
|
||||||
"rev": "4e1c76de8795608bb47295c018b37a563c492fd2",
|
"rev": "eabf8cf32e5f6a267ea637e1b3eabc9b7ddf29e1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -68,11 +67,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1677383253,
|
"lastModified": 1722073938,
|
||||||
"narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=",
|
"narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9952d6bc395f5841262b006fbace8dd7e143b634",
|
"rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -84,29 +83,23 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "lib",
|
"lastModified": 1738452942,
|
||||||
"lastModified": 1682879489,
|
"narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=",
|
||||||
"narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
|
"type": "tarball",
|
||||||
"owner": "NixOS",
|
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"dir": "lib",
|
"type": "tarball",
|
||||||
"owner": "NixOS",
|
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1684385584,
|
"lastModified": 1739446958,
|
||||||
"narHash": "sha256-O7y0gK8OLIDqz+LaHJJyeu09IGiXlZIS3+JgEzGmmJA=",
|
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "48a0fb7aab511df92a17cf239c37f2bd2ec9ae3a",
|
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -118,16 +111,16 @@
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680945546,
|
"lastModified": 1735554305,
|
||||||
"narHash": "sha256-8FuaH5t/aVi/pR1XxnF0qi4WwMYC+YxlfdsA0V+TEuQ=",
|
"narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "d9f759f2ea8d265d974a6e1259bd510ac5844c5d",
|
"rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixpkgs-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
@ -142,31 +135,16 @@
|
||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"treefmt-nix": {
|
"treefmt-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs_3"
|
"nixpkgs": "nixpkgs_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1684416994,
|
"lastModified": 1738953846,
|
||||||
"narHash": "sha256-KkZ9diPRl3Y05TngWYs/QhZKnI/3tA3s+2Hhmei8FnE=",
|
"narHash": "sha256-yrK3Hjcr8F7qS/j2F+r7C7o010eVWWlm4T1PrbKBOxQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "42045102f90cfd23ca44ae4ef8362180fefcd7fd",
|
"rev": "4f09b473c936d41582dd744e19f34ec27592c5fd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
72
flake.nix
|
@ -9,7 +9,7 @@
|
||||||
flake-root.url = "github:srid/flake-root";
|
flake-root.url = "github:srid/flake-root";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ flake-parts, ... }:
|
outputs = inputs @ { flake-parts, ... }:
|
||||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
imports = [
|
imports = [
|
||||||
# To import a flake module
|
# To import a flake module
|
||||||
|
@ -22,18 +22,28 @@
|
||||||
inputs.flake-root.flakeModule
|
inputs.flake-root.flakeModule
|
||||||
];
|
];
|
||||||
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||||
perSystem = { config, self', inputs', pkgs, system, ... }: {
|
perSystem =
|
||||||
|
{ config
|
||||||
|
, self'
|
||||||
|
, inputs'
|
||||||
|
, pkgs
|
||||||
|
, system
|
||||||
|
, ...
|
||||||
|
}: {
|
||||||
# Per-system attributes can be defined here. The self' and inputs'
|
# Per-system attributes can be defined here. The self' and inputs'
|
||||||
# module parameters provide easy access to attributes of the same
|
# module parameters provide easy access to attributes of the same
|
||||||
# system.
|
# system.
|
||||||
|
|
||||||
haskellProjects.default = {
|
haskellProjects.default = {
|
||||||
# packages.haskell-template.root = ./.; # Auto-discovered by haskell-flake
|
|
||||||
overrides = self: super: { };
|
|
||||||
devShell = {
|
devShell = {
|
||||||
tools = hp: {
|
tools = hp:
|
||||||
|
{
|
||||||
|
fourmolu = hp.fourmolu;
|
||||||
|
hoogle = hp.hoogle;
|
||||||
|
haskell-language-server = hp.haskell-language-server;
|
||||||
treefmt = config.treefmt.build.wrapper;
|
treefmt = config.treefmt.build.wrapper;
|
||||||
} // config.treefmt.build.programs;
|
}
|
||||||
|
// config.treefmt.build.programs;
|
||||||
hlsCheck.enable = false;
|
hlsCheck.enable = false;
|
||||||
};
|
};
|
||||||
autoWire = [ "packages" "apps" "checks" ]; # Wire all but the devShell
|
autoWire = [ "packages" "apps" "checks" ]; # Wire all but the devShell
|
||||||
|
@ -50,6 +60,7 @@
|
||||||
programs.nixpkgs-fmt.enable = true;
|
programs.nixpkgs-fmt.enable = true;
|
||||||
programs.cabal-fmt.enable = true;
|
programs.cabal-fmt.enable = true;
|
||||||
programs.hlint.enable = true;
|
programs.hlint.enable = true;
|
||||||
|
programs.alejandra.enable = true;
|
||||||
|
|
||||||
# We use fourmolu
|
# We use fourmolu
|
||||||
programs.ormolu.package = pkgs.haskellPackages.fourmolu;
|
programs.ormolu.package = pkgs.haskellPackages.fourmolu;
|
||||||
|
@ -60,17 +71,54 @@
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.prettier.enable = true;
|
programs.prettier = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
printWidth = 80;
|
||||||
|
proseWrap = "always";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
# Equivalent to inputs'.nixpkgs.legacyPackages.hello;
|
# Equivalent to inputs'.nixpkgs.legacyPackages.hello;
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default =
|
||||||
|
let
|
||||||
|
menu =
|
||||||
|
pkgs.writeShellScriptBin "menu"
|
||||||
|
''
|
||||||
|
echo -e "\nCommands available: \n${
|
||||||
|
builtins.foldl' (x: y: x + " -> " + (pkgs.lib.getName y) + "\n") "" my-packages
|
||||||
|
}"
|
||||||
|
'';
|
||||||
|
my-packages = [
|
||||||
|
menu
|
||||||
|
build
|
||||||
|
watch
|
||||||
|
tailwind
|
||||||
|
deploy
|
||||||
|
];
|
||||||
|
tailwind = pkgs.writeShellScriptBin "tailwind" ''
|
||||||
|
tailwindcss -i ./content/css/main.css -o ./assets/css/mini.css --minify
|
||||||
|
'';
|
||||||
|
build = pkgs.writeShellScriptBin "build" ''
|
||||||
|
${tailwind}/bin/tailwind
|
||||||
|
cabal run site -- build
|
||||||
|
'';
|
||||||
|
watch = pkgs.writeShellScriptBin "watch" ''
|
||||||
|
${tailwind}/bin/tailwind
|
||||||
|
cabal run site -- watch
|
||||||
|
'';
|
||||||
|
deploy = pkgs.writeShellScriptBin "deploy" ''
|
||||||
|
rsync -r --delete ./docs/* genesis:/var/www/kompactio-landing/
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
inputsFrom = [
|
inputsFrom = [
|
||||||
config.haskellProjects.default.outputs.devShell
|
config.haskellProjects.default.outputs.devShell
|
||||||
config.flake-root.devShell
|
config.flake-root.devShell
|
||||||
];
|
];
|
||||||
packages = with pkgs; [
|
packages = with pkgs;
|
||||||
|
[
|
||||||
caddy
|
caddy
|
||||||
nil
|
nil
|
||||||
nodePackages_latest.vscode-langservers-extracted
|
nodePackages_latest.vscode-langservers-extracted
|
||||||
|
@ -78,14 +126,14 @@
|
||||||
nodePackages_latest.typescript-language-server
|
nodePackages_latest.typescript-language-server
|
||||||
haskellPackages.hakyll
|
haskellPackages.hakyll
|
||||||
zlib
|
zlib
|
||||||
];
|
]
|
||||||
|
++ my-packages;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
flake = {
|
flake = {
|
||||||
# The usual flake attributes can be defined here, including system-
|
# The usual flake attributes can be defined here, including system-
|
||||||
# agnostic ones like nixosModule and system-enumerating ones, although
|
# agnostic ones like nixosModule and system-enumerating ones, although
|
||||||
# those are more easily expressed in perSystem.
|
# those are more easily expressed in perSystem.
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
name: example
|
|
||||||
version: 0.1.0.0
|
|
||||||
build-type: Simple
|
|
||||||
cabal-version: >= 1.10
|
|
||||||
|
|
||||||
executable site
|
|
||||||
main-is: site.hs
|
|
||||||
build-depends: base == 4.*
|
|
||||||
, hakyll == 4.15.*
|
|
||||||
, hip == 1.5.*
|
|
||||||
, filepath
|
|
||||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
|
||||||
default-language: Haskell2010
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
name: site
|
||||||
|
version: 0.1.0.0
|
||||||
|
build-type: Simple
|
||||||
|
cabal-version: >=1.10
|
||||||
|
|
||||||
|
executable site
|
||||||
|
main-is: site.hs
|
||||||
|
build-depends:
|
||||||
|
base >=4 && <5
|
||||||
|
, filepath
|
||||||
|
, hakyll >=4.16 && <4.17
|
||||||
|
, hip >=1.5 && <1.6
|
||||||
|
|
||||||
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||||
|
default-language: Haskell2010
|
40
site.hs
|
@ -1,36 +1,44 @@
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
import Data.Monoid (mappend)
|
import Data.Monoid (mappend)
|
||||||
import Hakyll
|
import Hakyll
|
||||||
import System.FilePath (splitExtension, joinPath, splitDirectories, replaceExtension)
|
import System.FilePath (joinPath, replaceExtension, splitDirectories, splitExtension)
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
myConfiguration :: Configuration
|
||||||
|
myConfiguration =
|
||||||
|
defaultConfiguration
|
||||||
|
{ destinationDirectory = "docs"
|
||||||
|
}
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hakyll $ do
|
main = hakyllWith myConfiguration $ do
|
||||||
match "content/favicon.png" $ do
|
match "assets/favicon.png" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
|
|
||||||
match "content/images/*" $ do
|
match "assets/images/*" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
|
|
||||||
match "content/scripts/*" $ do
|
match "assets/scripts/*" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
|
|
||||||
match "content/css/*" $ do
|
match "assets/css/*" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile compressCssCompiler
|
compile compressCssCompiler
|
||||||
|
|
||||||
match "content/fonts/*" $ do
|
match "assets/fonts/*" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
|
|
||||||
match "content/posts/*.md" $ do
|
matchMetadata "content/posts/*.md" ((Just "true" /=) . lookupString "draft") $ do
|
||||||
route rmPrefixMd
|
route rmPrefixMd
|
||||||
compile $ pandocCompiler
|
compile $
|
||||||
|
pandocCompiler
|
||||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||||
>>= relativizeUrls
|
>>= relativizeUrls
|
||||||
|
@ -40,16 +48,15 @@ main = hakyll $ do
|
||||||
compile $ do
|
compile $ do
|
||||||
posts <- recentFirst =<< loadAll "content/posts/*.md"
|
posts <- recentFirst =<< loadAll "content/posts/*.md"
|
||||||
let archiveCtx =
|
let archiveCtx =
|
||||||
listField "posts" postCtx (return posts) `mappend`
|
listField "posts" postCtx (return posts)
|
||||||
constField "title" "Blog" `mappend`
|
`mappend` constField "title" "Blog"
|
||||||
defaultContext
|
`mappend` defaultContext
|
||||||
|
|
||||||
makeItem ""
|
makeItem ""
|
||||||
>>= loadAndApplyTemplate "templates/blog.html" archiveCtx
|
>>= loadAndApplyTemplate "templates/blog.html" archiveCtx
|
||||||
>>= loadAndApplyTemplate "templates/default.html" archiveCtx
|
>>= loadAndApplyTemplate "templates/default.html" archiveCtx
|
||||||
>>= relativizeUrls
|
>>= relativizeUrls
|
||||||
|
|
||||||
|
|
||||||
match "content/index.md" $ do
|
match "content/index.md" $ do
|
||||||
route rmPrefixMd
|
route rmPrefixMd
|
||||||
compile $ do
|
compile $ do
|
||||||
|
@ -63,12 +70,11 @@ main = hakyll $ do
|
||||||
|
|
||||||
match "templates/*" $ compile templateBodyCompiler
|
match "templates/*" $ compile templateBodyCompiler
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
postCtx :: Context String
|
postCtx :: Context String
|
||||||
postCtx =
|
postCtx =
|
||||||
dateField "date" "%Y-%m-%d" `mappend`
|
dateField "date" "%Y-%m-%d"
|
||||||
defaultContext
|
`mappend` defaultContext
|
||||||
|
|
||||||
setExtensionInner :: String -> FilePath -> FilePath
|
setExtensionInner :: String -> FilePath -> FilePath
|
||||||
setExtensionInner = flip replaceExtension
|
setExtensionInner = flip replaceExtension
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: ["./content/**/*.{html,js}", "./templates/**/*.{html,js}"],
|
||||||
"./content/**/*.{html,js}",
|
|
||||||
"./templates/**/*.{html,js}",
|
|
||||||
],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
'sans' : ['jetbrains-mono',],
|
sans: ["jetbrains-mono"],
|
||||||
},
|
},
|
||||||
typography: (theme) => ({}),
|
typography: (theme) => ({}),
|
||||||
},
|
},
|
||||||
darkMode: 'class',
|
darkMode: "class",
|
||||||
variants: {},
|
variants: {},
|
||||||
// plugins: [require('@tailwindcss/typography')],
|
// plugins: [require('@tailwindcss/typography')],
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,15 @@
|
||||||
<section id="about" class="py-12 px-2 flex flex-col gap-12">
|
<section id="about" class="py-12 px-2 flex flex-col gap-12">
|
||||||
<header class="text-3xl">
|
<header class="text-3xl"># about</header>
|
||||||
# about
|
|
||||||
</header>
|
|
||||||
<div>
|
<div>
|
||||||
Kompact.io is dapp dev house.
|
Kompact.io is dapp dev house. Our focus:
|
||||||
|
|
||||||
Our focus:
|
|
||||||
<ul class="list-decoration">
|
<ul class="list-decoration">
|
||||||
<li>
|
<li>safety-first</li>
|
||||||
safety-first
|
<li>fast turn around</li>
|
||||||
</li>
|
<li>integration support</li>
|
||||||
<li>
|
|
||||||
fast turn around
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
integration support
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
Our typical process:
|
Our typical process:
|
||||||
<div>
|
<div>Idea -> Spec -> Impl -> Test -> Handover</div>
|
||||||
Idea -> Spec -> Impl -> Test -> Handover
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
|
@ -1,7 +1,5 @@
|
||||||
<section id="services" class="py-6 px-2 flex flex-col gap-12">
|
<section id="services" class="py-6 px-2 flex flex-col gap-12">
|
||||||
<header class="text-3xl">
|
<header class="text-3xl"># blog</header>
|
||||||
# blog
|
|
||||||
</header>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
A nascent initiative sharing some of the things happening at Kompact.io.
|
A nascent initiative sharing some of the things happening at Kompact.io.
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<section id="contact" class="py-12 px-2 flex flex-col gap-12">
|
<section id="contact" class="py-12 px-2 flex flex-col gap-12">
|
||||||
<header class="text-3xl">
|
<header class="text-3xl"># contact</header>
|
||||||
# contact
|
|
||||||
</header>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Questions? We'll be happy to help answer any of your questions. Send us an email and we'll get back to you shortly.
|
Questions? We'll be happy to help answer any of your questions. Send us an
|
||||||
|
email and we'll get back to you shortly.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Reach us on : <a href="mailto:kompactio@proton.me">kompactio@proton.me</a>
|
Reach us on : <a href="mailto:kompactio@proton.me">kompactio@proton.me</a>
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html class="">
|
<html class="">
|
||||||
|
<head>
|
||||||
<head>
|
<meta charset="UTF-8" />
|
||||||
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<link rel="icon" type="image/x-icon" href="/favicon.png" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.png">
|
<link href="/css/mini.css" rel="stylesheet" />
|
||||||
<link href="/css/mini.css" rel="stylesheet">
|
<link href="/css/prism.css" rel="stylesheet" />
|
||||||
<title>$title$</title>
|
<title>$title$</title>
|
||||||
</head>
|
</head>
|
||||||
|
<script>
|
||||||
<script>
|
|
||||||
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||||
function updateTheme() {
|
function updateTheme() {
|
||||||
if (
|
if (
|
||||||
localStorage.theme === 'dark' ||
|
localStorage.theme === "dark" ||
|
||||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
(!("theme" in localStorage) &&
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||||
) {
|
) {
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove("dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
updateTheme()
|
updateTheme();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<body class="bg-white text-gray-900 min-h-screen
|
<body
|
||||||
dark:bg-gradient-to-br dark:from-slate-950 dark:to-black dark:text-white">
|
class="bg-white text-gray-900 min-h-screen dark:bg-gradient-to-br dark:from-slate-950 dark:to-black dark:text-white"
|
||||||
<div class="container mx-auto ">
|
>
|
||||||
|
<div class="container mx-auto">
|
||||||
<hr />
|
<hr />
|
||||||
$partial("templates/nav.html")$
|
$partial("templates/nav.html")$
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
<hr />
|
<hr />
|
||||||
$partial("templates/footer.html")$
|
$partial("templates/footer.html")$
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
<script src="/scripts/prism.js"></script>
|
||||||
</html>
|
</html>
|
|
@ -1,21 +1,33 @@
|
||||||
<section id="footer" class="py-12 px-2 flex flex-row gap-12 mx-2 sm:mx-4 items-start justify-between
|
<section
|
||||||
text-gray-800 dark:text-gray-200 dark:fill-white">
|
id="footer"
|
||||||
<div class="text-sm">
|
class="py-12 px-2 flex flex-row gap-12 mx-2 sm:mx-4 items-start justify-between text-gray-800 dark:text-gray-200 dark:fill-white"
|
||||||
® 2023 kompact.io ™ All Rights Reserved.
|
>
|
||||||
</div>
|
<div class="text-sm">® 2023 kompact.io ™ All Rights Reserved.</div>
|
||||||
<div class="flex flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<a href="https://www.linkedin.com/in/dominic-algernon-wallis-123b42187/">
|
<a href="https://www.linkedin.com/in/dominic-algernon-wallis-123b42187/">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" height="20" preserveAspectRatio="xMidYMid meet">
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 448 512"
|
||||||
|
height="20"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
>
|
||||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||||
<path
|
<path
|
||||||
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z" />
|
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://twitter.com/waalge">
|
<a href="https://twitter.com/waalge">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="20" preserveAspectRatio="xMidYMid meet">
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
height="20"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
>
|
||||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||||
<path
|
<path
|
||||||
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" />
|
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<section id="hero" class="py-8 px-2 h-96 min-h-[50vh] m-auto">
|
<section id="hero" class="py-8 px-2 h-96 min-h-[50vh] m-auto">
|
||||||
<div class="h-full flex justify-around align-center items-center">
|
<div class="h-full flex justify-around align-center items-center">
|
||||||
<div class="text-6xl">
|
<div class="text-6xl">⟨K⟩</div>
|
||||||
⟨K⟩
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 truncate">
|
<div class="flex flex-col gap-2 truncate">
|
||||||
<div>withKompact $ <span class="text-red-500 dark:text-yellow-400">do</span> </div>
|
<div>
|
||||||
<div><span class="text-gray-400">· ·</span> dapp <- lean dev </div>
|
withKompact $
|
||||||
<div><span class="text-gray-400">· ·</span> run dapp </div>
|
<span class="text-red-500 dark:text-yellow-400">do</span>
|
||||||
|
</div>
|
||||||
|
<div><span class="text-gray-400">· ·</span> dapp <- lean dev</div>
|
||||||
|
<div><span class="text-gray-400">· ·</span> run dapp</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="text-1xl font-bold">
|
||||||
|
$title$
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
|
$body$
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="text-1xl font-bold">
|
||||||
|
$title$
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
|
$body$
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="index flex flex-col justify-between gap-4 sm:gap-8">
|
||||||
|
$for(sections)$
|
||||||
|
$body$
|
||||||
|
$endfor$
|
||||||
|
</div>
|
|
@ -9,7 +9,8 @@
|
||||||
<div>
|
<div>
|
||||||
<ul class="flex flex-row gap-4 md:gap-8">
|
<ul class="flex flex-row gap-4 md:gap-8">
|
||||||
<li>
|
<li>
|
||||||
<button onClick="
|
<button
|
||||||
|
onClick="
|
||||||
(() => {
|
(() => {
|
||||||
if (!('theme' in localStorage)) {
|
if (!('theme' in localStorage)) {
|
||||||
localStorage.theme = 'light'
|
localStorage.theme = 'light'
|
||||||
|
@ -22,17 +23,16 @@
|
||||||
}
|
}
|
||||||
updateTheme()
|
updateTheme()
|
||||||
})()
|
})()
|
||||||
">◧</button>
|
"
|
||||||
|
>
|
||||||
|
◧
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/index.html#contact">
|
<a href="/index.html#contact"> contact </a>
|
||||||
contact
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/blog.html">
|
<a href="/blog.html"> blog </a>
|
||||||
blog
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
$for(posts)$
|
$for(posts)$
|
||||||
<li class="mt-4">
|
<li class="mt-4">
|
||||||
<a href="$url$">
|
<a href="$url$">
|
||||||
<span class="text-gray-800 dark:text-gray-200">
|
<span class="text-gray-800 dark:text-gray-200"> $date$ :: </span>
|
||||||
$date$ ::
|
|
||||||
</span>
|
|
||||||
$title$
|
$title$
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
<article>
|
<article class="mx-auto px-4 max-w-prose">
|
||||||
<section class="header">
|
<section class="header">
|
||||||
<h1>
|
<h1>$title$</h1>
|
||||||
$title$
|
|
||||||
</h1>
|
|
||||||
$if(date)$
|
$if(date)$
|
||||||
<p>
|
<p>Posted on $date$</p>
|
||||||
Posted on $date$
|
$endif$ $if(author)$
|
||||||
</p>
|
<p>by $author$</p>
|
||||||
$endif$
|
|
||||||
$if(author)$
|
|
||||||
<p>
|
|
||||||
by $author$
|
|
||||||
</p>
|
|
||||||
$endif$
|
$endif$
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>$body$</section>
|
||||||
$body$
|
|
||||||
</section>
|
|
||||||
</article>
|
</article>
|
|
@ -1,46 +1,39 @@
|
||||||
<section id="pricing" class="py-12 px-2 flex flex-col gap-12">
|
<section id="pricing" class="py-12 px-2 flex flex-col gap-12">
|
||||||
<header class="text-3xl">
|
<header class="text-3xl"># pricing</header>
|
||||||
# pricing
|
|
||||||
</header>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Plutus development has traditionally meant long development schedules, and expensive ( $ 25k+/mo FTE) engineers.
|
Plutus development has traditionally meant long development schedules, and
|
||||||
We can work with you at competitive rates in either deliverable or retainer based engagements.
|
expensive ( $ 25k+/mo FTE) engineers. We can work with you at
|
||||||
|
competitive rates in either deliverable or retainer based engagements.
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-8 md:mx-24">
|
<div class="flex flex-col justify-between gap-4 sm:flex-row sm:gap-8">
|
||||||
<div class="max-w-48">
|
<div class="flex-1">
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">## retainer</div>
|
||||||
## retainer
|
<p class="text-gray-800 dark:text-gray-200 mt-4">Time-based</p>
|
||||||
</div>
|
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
|
||||||
Time-based
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
|
||||||
Still figuring out your project scope?
|
Still figuring out your project scope?
|
||||||
</div>
|
</p>
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Need an extra pair of hands on an existing project?
|
Need an extra pair of hands on an existing project?
|
||||||
</div>
|
</p>
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Then a retainer based engagement is for you.
|
Then a retainer based engagement is for you.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="flex-1">
|
||||||
<div class="max-w-48">
|
<div class="text-1xl font-bold">## deliverable</div>
|
||||||
<div class="text-1xl font-bold">
|
<p class="text-gray-800 dark:text-gray-200 mt-4">Output-based</p>
|
||||||
## deliverable
|
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
</div>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
|
||||||
Output-based
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
|
||||||
You know what you want and need help implementing it?
|
You know what you want and need help implementing it?
|
||||||
</div>
|
</p>
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
We'll first produce a spec on how the dapp will operate technically.
|
We'll first produce a spec on how the dapp will operate technically.
|
||||||
This involves discussing different options and trade-offs on things from UX to validator complexity.
|
This involves discussing different options and trade-offs on things from
|
||||||
</div>
|
UX to validator complexity.
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
</p>
|
||||||
Once settled we'll begin the implementation phase and finally integration phase.
|
<p class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
</div>
|
Once settled we'll begin the implementation phase and finally
|
||||||
|
integration phase.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
|
@ -1,34 +1,29 @@
|
||||||
<section id="services" class="py-12 px-2 flex flex-col gap-12">
|
<section id="services" class="py-12 px-2 flex flex-col gap-12">
|
||||||
<header class="text-3xl">
|
<header class="text-3xl"># services</header>
|
||||||
# services
|
|
||||||
</header>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
We are cardano native dapp dev outfit focused on helping you going from 0 to launch ASAP.
|
We are cardano native dapp dev outfit focused on helping you going from 0 to
|
||||||
|
launch ASAP.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3 sm:gap-8">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3 sm:gap-8">
|
||||||
<div class="max-w-48">
|
<div class="max-w-48">
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">## strategy</div>
|
||||||
## strategy
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
We'll work with you to validate your concept, and translate it into an implementable Proof of Concept
|
We'll work with you to validate your concept, and translate it into an
|
||||||
|
implementable Proof of Concept
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-48">
|
<div class="max-w-48">
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">## implementation</div>
|
||||||
## implementation
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Cook up appropriate Plutus validators to meet your needs
|
Cook up appropriate Plutus validators to meet your needs
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-48">
|
<div class="max-w-48">
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">## deployment</div>
|
||||||
## deployment
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
We facilitate integrating the on-chain aspects with the rest of your stack
|
We facilitate integrating the on-chain aspects with the rest of your
|
||||||
|
stack
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,55 +3,135 @@
|
||||||
<div class="relative flex h-16 items-center justify-between">
|
<div class="relative flex h-16 items-center justify-between">
|
||||||
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
||||||
<!-- Mobile menu button-->
|
<!-- Mobile menu button-->
|
||||||
<button type="button" class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" aria-controls="mobile-menu" aria-expanded="false">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
<span class="sr-only">Open main menu</span>
|
<span class="sr-only">Open main menu</span>
|
||||||
<!--
|
<!--
|
||||||
Icon when menu is closed.
|
Icon when menu is closed.
|
||||||
|
|
||||||
Menu open: "hidden", Menu closed: "block"
|
Menu open: "hidden", Menu closed: "block"
|
||||||
-->
|
-->
|
||||||
<svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
class="block h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<!--
|
<!--
|
||||||
Icon when menu is open.
|
Icon when menu is open.
|
||||||
|
|
||||||
Menu open: "block", Menu closed: "hidden"
|
Menu open: "block", Menu closed: "hidden"
|
||||||
-->
|
-->
|
||||||
<svg class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
class="hidden h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
|
<div
|
||||||
|
class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start"
|
||||||
|
>
|
||||||
<div class="flex flex-shrink-0 items-center">
|
<div class="flex flex-shrink-0 items-center">
|
||||||
<img class="block h-8 w-auto lg:hidden" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
|
<img
|
||||||
<img class="hidden h-8 w-auto lg:block" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
|
class="block h-8 w-auto lg:hidden"
|
||||||
|
src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500"
|
||||||
|
alt="Your Company"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="hidden h-8 w-auto lg:block"
|
||||||
|
src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500"
|
||||||
|
alt="Your Company"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden sm:ml-6 sm:block">
|
<div class="hidden sm:ml-6 sm:block">
|
||||||
<div class="flex space-x-4">
|
<div class="flex space-x-4">
|
||||||
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
||||||
<a href="#" class="bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium" aria-current="page">Dashboard</a>
|
<a
|
||||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Team</a>
|
href="#"
|
||||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Projects</a>
|
class="bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium">Calendar</a>
|
aria-current="page"
|
||||||
|
>Dashboard</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||||
|
>Team</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||||
|
>Projects</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium"
|
||||||
|
>Calendar</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
<div
|
||||||
<button type="button" class="rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
|
class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
|
||||||
|
>
|
||||||
<span class="sr-only">View notifications</span>
|
<span class="sr-only">View notifications</span>
|
||||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
|
class="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Profile dropdown -->
|
<!-- Profile dropdown -->
|
||||||
<div class="relative ml-3">
|
<div class="relative ml-3">
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
|
||||||
|
id="user-menu-button"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="true"
|
||||||
|
>
|
||||||
<span class="sr-only">Open user menu</span>
|
<span class="sr-only">Open user menu</span>
|
||||||
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
|
<img
|
||||||
|
class="h-8 w-8 rounded-full"
|
||||||
|
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -65,11 +145,38 @@
|
||||||
From: "transform opacity-100 scale-100"
|
From: "transform opacity-100 scale-100"
|
||||||
To: "transform opacity-0 scale-95"
|
To: "transform opacity-0 scale-95"
|
||||||
-->
|
-->
|
||||||
<div class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
<div
|
||||||
|
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="user-menu-button"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
<!-- Active: "bg-gray-100", Not Active: "" -->
|
<!-- Active: "bg-gray-100", Not Active: "" -->
|
||||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
|
<a
|
||||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
|
href="#"
|
||||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
|
class="block px-4 py-2 text-sm text-gray-700"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
id="user-menu-item-0"
|
||||||
|
>Your Profile</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
id="user-menu-item-1"
|
||||||
|
>Settings</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
id="user-menu-item-2"
|
||||||
|
>Sign out</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,10 +187,27 @@
|
||||||
<div class="sm:hidden" id="mobile-menu">
|
<div class="sm:hidden" id="mobile-menu">
|
||||||
<div class="space-y-1 px-2 pb-3 pt-2">
|
<div class="space-y-1 px-2 pb-3 pt-2">
|
||||||
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
||||||
<a href="#" class="bg-gray-900 text-white block rounded-md px-3 py-2 text-base font-medium" aria-current="page">Dashboard</a>
|
<a
|
||||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Team</a>
|
href="#"
|
||||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Projects</a>
|
class="bg-gray-900 text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium">Calendar</a>
|
aria-current="page"
|
||||||
|
>Dashboard</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||||
|
>Team</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||||
|
>Projects</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md px-3 py-2 text-base font-medium"
|
||||||
|
>Calendar</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
@ -12,13 +12,13 @@
|
||||||
/>
|
/>
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<title>$title$</title>
|
<title>$title$</title>
|
||||||
<meta
|
<meta name="description" content="Lean dapp development" />
|
||||||
name="description"
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
content="Lean dapp development"
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="/css/normalize.css" />
|
<link rel="stylesheet" href="/css/normalize.css" />
|
||||||
<link rel="stylesheet" href="/css/terminal.css" />
|
<link rel="stylesheet" href="/css/terminal.css" />
|
||||||
<link rel="stylesheet" href="/css/custom.css" />
|
<link rel="stylesheet" href="/css/custom.css" />
|
||||||
|
|