wip
This commit is contained in:
parent
712c7cac44
commit
194492234e
18
README.md
18
README.md
|
@ -1,16 +1,6 @@
|
||||||
## Commands
|
# Kompact.io site
|
||||||
|
|
||||||
recompile css
|
## Commands
|
||||||
```sh
|
|
||||||
tailwindcss -i ./content/css/main.css -o ./content/css/mini.css --minify
|
|
||||||
```
|
|
||||||
|
|
||||||
build, serve and watch
|
Enter devshell, and run `menu`
|
||||||
```sh
|
See flake for details.
|
||||||
cabal run site -- watch
|
|
||||||
```
|
|
||||||
|
|
||||||
deploy
|
|
||||||
```sh
|
|
||||||
rsync -r --delete ./_site/* genesis:/var/www/kompactio-landing/
|
|
||||||
```
|
|
||||||
|
|
|
@ -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,59 +14,61 @@ article {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section> :is(pre, 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 {
|
article > section {
|
||||||
font-family: "Lucida" Grande, sans-serif;
|
font-family:
|
||||||
|
"Lucida" Grande,
|
||||||
|
sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section> :is(h1, h2, h3, h4, h5, h6, code) {
|
article > section > :is(h1, h2, h3, h4, h5, h6, code) {
|
||||||
font-family: "jetbrains-mono";
|
font-family: "jetbrains-mono";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>blockquote {
|
article > section > blockquote {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-left-width: 4px;
|
border-left-width: 4px;
|
||||||
border-color: rgb(239 68 68);
|
border-color: rgb(239 68 68);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h1 {
|
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 {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h2::before {
|
article > section > h2::before {
|
||||||
content: "## ";
|
content: "## ";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h3 {
|
article > section > h3 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h3::before {
|
article > section > h3::before {
|
||||||
content: "### ";
|
content: "### ";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h4 {
|
article > section > h4 {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section>h4::before {
|
article > section > h4::before {
|
||||||
content: "#### ";
|
content: "#### ";
|
||||||
}
|
}
|
||||||
|
|
||||||
article>section {
|
article > section {
|
||||||
margin-top: 4rem;
|
margin-top: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,4 +93,16 @@ article ul {
|
||||||
article ol {
|
article ol {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
list-style: decimal inside;
|
list-style: decimal inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#footnotes {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes > ol > li {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footnotes > ol > li > p {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,3 +1,118 @@
|
||||||
/* PrismJS 1.29.0
|
/* PrismJS 1.29.0
|
||||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+haskell+json+nix+racket+rust+scheme */
|
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:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.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%,.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}
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
---
|
||||||
|
title: why is building txs hard?
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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,3 +1,5 @@
|
||||||
---
|
---
|
||||||
title: Kompact.io
|
title: Kompact.io
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Hero
|
||||||
|
|
|
@ -3,74 +3,73 @@ title: Are we zk-Cardano yet?
|
||||||
date: 2023-08-07
|
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 space.
|
||||||
Things went monotonically south ever since the announcement, but that's a different story.
|
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 hackathon,
|
||||||
one was _Zero Knowledge_ (aka zk).
|
one was _Zero Knowledge_ (aka zk).
|
||||||
Why particularly interesting quirk? In some sense it is not surprising:
|
Why particularly interesting quirk? In some sense it is not surprising:
|
||||||
zk has been very trendy these last few years around blockchains.
|
zk has been very trendy these last few years around blockchains.
|
||||||
However, building on Cardano is notoriously challenging.
|
However, building on Cardano is notoriously challenging.
|
||||||
Building with zk on a zk-native blockchain is itself a very steep learning curve.
|
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.
|
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_ [by Vitalik](https://vitalik.ca/general/2021/01/26/snarks.html){target="_blank"} or
|
( _eg_ [by Vitalik](https://vitalik.ca/general/2021/01/26/snarks.html){target="\_blank"} or
|
||||||
[a full mooc](https://zk-learning.org/){target="_blank"} ).
|
[a full mooc](https://zk-learning.org/){target="\_blank"} ).
|
||||||
There is also a reasonable breath to the field of zk that includes things like distributed compute.
|
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
|
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.
|
and pairs well with blockchain in extending what is functionally possible.
|
||||||
Let's stick to a simple and prototypical example.
|
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 Bob placing their ships within their own coordinate grid.
|
The game begins with Alice and Bob placing their ships within their own coordinate grid.
|
||||||
They then take turns picking coordinates to "strike".
|
They then take turns picking coordinates to "strike".
|
||||||
If they hit nothing then their turn ends, but if they hit a ship then they strike again.
|
If they hit nothing then their turn ends, but if they hit a ship then they strike again.
|
||||||
The winner is the first to strike all coordinates containing their opponent's ships.
|
The winner is the first to strike all 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 he's not lying by revealing the actual locations of the ships.
|
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,
|
She could ask Charlie to independently verify Bob's not lying,
|
||||||
but then what if Charlie is actually on team Bob and also lies.
|
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 Alice some hints.
|
Or Bob might suspect Charlie is actually on 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 without revealing the locations of the ships either to Alice or anyone else?
|
but 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 misses if and only if it honestly does.
|
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 inspect each proof and verify Bob's response.
|
||||||
Alice can interrogate the proof as much as she wants, but she won't learn anything more than
|
Alice can interrogate the proof as much as she wants, but she 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 modeling the problem as a bunch of algebra
|
but essentially it involves modeling the problem as a bunch of algebra
|
||||||
over finite fields - like a lot of cryptography.
|
over finite fields - like a lot of cryptography.
|
||||||
|
|
||||||
What's the _snark_ of zk-snark?
|
What's the _snark_ of zk-snark?
|
||||||
Snark stands for _Succinct Non-Interactive Argument of Knowledge_.
|
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.
|
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.
|
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 is [here](https://pub.kompact.io/sudoku-snark){target="_blank"}.
|
The summary-pitch-story deck is [here](https://pub.kompact.io/sudoku-snark){target="\_blank"}.
|
||||||
Links to the associated repos: [plutus-zk](https://github.com/waalge/plutus-zk){target="_blank"}
|
Links to the associated repos: [plutus-zk](https://github.com/waalge/plutus-zk){target="\_blank"}
|
||||||
and [sudoku-snark](https://github.com/waalge/sudoku-snark){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 of work.
|
It's a mammoth culmination of many many months of work.
|
||||||
In it were some fundamental primitives needed for running zk algorithms.
|
In it were some fundamental primitives needed for running zk algorithms.
|
||||||
|
|
||||||
The idea of the project was as follows:
|
The idea of the project was as follows:
|
||||||
|
|
||||||
|
@ -79,35 +78,35 @@ The idea of the project was as follows:
|
||||||
- try to get a version of hydra running this newest version of plutus
|
- try to get a version of hydra running this newest version of plutus
|
||||||
- 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 cluster of nodes in the Conway era with the latest version of plutus
|
We did get as far as running a cluster of nodes in the Conway era with the latest version of plutus
|
||||||
but unrelated changes seemed to thwart any chance of building transactions here.
|
but 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"} team.
|
||||||
They had a different approach and managed to implement a zk algorithm with the existing plutus primitives.
|
They had a different approach and managed to implement a zk algorithm with the existing plutus primitives.
|
||||||
This spared the need to play the foolhardy dependency bumping game with the Cardano node.
|
This spared the need to play the foolhardy dependency bumping game with the Cardano node.
|
||||||
However, because zk is so arithmetically intense,
|
However, because zk is so arithmetically intense,
|
||||||
the app wont run outside a hydra head and with very generous max unit budgets (afaics).
|
the app wont run outside a hydra head and with very generous max unit budgets (afaics).
|
||||||
This approach won't be necessary when we have the new version of plutus available.
|
This approach won't be necessary when we have the new version of plutus available.
|
||||||
Nonetheless, it's very neat to see it done and they packaged it very nicely.
|
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 [groth16](https://eprint.iacr.org/2016/260.pdf).
|
||||||
In part because this was already mostly available from the plutus repo itself.
|
In part because this was already mostly available from the plutus repo itself.
|
||||||
It is also the most obvious candidate to begin with.
|
It is also the most obvious candidate to begin with.
|
||||||
It's relatively mature, relatively simple, can be implemented from the new primitives,
|
It's relatively mature, relatively simple, can be implemented from the new primitives,
|
||||||
and importantly in Cardano land has small proof size.
|
and importantly in Cardano land has small proof size.
|
||||||
(As far as I know, the smallest of comparable algorithms.)
|
(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 this choice was initially inspired by a script from the IOG team,
|
Again this choice was initially inspired by a script from the IOG team,
|
||||||
but again it seems like a smart choice.
|
but again it seems like a smart choice.
|
||||||
Arkworks is a well conceived, highly modular framework for zk,
|
Arkworks is a well conceived, highly modular framework for zk,
|
||||||
which makes it easy to pull in the bits we need to perform our off-chain logic.
|
which makes it easy to pull in the bits we need to perform our 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 not the most compelling of choices, but it's simple and it did for now.
|
It's 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 puzzle,
|
||||||
and spendable only if a player could provide proof they knew the solution.
|
and spendable only if a player could provide proof they knew the solution.
|
||||||
|
@ -118,10 +117,10 @@ Other details were TBC: is it first and second prizes? are players whitelisted?
|
||||||
|
|
||||||
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 reach mainnet.
|
||||||
The word on the street is that it might happen before the end of 2023.
|
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 primitives,
|
||||||
and so possibly plumb-able into hydra without causing oneself an aneurysm.
|
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,104 +1,104 @@
|
||||||
---
|
---
|
||||||
title: "Hydra is neat: You don't need Hydra"
|
title: "Hydra is cool: You don't need Hydra"
|
||||||
date: 2023-09-20
|
date: 2023-09-20
|
||||||
---
|
---
|
||||||
|
|
||||||
## Hydra is neat
|
## Hydra is cool
|
||||||
|
|
||||||
Hydra[^1] is a very cool project. It is a layer 2 for Cardano that is _isomorphic_ to the L1.
|
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.
|
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'.
|
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's compromise
|
||||||
|
|
||||||
Hydra boasts it can achieve higher throughput and lower transaction fees compared to the Cardano L1
|
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.
|
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,
|
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?_.
|
then why don't we all just use Hydra?_.
|
||||||
The answer is because these improvements come at a cost.
|
The answer is because these improvements come at a cost.
|
||||||
Consensus in Hydra differs from that on the L1.
|
Consensus in Hydra differs from that on the L1.
|
||||||
Hydra doesn't use ouroboros; all participating hydra nodes
|
Hydra doesn't use ouroboros. Instead all participating hydra nodes
|
||||||
must sign-off on all updates to the chain state.
|
must sign-off on all updates to the chain state.
|
||||||
Practically speaking, far fewer nodes can participate in Hydra
|
Practically speaking, far fewer nodes can participate in Hydra
|
||||||
and one quiet node stops the whole Hydra chain updating.
|
and one quiet node stops the whole Hydra chain updating.
|
||||||
Not great for an L1.
|
Not great for an L1.
|
||||||
|
|
||||||
## You don't need Hydra
|
## You don't need Hydra
|
||||||
|
|
||||||
Hydra is an example of a way to do state channels.
|
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).
|
A state channel relies on the integrity of the L1, while accumulating state separately from it (L2).
|
||||||
At some point the the layers are brought into sync.
|
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.
|
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.
|
Hydra could be thought to be providing some future-proofing.
|
||||||
It is possible for a Hydra instance to run indefinitely
|
It is possible for a Hydra instance to run indefinitely
|
||||||
and scripts yet written will be executable in some already running instance.
|
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.
|
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.
|
Each and any transaction may be its last.
|
||||||
|
|
||||||
A key question is when considering Hydra is _Do I need isomorphic-ness?_.
|
A key question when considering Hydra is _Do I need isomorphic-ness?_.
|
||||||
If you know all your business logic before instantiation
|
If you know all your business logic before instantiation
|
||||||
then the answer is **no, you don't care for isomorphic-ness**.
|
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.
|
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.
|
It can be very simple.
|
||||||
|
|
||||||
|
|
||||||
## You don't want Hydra
|
## 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.
|
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.
|
This is a sensible default.
|
||||||
|
|
||||||
Suppose however you have a game of poker where one player learns that they've lost and rage quits.
|
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.
|
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.
|
At present this isn't possible with Hydra.
|
||||||
If a party doesn't sign then a state isn't valid.
|
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
|
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
|
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.
|
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.
|
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?_.
|
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.
|
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.
|
Rolling your own L2 means that the sync logic can fit your business needs.
|
||||||
Both the cases above are resolvable with custom sync logic.
|
Both the cases above are resolvable with custom sync logic.
|
||||||
|
|
||||||
## An Example: Subbit.xyz
|
## An Example: Subbit.xyz
|
||||||
|
|
||||||
Probably the simplest, non-trivial example using state channels is [Subbit.xyz][https://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 very common use case:
|
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.
|
there are two parties where one pays the other incrementally.
|
||||||
It sacrifices generality to gain absolutely minimal overhead for both parties.
|
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.
|
In Subbit.xyz, Alice, a consumer, subscribes to some service of Bob, a provider.
|
||||||
Alice instantiates the channel by locking funds, similar to Hydra.
|
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.
|
There are only two mechanisms for unlocking - one for Alice and the other for Bob.
|
||||||
All logic is known at instantiation.
|
All logic is known at instantiation.
|
||||||
|
|
||||||
A consumer needs only to keep track of their account balance,
|
A consumer needs only to keep track of their account balance,
|
||||||
ascertain the cost of each outgoing request,
|
ascertain the cost of each outgoing request,
|
||||||
and produce valid signatures for a few dozen bytes of data at a time.
|
and produce valid signatures for a few dozen bytes of data at a time.
|
||||||
They don't need to watch the L1 and its a non-chatty protocol.
|
They don't need to watch the L1 and it's a non-chatty protocol.
|
||||||
The low resource needs opens it up to applications
|
The low resource needs opens it up to applications
|
||||||
on intermittently connected user devices such as laptops and mobile,
|
on intermittently connected user devices such as laptops and mobile,
|
||||||
and even micro-controllers.
|
and even micro-controllers.
|
||||||
High throughput remains achievable.
|
High throughput remains achievable.
|
||||||
|
|
||||||
A provider must track each subscriber's account, and periodically check the state of the L1.
|
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.
|
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.
|
The low resource needs for a provider means they have the ability to serve more with less.
|
||||||
|
|
||||||
## Hydra for QoL
|
## Hydra for QoL
|
||||||
|
|
||||||
When Hydra reaches a point of maturity that its plug and play,
|
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.
|
it's potentially far easier to deploy with Hydra then roll-your-own L2.
|
||||||
Isomorphic-ness gives Hydra incredible flexibility and generality.
|
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.
|
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 on interesting applications where
|
|
||||||
its far easier and more effective to reuse Hydra infra and modify it than creating your own L2 from scratch.
|
|
||||||
|
|
||||||
[^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).
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -3,160 +3,160 @@ title: Tracing Aiken Build
|
||||||
date: 2023-09-02
|
date: 2023-09-02
|
||||||
---
|
---
|
||||||
|
|
||||||
Aims:
|
Aims:
|
||||||
|
|
||||||
> Describe the pipeline and components getting from Aiken to Uplc.
|
> Describe the pipeline and components getting from Aiken to Uplc.
|
||||||
|
|
||||||
## The Preface
|
## The Preface
|
||||||
|
|
||||||
### Motivations
|
### Motivations
|
||||||
|
|
||||||
The motivation for writing this came from a desire to add additional features to Aiken not yet available.
|
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.
|
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.
|
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.
|
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?!_
|
A digression to answer _why would this be at all helpful?!_
|
||||||
Validator logic often needs a broad context throughout.
|
Validator logic often needs a broad context throughout.
|
||||||
How then to best factor code?
|
How then to best factor code?
|
||||||
Possible solutions:
|
Possible solutions:
|
||||||
|
|
||||||
1. Introduce types / structs
|
1. Introduce types / structs
|
||||||
2. Have functions with lots of arguments
|
2. Have functions with lots of arguments
|
||||||
3. Don't
|
3. Don't
|
||||||
|
|
||||||
The problems are:
|
The problems are:
|
||||||
|
|
||||||
1. Requires relentless constructing and deconstructing across the function call.
|
1. Requires relentless constructing and deconstructing across the function call.
|
||||||
This adds costs.
|
This adds costs.
|
||||||
2. Becomes tedious aligning the definition and function call.
|
2. Becomes tedious aligning the definition and function call.
|
||||||
3. Ends up with very long validators which are hard to unit test.
|
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.
|
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.
|
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.
|
To do either of these, we need to get to grips with the Aiken compilation pipeline.
|
||||||
|
|
||||||
### This won't age well
|
### This won't age well
|
||||||
|
|
||||||
Aiken is undergoing active development.
|
Aiken is undergoing active development.
|
||||||
This post started life with Aiken ~v1.14.
|
This post started life with Aiken ~v1.14.
|
||||||
Aiken v1.15 introduced reasonably significant changes to the compilation pipeline.
|
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,
|
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.
|
but this article will undoubtedly begin to diverge from the current code-base even before publishing.
|
||||||
|
|
||||||
### Limitations of narrating code
|
### Limitations of narrating code
|
||||||
|
|
||||||
Narrating code becomes a compromise between being honest and accurate, and being readable and digestible.
|
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 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.
|
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.
|
To redeem it, some (possibly large) sections remain black boxes.
|
||||||
|
|
||||||
## Aiken build
|
## Aiken build
|
||||||
|
|
||||||
Tracing `aiken build`, the pipeline is roughly:
|
Tracing `aiken build`, the pipeline is roughly:
|
||||||
|
|
||||||
```sample
|
```sample
|
||||||
. -> Project::read_source_files ->
|
. -> Project::read_source_files ->
|
||||||
Vec<Source> -> Project::parse_sources ->
|
Vec<Source> -> Project::parse_sources ->
|
||||||
ParsedModules -> Project::type_check ->
|
ParsedModules -> Project::type_check ->
|
||||||
CheckedModules -> CodeGenerator::build ->
|
CheckedModules -> CodeGenerator::build ->
|
||||||
AirTree -> AirTree::to_vec ->
|
AirTree -> AirTree::to_vec ->
|
||||||
Vec<Air> -> CodeGenerator::uplc_code_gen ->
|
Vec<Air> -> CodeGenerator::uplc_code_gen ->
|
||||||
Program / Term<Name> -> serialize ->
|
Program / Term<Name> -> serialize ->
|
||||||
.
|
.
|
||||||
```
|
```
|
||||||
|
|
||||||
We'll pick our way through these steps
|
We'll pick our way through these steps
|
||||||
|
|
||||||
At a high level we are trying to do something straightforward: reformulate Aiken code as Uplc.
|
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 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 `
|
Some Aiken expressions require more involved handling, for example an Aiken `If... If Else... Else `
|
||||||
must have the branches "nested" in Uplc.
|
must have the branches "nested" in Uplc.
|
||||||
Aiken has lots of nice-to-haves like pattern matching, modules, and generics;
|
Aiken has lots of nice-to-haves like pattern matching, modules, and generics;
|
||||||
Uplc has none of these.
|
Uplc has none of these.
|
||||||
|
|
||||||
### The Preamble
|
### The Preamble
|
||||||
|
|
||||||
#### Cli handling
|
#### Cli handling
|
||||||
|
|
||||||
The cli enters at `aiken/src/cmd/mod.rs` which parses the command.
|
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`),
|
With some establishing of context, the program enters `Project::build` (`crates/aiken-project/src/lib.rs`),
|
||||||
which in turn calls `Project::compile`.
|
which in turn calls `Project::compile`.
|
||||||
|
|
||||||
#### File crawl
|
#### File crawl
|
||||||
|
|
||||||
The program looks for Aiken files in both `./lib` and `./validator` sub-directories.
|
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.
|
For each it walks over all contents (recursively) looking for `.ak` extensions.
|
||||||
It treats these two sets of files a little differently.
|
It treats these two sets of files a little differently.
|
||||||
For example, only validator files can contain the special validator functions.
|
For example, only validator files can contain the special validator functions.
|
||||||
|
|
||||||
#### Parse and Type check
|
#### Parse and Type check
|
||||||
|
|
||||||
`Project::parse_sources` parses the module source code.
|
`Project::parse_sources` parses the module source code.
|
||||||
The heavy lifting is done by `aiken_lang::parser::module`, which is evaluated on each file.
|
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_,
|
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.
|
together with metadata like docstrings and the file path.
|
||||||
|
|
||||||
`Project::type_check` inspects the parsed modules and, as the name implies, checks the types.
|
`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.
|
It flags type level warnings and errors and constructs a hash map of `CheckedModule`s.
|
||||||
|
|
||||||
#### Code generator
|
#### Code generator
|
||||||
|
|
||||||
The code generator `CodeGenerator` (`aiken-lang/src/gen_uplc.rs`) is given
|
The code generator `CodeGenerator` (`aiken-lang/src/gen_uplc.rs`) is given
|
||||||
the definitions found from the previous step,
|
the definitions found from the previous step,
|
||||||
together with the plutus builtins.
|
together with the plutus builtins.
|
||||||
It has additional fields for things like debugging.
|
It has additional fields for things like debugging.
|
||||||
|
|
||||||
This is handed over to a `Blueprint` (`aiken-project/src/blueprint/mod.rs`).
|
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 blueprint does little more than find the validators on which to run the code gen.
|
||||||
The heavy lifting is done by `CodeGenerator::generate`.
|
The heavy lifting is done by `CodeGenerator::generate`.
|
||||||
|
|
||||||
We are now ready to take the source code and create plutus.
|
We are now ready to take the source code and create plutus.
|
||||||
|
|
||||||
### In the air
|
### In the air
|
||||||
|
|
||||||
Things become a bit intimidating at this point in terms of sheer lines of code:
|
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.
|
`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).
|
Aiken has its own _intermediate representation_ called `air` (as in Aiken Intermediate Representation).
|
||||||
Intermediate representations are common in compiled languages.
|
Intermediate representations are common in compiled languages.
|
||||||
`Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
`Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
||||||
Unsurprisingly, it looks a little bit like a language between Aiken and plutus.
|
Unsurprisingly, it looks a little bit like a language between Aiken and plutus.
|
||||||
|
|
||||||
In fact, Aiken has another intermediate representation: `AirTree`.
|
In fact, Aiken has another intermediate representation: `AirTree`.
|
||||||
This is constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and air.
|
This is constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and air.
|
||||||
|
|
||||||
#### Climbing the AirTree
|
#### Climbing the AirTree
|
||||||
|
|
||||||
Within `CodeGenerator::generate`, `CodeGenerator::build` is called on the function body.
|
Within `CodeGenerator::generate`, `CodeGenerator::build` is called on the function body.
|
||||||
This takes a `TypedExpr` and constructs and returns an `AirTree`.
|
This takes a `TypedExpr` and constructs and returns an `AirTree`.
|
||||||
The construction is recursive as it traverses the recursive `TypedExpr` data structure.
|
The construction is recursive as it traverses the recursive `TypedExpr` data structure.
|
||||||
More on what an airtree is and its construction below.
|
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.
|
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`.
|
The method which is called and uses this mutability of self is `self.assignment`.
|
||||||
It does so by
|
It does so by
|
||||||
|
|
||||||
```sample
|
```sample
|
||||||
- self.assignment
|
- self.assignment
|
||||||
└ self.expect_type_assign
|
└ self.expect_type_assign
|
||||||
└ self.code_gen_functions.insert
|
└ self.code_gen_functions.insert
|
||||||
```
|
```
|
||||||
|
|
||||||
and thus is creating a hashmap of all the functions that appear in the definition.
|
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.
|
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.)
|
(`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.
|
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`).
|
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
|
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.
|
that error if anything is wrong - which is what validators are.
|
||||||
|
|
||||||
`check_validator_args` again extends the airtree from the previous step,
|
`check_validator_args` again extends the airtree from the previous step,
|
||||||
and again calls `self.assignment` mutating self.
|
and again calls `self.assignment` mutating self.
|
||||||
Something interesting is happening here.
|
Something interesting is happening here.
|
||||||
Script context is the final argument of a validator - for any script purpose.
|
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.
|
`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.
|
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
|
Let's take a look at what AirTree actually is
|
||||||
|
@ -172,8 +172,8 @@ pub enum AirTree {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that `AirStatement` and `AirExpression` are mutually recursive definitions with `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.
|
Otherwise, it would be unclear from first inspection how tree-like this really is.
|
||||||
|
|
||||||
`AirExpression` has multiple constructors. These include (non-exhaustive)
|
`AirExpression` has multiple constructors. These include (non-exhaustive)
|
||||||
|
|
||||||
|
@ -183,55 +183,55 @@ Otherwise, it would be unclear from first inspection how tree-like this really i
|
||||||
- handling when and if
|
- handling when and if
|
||||||
- handling error and tracing
|
- handling error and tracing
|
||||||
|
|
||||||
`AirStatement` also has multiple constructors. These include
|
`AirStatement` also has multiple constructors. These include
|
||||||
|
|
||||||
- let assignments and named function definitions
|
- let assignments and named function definitions
|
||||||
- handling expect assignments
|
- handling expect assignments
|
||||||
- pattern matching
|
- pattern matching
|
||||||
- unwrapping data structures
|
- unwrapping data structures
|
||||||
|
|
||||||
Note that `AirTree` has many methods that are partial functions,
|
Note that `AirTree` has many methods that are partial functions,
|
||||||
as in there are possible states that are not considered legitimate
|
as in there are possible states that are not considered legitimate
|
||||||
at different points of its construction and use.
|
at different points of its construction and use.
|
||||||
For example `hoist_over` will throw an error if called on an `Expression`.
|
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.
|
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.
|
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.
|
However, the trade off is that it partially obfuscates what is a valid state where.
|
||||||
|
|
||||||
What is hoisting? Hoisting gives the airtree depth.
|
What is hoisting? Hoisting gives the airtree depth.
|
||||||
The motivation is that by the time we hit Uplc it is "generally better"
|
The motivation is that by the time we hit Uplc it is "generally better"
|
||||||
that
|
that
|
||||||
|
|
||||||
- function definitions appear once rather than being inlined multiple times
|
- function definitions appear once rather than being inlined multiple times
|
||||||
- the definition appears as close to use as possible
|
- the definition appears as close to use as possible
|
||||||
|
|
||||||
Hoisting creates tree paths.
|
Hoisting creates tree paths.
|
||||||
The final airtree to airtree step, `self.hoist_functions_to_validator`, traverses these 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.
|
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
|
In all this (several thousand?) LoC, it is essentially ascertaining in which node of the tree
|
||||||
to insert each function definition.
|
to insert each function definition.
|
||||||
In a resource constrained environment like plutus, this effort is warranted.
|
In a resource constrained environment like plutus, this effort is warranted.
|
||||||
|
|
||||||
At the same time this function deals with
|
At the same time this function deals with
|
||||||
|
|
||||||
- monomophisation - no more generics
|
- monomophisation - no more generics
|
||||||
- erasing opaque types
|
- erasing opaque types
|
||||||
|
|
||||||
Neither of which exist at the Uplc level.
|
Neither of which exist at the Uplc level.
|
||||||
|
|
||||||
#### Into Air
|
#### Into Air
|
||||||
|
|
||||||
The `to_vec : AirTree -> Vec<Air>` is much easier to digest.
|
The `to_vec : AirTree -> Vec<Air>` is much easier to digest.
|
||||||
For one, it is not evaluated in the context of the code generator,
|
For one, it is not evaluated in the context of the code generator,
|
||||||
and two, there is no mutation of the airtree.
|
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.
|
The function recursively takes nodes of the tree and maps them to entries in a mutable vector.
|
||||||
It flattens the tree to a vec.
|
It flattens the tree to a vec.
|
||||||
|
|
||||||
### Down to Uplc
|
### Down to Uplc
|
||||||
|
|
||||||
Next we go from `Vec<Air> -> Term<Name>`.
|
Next we go from `Vec<Air> -> Term<Name>`.
|
||||||
This step is a little more involved than the previous.
|
This step is a little more involved than the previous.
|
||||||
For one, this is executed in the context of the code generator.
|
For one, this is executed in the context of the code generator.
|
||||||
Moreover, the code generator is treated as mutable - ouch.
|
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`.
|
On further inspection we see that the only mutation is setting `self.needs_field_access = true`.
|
||||||
|
@ -242,45 +242,45 @@ As noted above, some of the mappings from air to terms are immediate like `Air::
|
||||||
Others are less so.
|
Others are less so.
|
||||||
Some examples:
|
Some examples:
|
||||||
|
|
||||||
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
||||||
- Lists in air have no immediate analogue in uplc
|
- Lists in air have no immediate analogue in uplc
|
||||||
- builtins, as in built-in functions (standard shorthand), have to be mediated
|
- 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.
|
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,
|
- user functions must be "uncurried", ie treated as a sequence of single argument functions,
|
||||||
and recursion must be handled
|
and recursion must be handled
|
||||||
- Do some magic in order to efficiently allow "record updates".
|
- Do some magic in order to efficiently allow "record updates".
|
||||||
|
|
||||||
#### Cranking the Optimizer
|
#### Cranking the Optimizer
|
||||||
|
|
||||||
There is a sequence of operations performed on the Uplc, mapping `Term<Name> -> Term<Name>`.
|
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:
|
This removes inconsequential parts of the logic which have been generated, including:
|
||||||
|
|
||||||
- removing application of the identity function
|
- removing application of the identity function
|
||||||
- directly substituting where apply lambda is applied to a constant or builtin
|
- 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
|
- 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,
|
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.
|
and so although there is a fair number of LoC, it's reasonably straightforward to follow.
|
||||||
Some are applied multiple times.
|
Some are applied multiple times.
|
||||||
|
|
||||||
### The End
|
### The End
|
||||||
|
|
||||||
The generated program can now be serialized and included in the blueprint.
|
The generated program can now be serialized and included in the blueprint.
|
||||||
|
|
||||||
### Plutus Core Signposting
|
### Plutus Core Signposting
|
||||||
|
|
||||||
All this fuss is to get us to a point where we can write Uplc - and good Uplc at that.
|
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.
|
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
|
The various design decisions and compilation steps make more sense
|
||||||
when we have a better understanding of the target language.
|
when we have a better understanding of the target language.
|
||||||
|
|
||||||
Uplc is a lambda calculus.
|
Uplc is a lambda calculus.
|
||||||
For a comprehensive definition on Uplc checkout the specification found
|
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.
|
[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.)
|
(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
|
If you're not at all familiar with lambda calculus I recommend
|
||||||
[an unpacking](https://crypto.stanford.edu/~blynn/lambda/) by Ben Lynn.
|
[an unpacking](https://crypto.stanford.edu/~blynn/lambda/) by Ben Lynn.
|
||||||
|
|
||||||
### What next?
|
### What next?
|
||||||
|
|
||||||
I think it would be helpful to have some examples... Watch this space.
|
I think it would be helpful to have some examples... Watch this space.
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Get all "navbar-burger" elements
|
// Get all "navbar-burger" elements
|
||||||
var $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
var $navbarBurgers = Array.prototype.slice.call(
|
||||||
|
document.querySelectorAll(".navbar-burger"),
|
||||||
|
0,
|
||||||
|
);
|
||||||
// Check if there are any navbar burgers
|
// Check if there are any navbar burgers
|
||||||
if ($navbarBurgers.length > 0) {
|
if ($navbarBurgers.length > 0) {
|
||||||
// Add a click event on each of them
|
// Add a click event on each of them
|
||||||
$navbarBurgers.forEach(function ($el) {
|
$navbarBurgers.forEach(function ($el) {
|
||||||
$el.addEventListener('click', function () {
|
$el.addEventListener("click", function () {
|
||||||
// Get the "main-nav" element
|
// Get the "main-nav" element
|
||||||
var $target = document.getElementById('main-nav');
|
var $target = document.getElementById("main-nav");
|
||||||
// Toggle the class on "main-nav"
|
// Toggle the class on "main-nav"
|
||||||
$target.classList.toggle('hidden');
|
$target.classList.toggle("hidden");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
86
flake.lock
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": {
|
||||||
|
|
63
flake.nix
63
flake.nix
|
@ -28,10 +28,11 @@
|
||||||
# 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;
|
||||||
|
@ -59,27 +60,53 @@
|
||||||
"-XImportQualifiedPost"
|
"-XImportQualifiedPost"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.prettier.enable = true;
|
programs.prettier.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
# Equivalent to inputs'.nixpkgs.legacyPackages.hello;
|
# Equivalent to inputs'.nixpkgs.legacyPackages.hello;
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default =
|
||||||
inputsFrom = [
|
let
|
||||||
config.haskellProjects.default.outputs.devShell
|
menu = pkgs.writeShellScriptBin "menu"
|
||||||
config.flake-root.devShell
|
''
|
||||||
];
|
echo -e "\nCommands available: \n${
|
||||||
packages = with pkgs; [
|
builtins.foldl' (x: y: x + " -> " + (pkgs.lib.getName y) + "\n") "" my-packages
|
||||||
caddy
|
}"
|
||||||
nil
|
'';
|
||||||
nodePackages_latest.vscode-langservers-extracted
|
my-packages = [
|
||||||
nodePackages_latest.tailwindcss
|
menu
|
||||||
nodePackages_latest.typescript-language-server
|
build
|
||||||
haskellPackages.hakyll
|
watch
|
||||||
zlib
|
deploy
|
||||||
];
|
];
|
||||||
};
|
build = pkgs.writeShellScriptBin "build" ''
|
||||||
|
tailwindcss -i ./content/css/main.css -o ./content/css/mini.css --minify
|
||||||
|
cabal run site -- build
|
||||||
|
'';
|
||||||
|
watch = pkgs.writeShellScriptBin "watch" ''
|
||||||
|
tailwindcss -i ./content/css/main.css -o ./content/css/mini.css --minify
|
||||||
|
cabal run site -- watch
|
||||||
|
'';
|
||||||
|
deploy = pkgs.writeShellScriptBin "deploy" ''
|
||||||
|
rsync -r --delete ./docs/* genesis:/var/www/kompactio-landing/
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
inputsFrom = [
|
||||||
|
config.haskellProjects.default.outputs.devShell
|
||||||
|
config.flake-root.devShell
|
||||||
|
];
|
||||||
|
packages = with pkgs; [
|
||||||
|
caddy
|
||||||
|
nil
|
||||||
|
nodePackages_latest.vscode-langservers-extracted
|
||||||
|
nodePackages_latest.tailwindcss
|
||||||
|
nodePackages_latest.typescript-language-server
|
||||||
|
haskellPackages.hakyll
|
||||||
|
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-
|
||||||
|
|
|
@ -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
|
38
site.hs
38
site.hs
|
@ -1,9 +1,9 @@
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
import Data.Monoid (mappend)
|
|
||||||
import Hakyll
|
|
||||||
import System.FilePath (splitExtension, joinPath, splitDirectories, replaceExtension)
|
|
||||||
|
|
||||||
|
import Data.Monoid (mappend)
|
||||||
|
import Hakyll
|
||||||
|
import System.FilePath (joinPath, replaceExtension, splitDirectories, splitExtension)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
@ -17,38 +17,43 @@ main = hakyll $ do
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
|
|
||||||
match "content/scripts/*" $ do
|
match "content/scripts/*" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
|
|
||||||
match "content/css/*" $ do
|
match "content/css/*" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile compressCssCompiler
|
compile compressCssCompiler
|
||||||
|
|
||||||
match "content/fonts/*" $ do
|
match "content/fonts/*" $ do
|
||||||
route rmPrefix
|
route rmPrefix
|
||||||
compile copyFileCompiler
|
compile copyFileCompiler
|
||||||
|
|
||||||
match "content/posts/*.md" $ do
|
match "content/posts/*.md" $ do
|
||||||
route rmPrefixMd
|
route rmPrefixMd
|
||||||
compile $ pandocCompiler
|
compile $
|
||||||
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
pandocCompiler
|
||||||
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
>>= loadAndApplyTemplate "templates/post.html" postCtx
|
||||||
>>= relativizeUrls
|
>>= loadAndApplyTemplate "templates/default.html" postCtx
|
||||||
|
>>= relativizeUrls
|
||||||
|
|
||||||
create ["blog.html"] $ do
|
create ["blog.html"] $ do
|
||||||
route idRoute
|
route idRoute
|
||||||
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/*" $ do
|
||||||
|
-- compile $
|
||||||
|
-- pandocCompilerWith x
|
||||||
|
|
||||||
|
|
||||||
match "content/index.md" $ do
|
match "content/index.md" $ do
|
||||||
route rmPrefixMd
|
route rmPrefixMd
|
||||||
|
@ -63,18 +68,17 @@ 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
|
||||||
|
|
||||||
rmPrefixInner :: FilePath -> FilePath
|
rmPrefixInner :: FilePath -> FilePath
|
||||||
rmPrefixInner = joinPath . tail . splitDirectories
|
rmPrefixInner = joinPath . tail . splitDirectories
|
||||||
|
|
||||||
rmPrefix :: Routes
|
rmPrefix :: Routes
|
||||||
rmPrefix = customRoute $ rmPrefixInner . toFilePath
|
rmPrefix = customRoute $ rmPrefixInner . toFilePath
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -9,4 +7,4 @@
|
||||||
|
|
||||||
<section class="py-6 px-2 flex flex-col gap-12">
|
<section class="py-6 px-2 flex flex-col gap-12">
|
||||||
$partial("templates/post-list.html")$
|
$partial("templates/post-list.html")$
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,42 +1,41 @@
|
||||||
<!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" />
|
||||||
<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) &&
|
||||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
}
|
<body
|
||||||
updateTheme()
|
class="bg-white text-gray-900 min-h-screen dark:bg-gradient-to-br dark:from-slate-950 dark:to-black dark:text-white"
|
||||||
</script>
|
>
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<hr />
|
||||||
|
$partial("templates/nav.html")$
|
||||||
|
<hr />
|
||||||
|
$body$
|
||||||
|
<hr />
|
||||||
|
$partial("templates/footer.html")$
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
<body class="bg-white text-gray-900 min-h-screen
|
<script src="/scripts/prism.js"></script>
|
||||||
dark:bg-gradient-to-br dark:from-slate-950 dark:to-black dark:text-white">
|
</html>
|
||||||
<div class="container mx-auto ">
|
|
||||||
<hr />
|
|
||||||
$partial("templates/nav.html")$
|
|
||||||
<hr />
|
|
||||||
$body$
|
|
||||||
<hr />
|
|
||||||
$partial("templates/footer.html")$
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
<script src="/scripts/prism.js"></script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,22 +1,34 @@
|
||||||
<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>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -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>
|
||||||
|
<div><span class="text-gray-400">· ·</span> dapp <- lean dev</div>
|
||||||
|
<div><span class="text-gray-400">· ·</span> run dapp</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
|
@ -4,4 +4,4 @@ $partial("templates/services.html")$
|
||||||
<hr />
|
<hr />
|
||||||
$partial("templates/pricing.html")$
|
$partial("templates/pricing.html")$
|
||||||
<hr />
|
<hr />
|
||||||
$partial("templates/contact.html")$
|
$partial("templates/contact.html")$
|
||||||
|
|
|
@ -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,19 +23,18 @@
|
||||||
}
|
}
|
||||||
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>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -2,11 +2,9 @@
|
||||||
$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>
|
||||||
$endfor$
|
$endfor$
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
<article class="mx-auto px-4 max-w-prose">
|
<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$
|
</article>
|
||||||
</section>
|
|
||||||
</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.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-48">
|
<div class="flex-1">
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">## deliverable</div>
|
||||||
## deliverable
|
<p class="text-gray-800 dark:text-gray-200 mt-4">Output-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">
|
|
||||||
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,35 +1,30 @@
|
||||||
<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>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
Loading…
Reference in New Issue