add dark mode
This commit is contained in:
parent
05969ce7cc
commit
f3b88b8446
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,177 @@ Aims:
|
||||||
|
|
||||||
- Describe the pipeline, and components getting from aiken to uplc.
|
- Describe the pipeline, and components getting from aiken to uplc.
|
||||||
|
|
||||||
|
## Preface
|
||||||
|
|
||||||
|
Aiken is undergoing active development.
|
||||||
|
This post was started Aiken ~v1.14.
|
||||||
|
With Aiken v1.15, there were already reasonably significant changes to the compilation pipeline.
|
||||||
|
The word is that there aren't as big changes in the near future, but
|
||||||
|
this article will undoubtably begin to diverge from the current codebase even before publishing.
|
||||||
|
|
||||||
|
## Aiken build
|
||||||
|
|
||||||
|
Tracing `aiken build`, the pipeline is roughly something like:
|
||||||
|
```
|
||||||
|
. -> Project::read_source_files ->
|
||||||
|
Vec<Source> -> Project::parse_sources ->
|
||||||
|
ParsedModules -> Project::type_check ->
|
||||||
|
CheckedModules -> CodeGenerator::build ->
|
||||||
|
AirTree -> AirTree::to_vec ->
|
||||||
|
Vec<Air> -> CodeGenerator::uplc_code_gen ->
|
||||||
|
Program / Term<Name> -> serialize ->
|
||||||
|
.
|
||||||
|
```
|
||||||
|
We'll pick our way through these steps
|
||||||
|
|
||||||
|
At a high level we are trying to do something straightforward: reformulate aiken code as uplc.
|
||||||
|
Some aiken expressions are relatively easy to handle for example an aiken `Int` goes to an `Int` in uplc.
|
||||||
|
Some aiken expressions require more involved handling, for example an aiken `If... If Else... Else `
|
||||||
|
must have the branches "nested" in uplc.
|
||||||
|
|
||||||
|
### The Preamble
|
||||||
|
|
||||||
|
#### cli handling
|
||||||
|
|
||||||
|
The cli enters at `aiken/src/cmd/mod.rs` which parses the command.
|
||||||
|
With some establishing of context, the program enters `Project::build` (`crates/aiken-project/src/lib.rs`),
|
||||||
|
which in turn calls `Project::compile`.
|
||||||
|
|
||||||
|
#### File crawl
|
||||||
|
|
||||||
|
The program looks for aiken files in both `./lib` and `./validator` subdirs.
|
||||||
|
For each it walks over all contents (recursively) looking for `.ak` extensions.
|
||||||
|
It treats these two sets of files a little differently.
|
||||||
|
Only validator files can contain the special validator functions.
|
||||||
|
|
||||||
|
#### Parse and Type check
|
||||||
|
|
||||||
|
`Project::parse_sources` parses the module source code.
|
||||||
|
The heavy lifting is done by `aiken_lang::parser::module`, which is evaluated on each file.
|
||||||
|
It produces a `Module` containing a list of parsed definitions of the file: functions, types _etc_,
|
||||||
|
together with "metadata" like docstrings and the file path.
|
||||||
|
|
||||||
|
`Project::type_check` inspects the parsed modules and, as the name implies, checks the types.
|
||||||
|
It flags type level warnings and errors.
|
||||||
|
It constructs a hash map of `CheckedModule`s.
|
||||||
|
|
||||||
|
#### Code generator
|
||||||
|
|
||||||
|
The code generator `CodeGenerator` (`aiken-lang/src/gen_uplc.rs`) is given
|
||||||
|
the definitions found from the previous step,
|
||||||
|
together with the plutus builtins.
|
||||||
|
It has additional fields for things like debugging.
|
||||||
|
|
||||||
|
This is handed over to a `Blueprint` (`aiken-project/src/blueprint/mod.rs`).
|
||||||
|
A blueprint does little more than find the validators on which to run the code gen.
|
||||||
|
The heavy lifting is done by `CodeGenerator::generate`.
|
||||||
|
|
||||||
|
We are now ready to take the source code and create plutus.
|
||||||
|
|
||||||
|
### Up in the air
|
||||||
|
|
||||||
|
Things become a bit intimidating at this point in terms of sheer lines of code:
|
||||||
|
`gen_uplc.rs` and three modules in `gen_uplc/` totals > 8500 LoC.
|
||||||
|
|
||||||
|
Aiken has its own _intermediate representation_ called `air` (as in Aiken Intermediate Representation).
|
||||||
|
These are common in compiled languages.
|
||||||
|
`Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
||||||
|
Unsurprisingly, it looks little bit like a language between aiken and plutus.
|
||||||
|
|
||||||
|
In fact, Aiken has another intermediate representation: `AirTree`.
|
||||||
|
This is constructed between the `TypedExpr` and `Vec<Air>` ie between parsed aiken and air.
|
||||||
|
|
||||||
|
#### AirTree
|
||||||
|
|
||||||
|
Within `CodeGenerator::generate`, `CodeGenerator::build` is called on the function body.
|
||||||
|
This constructs and returns an `AirTree`.
|
||||||
|
More on what an airtree is and its construction below.
|
||||||
|
At the same time `self` is treated as `mut`, so we need to keep an eye on this too.
|
||||||
|
The method which is called and uses this mutability of self is `self.assignment`.
|
||||||
|
It does so by
|
||||||
|
```sample
|
||||||
|
self.assignment >> self.expect_type_assign >> self.code_gen_functions.insert
|
||||||
|
```
|
||||||
|
and thus is creating a hashmap of all the functions that appear in the definition.
|
||||||
|
(`self.handle_each_clause` is also called with `mut` which in turn calls `self.build` for which `mut` it is needed.
|
||||||
|
`self.clause_pattern` is called with `mut` but it isn't used.)
|
||||||
|
|
||||||
|
###### Codegen assignment
|
||||||
|
|
||||||
|
~200 LoC
|
||||||
|
|
||||||
|
###### Codegen expect type assign
|
||||||
|
|
||||||
|
~400 LoC
|
||||||
|
|
||||||
|
###### ... Back to build
|
||||||
|
|
||||||
|
Validators in aiken are boolean functions while in uplc they are unit-valued (aka void-valued) functions.
|
||||||
|
Thus the airtree is wrapped such that `false` results in an error (`wrap_validator_condition`).
|
||||||
|
(Ed: I don't know why there is a prevailing thought that boolean functions are preferable than functions
|
||||||
|
that simply error if anything is wrong.)
|
||||||
|
|
||||||
|
`check_validator_args` again extends the airtree from the previous step,
|
||||||
|
and again calls `self.assignment` mutating self.
|
||||||
|
Something interesting is happening here.
|
||||||
|
Script context is the final argument of a validator - for any script purpose.
|
||||||
|
`check_validator_args` treats the script context like it is an unused argument.
|
||||||
|
We'll circle back to how this works later on.
|
||||||
|
|
||||||
|
Next we encounter
|
||||||
|
```rust
|
||||||
|
AirTree::no_op().hoist_over(validator_args_tree);
|
||||||
|
```
|
||||||
|
Its not very apparent why we need to do this. Let's look ahead and consider this later.
|
||||||
|
|
||||||
|
The final airtree to step(s) are in `self.hoist_functions_to_validator`.
|
||||||
|
TODO: What happens here?!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Note that `AirTree` and its methods aren't fully typesafe.
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The AirTree has the following definition
|
||||||
|
```rust
|
||||||
|
pub enum AirTree {
|
||||||
|
Statement {
|
||||||
|
statement: AirStatement,
|
||||||
|
hoisted_over: Option<Box<AirTree>>,
|
||||||
|
},
|
||||||
|
Expression(AirExpression),
|
||||||
|
UnhoistedSequence(Vec<AirTree>),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
We can see it has a tree-like structure, as the name suggests.
|
||||||
|
|
||||||
|
`AirExpression` has multiple constructors. These include (non-exhaustive)
|
||||||
|
- air primitives (including all the ones that appear in plutus)
|
||||||
|
- constructors `Call` and `Fn` to handle functions
|
||||||
|
- binary and unary operators
|
||||||
|
- handling when and if
|
||||||
|
- error and tracing
|
||||||
|
|
||||||
|
`AirStatement` also has multiple constructors.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for handling functions, `plutus primitives, along with
|
||||||
|
An `AirStatement`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Down to uplc
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Air
|
## Air
|
||||||
|
|
||||||
Aiken compiles aiken code to uplc via _air_:
|
Aiken compiles aiken code to uplc via _air_:
|
||||||
|
|
1
site.hs
1
site.hs
|
@ -58,6 +58,7 @@ main = hakyll $ do
|
||||||
getResourceBody
|
getResourceBody
|
||||||
>>= applyAsTemplate indexCtx
|
>>= applyAsTemplate indexCtx
|
||||||
>>= loadAndApplyTemplate "templates/index.html" indexCtx
|
>>= loadAndApplyTemplate "templates/index.html" indexCtx
|
||||||
|
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
||||||
>>= relativizeUrls
|
>>= relativizeUrls
|
||||||
|
|
||||||
match "templates/*" $ compile templateBodyCompiler
|
match "templates/*" $ compile templateBodyCompiler
|
||||||
|
|
|
@ -8,8 +8,12 @@ module.exports = {
|
||||||
extend: {},
|
extend: {},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
'sans' : ['jetbrains-mono',],
|
'sans' : ['jetbrains-mono',],
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
typography: (theme) => ({}),
|
||||||
|
},
|
||||||
|
darkMode: 'class',
|
||||||
|
variants: {},
|
||||||
|
// plugins: [require('@tailwindcss/typography')],
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<header class="text-3xl">
|
<header class="text-3xl">
|
||||||
# blog
|
# blog
|
||||||
</header>
|
</header>
|
||||||
<div class="text-gray-700 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>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<header class="text-3xl">
|
<header class="text-3xl">
|
||||||
# contact
|
# contact
|
||||||
</header>
|
</header>
|
||||||
<div class="text-gray-700 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>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html class="">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
@ -9,7 +9,24 @@
|
||||||
<title>$title$</title>
|
<title>$title$</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<script>
|
||||||
|
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||||
|
function updateTheme() {
|
||||||
|
if (
|
||||||
|
localStorage.theme === 'dark' ||
|
||||||
|
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
) {
|
||||||
|
document.documentElement.classList.add('dark')
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
updateTheme()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<body class="bg-white text-gray-900 min-h-screen
|
||||||
|
dark:bg-gradient-to-br dark:from-slate-950 dark:to-black dark:text-white">
|
||||||
<div class="container mx-auto ">
|
<div class="container mx-auto ">
|
||||||
<hr />
|
<hr />
|
||||||
$partial("templates/nav.html")$
|
$partial("templates/nav.html")$
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<section id="footer" class="py-12 px-2 flex flex-row gap-12 mx-2 sm:mx-4 items-start justify-between">
|
<section id="footer" class="py-12 px-2 flex flex-row gap-12 mx-2 sm:mx-4 items-start justify-between
|
||||||
<div class="text-sm text-gray-700">
|
text-gray-800 dark:text-gray-200 dark:fill-white">
|
||||||
|
<div class="text-sm">
|
||||||
® 2023 kompact.io ™ All Rights Reserved.
|
® 2023 kompact.io ™ All Rights Reserved.
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 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. -->
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
⟨K⟩
|
⟨K⟩
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 truncate">
|
<div class="flex flex-col gap-2 truncate">
|
||||||
<div>withKompact $ <span class="text-red-500">do</span> </div>
|
<div>withKompact $ <span class="text-red-500 dark:text-yellow-400">do</span> </div>
|
||||||
<div><span class="text-gray-400">· ·</span> dapp <- lean dev </div>
|
<div><span class="text-gray-400">· ·</span> dapp <- lean dev </div>
|
||||||
<div><span class="text-gray-400">· ·</span> run dapp </div>
|
<div><span class="text-gray-400">· ·</span> run dapp </div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,19 +1,3 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.png">
|
|
||||||
<title>$title$</title>
|
|
||||||
<link href="/css/mini.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container mx-auto">
|
|
||||||
<hr />
|
|
||||||
$partial("templates/nav.html")$
|
|
||||||
<hr />
|
|
||||||
$partial("templates/hero.html")$
|
$partial("templates/hero.html")$
|
||||||
<hr />
|
<hr />
|
||||||
$partial("templates/services.html")$
|
$partial("templates/services.html")$
|
||||||
|
@ -21,10 +5,3 @@
|
||||||
$partial("templates/pricing.html")$
|
$partial("templates/pricing.html")$
|
||||||
<hr />
|
<hr />
|
||||||
$partial("templates/contact.html")$
|
$partial("templates/contact.html")$
|
||||||
<hr />
|
|
||||||
$partial("templates/footer.html")$
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -2,11 +2,28 @@
|
||||||
<div class="relative flex h-16 items-center justify-between">
|
<div class="relative flex h-16 items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
Kompact.io
|
<span class="hidden sm:inline">Kompact.io</span>
|
||||||
|
<span class="inline sm:hidden">K.io</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ul class="flex flex-row gap-4 md:gap-8">
|
<ul class="flex flex-row gap-4 md:gap-8">
|
||||||
|
<li>
|
||||||
|
<button onClick="
|
||||||
|
(() => {
|
||||||
|
if (!('theme' in localStorage)) {
|
||||||
|
localStorage.theme = 'light'
|
||||||
|
}
|
||||||
|
console.log('*', localStorage.theme)
|
||||||
|
if (localStorage.theme === 'light') {
|
||||||
|
localStorage.theme = 'dark'
|
||||||
|
} else {
|
||||||
|
localStorage.theme = 'light'
|
||||||
|
}
|
||||||
|
updateTheme()
|
||||||
|
})()
|
||||||
|
">◧</button>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/index.html#contact">
|
<a href="/index.html#contact">
|
||||||
contact
|
contact
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
$for(posts)$
|
$for(posts)$
|
||||||
<li class="mt-4">
|
<li class="mt-4">
|
||||||
<a href="$url$">
|
<a href="$url$">
|
||||||
<span class="text-gray-700">
|
<span class="text-gray-800 dark:text-gray-200">
|
||||||
$date$ ::
|
$date$ ::
|
||||||
</span>
|
</span>
|
||||||
$title$
|
$title$
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<header class="text-3xl">
|
<header class="text-3xl">
|
||||||
# pricing
|
# pricing
|
||||||
</header>
|
</header>
|
||||||
<div class="text-gray-700 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Plutus development has traditionally meant long development schedules, and expensive ( $ 25k+/mo FTE) engineers.
|
Plutus development has traditionally meant long development schedules, and expensive ( $ 25k+/mo FTE) engineers.
|
||||||
We can work with you at competitive rates in either deliverable or retainer based engagements.
|
We can work with you at competitive rates in either deliverable or retainer based engagements.
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,16 +11,16 @@
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">
|
||||||
## retainer
|
## retainer
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Time-based
|
Time-based
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Still figuring out your project scope?
|
Still figuring out your project scope?
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<div 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>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<div 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,17 +28,17 @@
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">
|
||||||
## deliverable
|
## deliverable
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Output-based
|
Output-based
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<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>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<div 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 UX to validator complexity.
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 mt-4">
|
<div class="text-gray-800 dark:text-gray-200 mt-4">
|
||||||
Once settled we'll begin the implementation phase and finally integration phase.
|
Once settled we'll begin the implementation phase and finally integration phase.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<header class="text-3xl">
|
<header class="text-3xl">
|
||||||
# services
|
# services
|
||||||
</header>
|
</header>
|
||||||
<div class="text-gray-700 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>
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">
|
||||||
## strategy
|
## strategy
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 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>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">
|
||||||
## implementation
|
## implementation
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 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>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<div class="text-1xl font-bold">
|
<div class="text-1xl font-bold">
|
||||||
## deployment
|
## deployment
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-700 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>
|
||||||
|
|
Loading…
Reference in New Issue