Skip to content

Commit

Permalink
docs: organize available docs (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
scarmuega authored Sep 18, 2024
1 parent e6a51be commit 44ae624
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 212 deletions.
11 changes: 4 additions & 7 deletions docs/components/hero.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ export function Hero() {
return (
<div className="py-10 md:p-20">
<div className="mb-5 text-center">
<div className="flex justify-center">
<img src="/logo.png" alt="Hollow logo" className="mb-4" />
</div>
<h1 className="flex flex-col flex-wrap font-bold text-4xl md:text-6xl lg:text-7xl dark:text-gray-200">
SDK for building dApps
<h1 className="flex flex-col flex-wrap font-bold text-4xl md:text-4xl lg:text-6xl dark:text-gray-200">
Balius: Headless dApp SDK
</h1>
</div>

<div className="mb-8 max-w-3xl text-center mx-auto">
<p className="md:text-lg text-gray-600 dark:text-gray-400">
Hollow SDK streamlines the development of headless Cardano dApps by providing essential components out-of-the-box. Developers can focus on their business logic while leveraging the SDK's plumbing for on-chain interactions, promoting code reusability and scalability.
<p className="md:text-2xl text-gray-600 dark:text-gray-400">
Balius SDK streamlines the development of headless dApps for UTxO-based blockchains. It decouples business logic from on-chain interactions, promoting code reusability and scalability.
</p>
</div>

Expand Down
15 changes: 12 additions & 3 deletions docs/pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@
}
},
"introduction": "Introduction",
"nomenclature": "Nomenclature",
"events": "Events",
"order-book-example": "Order book example"
"quickstart": "Quickstart",
"-- Reference --": {
"type": "separator",
"title": "Reference"
},
"events": "Event Types",
"-- Examples --": {
"type": "separator",
"title": "Examples"
},
"order-book": "Order Book",
"marketplace": "Marketplace"
}
129 changes: 6 additions & 123 deletions docs/pages/introduction.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Introduction

Hollow is an SDK for building Headless Cardano dApps.

## About dApps

:thinking: the term "dApp" is already quite ambiguous, and now we also have "headless dApps"? WTF is a headless dApp?
Expand Down Expand Up @@ -38,6 +36,7 @@ This flexibility is a double-edged sword. For sure, is nice for developers to ch
- re-invent the wheel: each project ends up building their own middleware for interacting with the blockchain (reading on-chain data, submitting transactions, etc).

The end result is "snowflake" apps, each one is unique.

## Headless Apps

"Headless dApp" refers to the idea of decoupling the business logic from the external context by forcing all inputs and outputs through a well-defined interface. To run your app, you'll need a generic runtime that knows how to connect your business logic to the outside world[^4].
Expand All @@ -52,11 +51,6 @@ This strict separation of concerns provides several benefits:
The `Hollow` SDK is meant to provide the required artifacts to build headless Cardano dApps in a developer friendly way. It provides all of the plumbing out-of-the-box, you just need to relax and enjoy the ride.


[^1]: Keeping the on-chain code as small as possible it's very desirable trait. On-chain code is executed one time by each node of the blockchain. This is required as part of the consensus, but very redundant from the computational perspective.
[^2]: of course, one could inspect the script on-chain and tell the difference between a real validator and a mock one, but no difference whatsoever from the UX perspective.
[^3]: everything that lives outside of the blockchain is usually referred to as "off-chain". It's a little bit egocentric if you ask me, but serves its purpose. In our experience, there's usually much more off-chain code than on-chain.
[^4]: this concept is very similar to "hexagonal architecture" or "clean architecture". If this idea of strict separation of concerns resonates with you, I encourage you to take a look at those patterns too.

## What does it look like?

The introduction sounds good and very fancy but you're not convinced until you see some code, right?
Expand Down Expand Up @@ -105,120 +99,9 @@ fn on_asset_sold(utxo: UTxO) -> Result<()> {
}
```

## Footnotes

## Putting it all together

The following code shows what the off-chain code looks like for basic NFT marketplace that uses the `Hollow` SDK.

The idea is simple: the marketplace address can lock NFT and release them only if the correct price for the NFT is paid. To accomplish that, our off-chain component is going to do the following:

- for each time our marketplace address receives an UTxO representing an buyable asset, we store a reference in database table.
- for each time our marketplace address releases an UTxO representing a buyable asset, we remove it from our database table.
- if someone wants to query the list available assets, we lookup available items in the db and provide them as a json values.
- if someone want to buy one of the available assets, we create a partial transaction and return it for the user to balance & sign.

Here's the code:

::warning:: this is WIP, specific function names and structures will most likely suffer some changes. The essence of the framework and the semantic meaning of the artifacts will remain.

```rust
const MARKETPLACE: &str = "addr1xxx";

#[derive(Datum)]
struct AssetDatum {
price: PlutusInt,
}

#[chain_event]
#[match_inbound_utxo(to_address=MARKETPLACE)]
fn on_new_asset(utxo: UTxO) -> Result<()> {
let db = use_extension::<Database>();

let datum = utxo.datum_as::<AssetDatum>();

if datum.is_none() {
bail!("unexpected utxo");
}

for asset in utxo.assets() {
db.execute(
"INSERT INTO (policy, asset, price) VALUES ({}, {}, {})",
asset.policy_id,
asset.asset_name,
datum.price,
);
}

Ok(())
}

