This commit is contained in:
waalge 2025-02-16 11:30:41 +00:00
parent 712c7cac44
commit 194492234e
31 changed files with 2977 additions and 550 deletions

View File

@ -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/
```

View File

@ -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

View File

@ -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;
}

View File

@ -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.

View File

@ -1,3 +1,5 @@
--- ---
title: Kompact.io title: Kompact.io
--- ---
## Hero

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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": {

View File

@ -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-

View File

@ -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

15
site.cabal Normal file
View File

@ -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
View File

@ -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

View File

@ -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: [],
} };

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"
&reg; 2023 kompact.io &trade; All Rights Reserved. >
</div> <div class="text-sm">&reg; 2023 kompact.io &trade; 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>

View File

@ -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 &#36; <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 &#36;
<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>

View File

@ -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")$

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 ( &#36; 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 ( &#36; 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>

View File

@ -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>

View File

@ -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>

View File

@ -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" />