diff --git a/src/pages/example--gift-card.mdx b/src/pages/example--gift-card.mdx index 49db6a8..c79c9ad 100644 --- a/src/pages/example--gift-card.mdx +++ b/src/pages/example--gift-card.mdx @@ -15,16 +15,13 @@ Let's build a UI to send and redeem a gift card using smart contracts on Cardano - [x] Writing `Aiken` inter-dependent `mint` & `spend` validators. - [x] Parameterizing validators. -- [x] Using [Lucid](https://lucid.spacebudz.io/) with [Blockfrost](https://blockfrost.io). +- [x] Using [Blaze](https://github.com/butaneprotocol/blaze-cardano/) with [Blockfrost](https://blockfrost.io). ★ We'll once again be using the `Blockfrost` provider. So have your Blockfrost API key ready. -- [x] Using [Deno fresh](https://fresh.deno.dev/). - - ★ You can install deno using these - [instructions](https://deno.land/manual@v1.29.1/getting_started/installation). - +- [x] Using [SvelteKit](https://kit.svelte.dev/). + ★ Make sure you have Node.js installed. When encountering an unfamiliar syntax or concept, do not hesitate to refer to @@ -379,170 +376,130 @@ aiken build ## Building a frontend With the easy part out of the way we can start building a frontend to interact with our -smart contracts in the browser. Deno fresh is an interesting project for building -web applications in Deno. +smart contracts in the browser. SvelteKit is an interesting framework for building +web applications. ### Setting up -Let's generate a Deno fresh project in the same directory as our Aiken project. +Let's generate a SvelteKit project in the same directory as our Aiken project. ```sh -deno run -A -r https://fresh.deno.dev . +npm create svelte@latest . ``` -When prompted to enable Tailwind CSS say yes. - -We need lucid and we should probably add an alias for better looking imports. -Let's edit `import_map.json`. - -```json filename="import_map.json" {11-12} -{ - "imports": { - "$fresh/": "...", - "preact": "...", - "preact/": "...", - "preact-render-to-string": "...", - "@preact/signals": "...", - "@preact/signals-core": "...", - "twind": "...", - "twind/": "...", - "lucid/": "https://deno.land/x/lucid@0.9.5/", - "~/": "./" - } -} + + When prompted use the current directory, continue even though directory is not + empty, choose a skeleton project, use Svelte 5, and enable typescript. + + +We need to add Blaze now. + +```sh +npm add @blaze-cardano/sdk@latest @blaze-cardano/core@latest @blaze-cardano/query@latest @blaze-cardano/uplc@latest @blaze-cardano/wallet@latest @blaze-cardano/tx@latest ``` -We can delete a few things that come with the starter template that we don't need. +Let's add tailwindcss to our project. ```sh -rm islands/Counter.tsx -rm -rf routes/api -rm routes/\[name\].tsx +npx svelte-add@latest tailwindcss ``` +When prompted just say yes to everything. + Let's also add some reusable components to our project. -```tsx filename="components/Button.tsx" -import { JSX } from "preact"; -import { IS_BROWSER } from "$fresh/runtime.ts"; +```svelte filename="src/lib/components/Button.svelte" + -export function Input({ - children, - id, - ...props -}: JSX.HTMLAttributes) { - return ( -
- - -
- ); -} + ``` -### Home page - -Everything we'll be doing with validators and transactions will happen fully client side. -This means we can just have our route render a single `island` component and then -we can write all of our code in this island for the most part. +```svelte filename="src/lib/components/Input.svelte" + + +
+ + +
``` -Now inside of `routes/index.tsx` we can import our new island and render it. +### Home page -```tsx filename="routes/index.tsx" {3,29} -import { Head } from "$fresh/runtime.ts"; +Everything we'll be doing with validators and transactions will happen fully client side. +This means we can just have our app render a single `+page.svelte` component and then +we can write all of our code in this page component for the most part. -import Oneshot from "~/islands/Oneshot"; +Let's edit `src/routes/+page.svelte` to contain the following code. -export default function Home() { - return ( - <> - - One Shot - +```svelte filename="src/routes/+page.svelte" + + One Shot + -
-
-

- Make a one shot minting and lock contract -

+
+
+

Make a one shot minting and lock contract

-

Redeem

-
-            TODO: Render non-parameterized redeem validator
-          
+

Redeem

+
+      TODO: Render non-parameterized redeem validator
+    
-

Gift Card

-
-            TODO: Render non-parameterized gift_card validator
-          
-
+

Gift Card

+
+      TODO: Render non-parameterized gift_card validator
+    
+
- -
- - ); -} +
Oneshot
+
``` -You can replace everything that was in `routes/index.tsx` with the above code. -We've left some `TODO`'s in the code to remind us to render the validators. We'll render +We've left a `TODO` in the code to remind us to render the validator. We'll render the compiled aiken code as a hex encoded string. There not much of a reason to do this, it's just kinda cool to see. Next we should load the `plutus.json` file and get the compiled aiken code. Let's create -a file called `utils.ts` and add the following code. - -```ts filename="utils.ts" -import { MintingPolicy, SpendingValidator } from "lucid/mod.ts"; +a file called `lib/utils.ts` and add the following code. -import blueprint from "~/plutus.json" assert { type: "json" }; +```ts filename="lib/utils.ts" +import blueprint from "../../plutus.json" assert { type: "json" }; export type Validators = { - redeem: SpendingValidator; - giftCard: MintingPolicy; + giftCard: string; }; export function readValidators(): Validators { - const redeem = blueprint.validators.find((v) => v.title === "oneshot.redeem"); - - if (!redeem) { - throw new Error("Redeem validator not found"); - } - const giftCard = blueprint.validators.find( - (v) => v.title === "oneshot.gift_card" + (v) => v.title === "oneshot.gift_card.spend" ); if (!giftCard) { @@ -550,173 +507,148 @@ export function readValidators(): Validators { } return { - redeem: { - type: "PlutusV2", - script: redeem.compiledCode, - }, - giftCard: { - type: "PlutusV2", - script: giftCard.compiledCode, - }, + giftCard: giftCard.compiledCode, }; } ``` There's nothing particularly special here. We're just reading the `plutus.json` file -and finding the compiled code for the `redeem` and `gift_card` validators. We're also -exporting a type for the validators so we can use it in our island later. Having this function +and finding the compiled code for the `gift_card` validator. We're also +exporting a type for the validators so we can use it in our page later. Having this function potentially throw an error is just a way to signal to us that we've done something wrong. -Let's import our new `readValidators` file into our `routes/index.tsx` file and use it to -in a server side handler. This will allow us to access the data in the `Home` page component +Let's import our new `readValidators` file into `src/routes/+page.server.ts` file and use it to +in a server side loader. This will allow us to access the data in the `+page.svelte` page component as page props which we'll then use to render the validator's compiled code. -```tsx filename="routes/index.tsx" {2,5,7-9,11-17} /validators/4-8 /PageProps/ /data/ -import { Head } from "$fresh/runtime.ts"; -import { Handlers, PageProps } from "$fresh/server.ts"; +```ts filename="src/routes/+page.server.ts" +import { readValidators } from "$lib/utils"; +import type { PageServerLoad } from "./$types"; -import Oneshot from "~/islands/Oneshot"; -import { readValidators, Validators } from "~/utils.ts"; +export const load: PageServerLoad = async () => { + const validator = readValidators().giftCard; -interface Data { - validators: Validators; -} - -export const handler: Handlers = { - GET(_req, ctx) { - const validators = readValidators(); - - return ctx.render({ validators }); - }, + return { validator }; }; +``` -export default function Home({ data }: PageProps) { - const { validators } = data; +```svelte filename="src/routes/+page.svelte" + -

Redeem

-
-            {validators.redeem.script}
-          
+ + One Shot + -

Gift Card

-
-            {validators.giftCard.script}
-          
- +
+
+

Make a one shot minting and lock contract

- -
- - ); -} -``` +

Gift Card

+
{data.validator}
+
-Your editor will probably complain and say that `Oneshot` doesn't accept a `validators` prop. -We'll fix that in a moment. +
Oneshot
+ +``` -### The island +### The App It's about time we start the real party and we've made it to the juicy part. In this island -we'll capture some user input, apply some params to our raw validators, and execute some transactions. +we'll capture some user input, apply some params to our raw validator, and execute some transactions. To keep things simple we'll assume [eternl](https://eternl.io/) is setup in your browser. Another thing we'll do to keep things simple is have the gift card be sent to ourselves when minted. This way we can test the redeeming of the gift card without having to send it to someone else or using a second wallet. #### Token name -We should give `Oneshot`'s props a type and capture the `token_name` so we can use it to -apply some params to the raw validators. Lucid also requires initialization so let's get -some boilerplate out of the way. +We need to capture the `token_name` so we can use it to apply some params to the raw validators. +Blaze also requires initialization so let's get some boilerplate out of the way. -```tsx filename="islands/Oneshot.tsx" -import { useEffect, useState } from "preact/hooks"; -import { Blockfrost, Lucid } from "lucid/mod.ts"; +```svelte filename="src/routes/+page.svelte" + + + + One Shot + + +
+
+

Make a one shot minting and lock contract

+ +

Gift Card

+
{data.validator}
+
+ +
+ {#if blaze} +
+ + Blockfrost API Key + + + +
+ {:else} +
+ Token Name + + {#if tokenName.length > 0} + + {/if} +
+ {/if} +
+
``` #### Apply params @@ -780,7 +712,10 @@ export function applyParams( return { redeem: { type: "PlutusV2", script: applyDoubleCborEncoding(redeem) }, - giftCard: { type: "PlutusV2", script: applyDoubleCborEncoding(giftCard) }, + giftCard: { + type: "PlutusV2", + script: applyDoubleCborEncoding(giftCard), + }, policyId, lockAddress, };