#[chain_event]
#[match_outbound_utxo(from_address=MARKETPLACE)]
fn on_asset_sold(utxo: UTxO) -> Result<()> {
let db = use_extension::<Database>();

for asset in utxo.assets() {
db.execute(
"DELETE FROM assets WHERE policy={} and asset={}",
asset.policy_id,
asset.asset_name,
);
}

Ok(())
}

#[extrinsic_event]
#[match_http_route(path = "/assets", method = "GET")]
fn query_assets() -> Result<JsonValue> {
let db = use_extension::<Database>();

let assets = db.query_all("SELECT * from assets;").to_json();

Ok(assets)
}

#[derive(Serialize, Deserialize)]
struct Order {
policy_id: String,
asset_name: String,
buyer: Address,
}

#[extrinsic_event]
#[match_http_route(path = "/orders", method = "POST")]
fn create_order(params: Order) -> Result<PartialTx> {
let db = use_extension::<Database>();

let asset = db.query_first(
"SELECT * from assets WHERE policy={}, name={};",
params.policy_id,
params.asset_name,
);

if asset.is_none() {
bail!("we don't have that asset");
}

let market = party_from_address(MARKETPLACE);
let buyer = party_from_address(params.buyer);

let tx = TxBuilder::new()
.transfer_asset(
market, // from party
buyer, // to party
params.policy_id,
params.asset_name,
TransferQuantity::All,
)
.output_ada(
market, // to party
asset.price, // lovelace amount
)
.build();

Ok(tx)
}
```


[^1]: Keeping the on-chain code as small as possible it's very desirable trait. On-chain code is executed one time by each node of the blockchain. This is required as part of the consensus, but very redundant from the computational perspective.
[^2]: of course, one could inspect the script on-chain and tell the difference between a real validator and a mock one, but no difference whatsoever from the UX perspective.
[^3]: everything that lives outside of the blockchain is usually referred to as "off-chain". It's a little bit egocentric if you ask me, but serves its purpose. In our experience, there's usually much more off-chain code than on-chain.
[^4]: this concept is very similar to "hexagonal architecture" or "clean architecture". If this idea of strict separation of concerns resonates with you, I encourage you to take a look at those patterns too.
116 changes: 116 additions & 0 deletions docs/pages/marketplace.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Marketplace Example

The following code shows what the off-chain code looks like for basic NFT marketplace that uses the `Hollow` SDK.

The idea is simple: the marketplace address can lock NFT and release them only if the correct price for the NFT is paid. To accomplish that, our off-chain component is going to do the following:

- for each time our marketplace address receives an UTxO representing an buyable asset, we store a reference in database table.
- for each time our marketplace address releases an UTxO representing a buyable asset, we remove it from our database table.
- if someone wants to query the list available assets, we lookup available items in the db and provide them as a json values.
- if someone want to buy one of the available assets, we create a partial transaction and return it for the user to balance & sign.

Here's the code:

::warning:: this is WIP, specific function names and structures will most likely suffer some changes. The essence of the framework and the semantic meaning of the artifacts will remain.

```rust
const MARKETPLACE: &str = "addr1xxx";

#[derive(Datum)]
struct AssetDatum {
price: PlutusInt,
}

#[chain_event]
#[match_inbound_utxo(to_address=MARKETPLACE)]
fn on_new_asset(utxo: UTxO) -> Result<()> {
let db = use_extension::<Database>();

let datum = utxo.datum_as::<AssetDatum>();

if datum.is_none() {
bail!("unexpected utxo");
}

for asset in utxo.assets() {
db.execute(
"INSERT INTO (policy, asset, price) VALUES ({}, {}, {})",
asset.policy_id,
asset.asset_name,
datum.price,
);
}

Ok(())
}

#[chain_event]
#[match_outbound_utxo(from_address=MARKETPLACE)]
fn on_asset_sold(utxo: UTxO) -> Result<()> {
let db = use_extension::<Database>();

for asset in utxo.assets() {
db.execute(
"DELETE FROM assets WHERE policy={} and asset={}",
asset.policy_id,
asset.asset_name,
);
}

Ok(())
}

#[extrinsic_event]
#[match_http_route(path = "/assets", method = "GET")]
fn query_assets() -> Result<JsonValue> {
let db = use_extension::<Database>();

let assets = db.query_all("SELECT * from assets;").to_json();

Ok(assets)
}

#[derive(Serialize, Deserialize)]
struct Order {
policy_id: String,
asset_name: String,
buyer: Address,
}

#[extrinsic_event]
#[match_http_route(path = "/orders", method = "POST")]
fn create_order(params: Order) -> Result<PartialTx> {
let db = use_extension::<Database>();

let asset = db.query_first(
"SELECT * from assets WHERE policy={}, name={};",
params.policy_id,
params.asset_name,
);

if asset.is_none() {
bail!("we don't have that asset");
}

let market = party_from_address(MARKETPLACE);
let buyer = party_from_address(params.buyer);

let tx = TxBuilder::new()
.transfer_asset(
market, // from party
buyer, // to party
params.policy_id,
params.asset_name,
TransferQuantity::All,
)
.output_ada(
market, // to party
asset.price, // lovelace amount
)
.build();

Ok(tx)
}
```


8 changes: 0 additions & 8 deletions docs/pages/nomenclature.mdx

This file was deleted.

4 changes: 0 additions & 4 deletions docs/pages/order-book-example/_meta.json

This file was deleted.

53 changes: 0 additions & 53 deletions docs/pages/order-book-example/event-spec.mdx

This file was deleted.

Loading

0 comments on commit 44ae624

Please sign in to comment.