aiken/examples/gift_card/islands/App.tsx

311 lines
7.7 KiB
TypeScript

import { useEffect, useState } from "preact/hooks";
import { Blockfrost, Constr, Data, fromText, Lucid } from "lucid/mod.ts";
import { Input } from "~/components/Input.tsx";
import { Button } from "~/components/Button.tsx";
import {
AppliedValidators,
applyParams,
LocalCache,
Validators,
} from "~/utils.ts";
export interface AppProps {
validators: Validators;
}
export default function App({ validators }: AppProps) {
const [lucid, setLucid] = useState<Lucid | null>(null);
const [tokenName, setTokenName] = useState<string>("");
const [giftADA, setGiftADA] = useState<string | undefined>();
const [lockTxHash, setLockTxHash] = useState<string | undefined>(undefined);
const [waitingLockTx, setWaitingLockTx] = useState<boolean>(false);
const [unlockTxHash, setUnlockTxHash] = useState<string | undefined>(
undefined,
);
const [waitingUnlockTx, setWaitingUnlockTx] = useState<boolean>(false);
const [parameterizedContracts, setParameterizedContracts] = useState<
AppliedValidators | null
>(null);
const setupLucid = async (blockfrostApiKey: string) => {
const lucid = await Lucid.new(
new Blockfrost(
"https://cardano-preprod.blockfrost.io/api/v0",
blockfrostApiKey,
),
"Preprod",
);
const cache = localStorage.getItem("cache");
if (cache) {
const {
tokenName,
giftADA,
parameterizedValidators,
lockTxHash,
}: LocalCache = JSON.parse(cache);
setTokenName(tokenName);
setGiftADA(giftADA);
setParameterizedContracts(parameterizedValidators);
setLockTxHash(lockTxHash);
}
setLucid(lucid);
};
useEffect(() => {
if (lucid) {
window.cardano
.eternl
.enable()
.then((wallet) => {
lucid.selectWallet(wallet);
});
}
}, [lucid]);
const submitTokenName = async (e: Event) => {
e.preventDefault();
const utxos = await lucid?.wallet.getUtxos()!;
const utxo = utxos[0];
const outputReference = {
txHash: utxo.txHash,
outputIndex: utxo.outputIndex,
};
const contracts = applyParams(
tokenName,
outputReference,
validators,
lucid!,
);
setParameterizedContracts(contracts);
};
const createGiftCard = async (e: Event) => {
e.preventDefault();
setWaitingLockTx(true);
try {
const lovelace = Number(giftADA) * 1000000;
const assetName = `${parameterizedContracts!.policyId}${
fromText(tokenName)
}`;
// Action::Mint
const mintRedeemer = Data.to(new Constr(0, []));
const utxos = await lucid?.wallet.getUtxos()!;
const utxo = utxos[0];
const tx = await lucid!
.newTx()
.collectFrom([utxo])
.attachMintingPolicy(parameterizedContracts!.giftCard)
.mintAssets(
{ [assetName]: BigInt(1) },
mintRedeemer,
)
.payToContract(
parameterizedContracts!.lockAddress,
{ inline: Data.void() },
{ "lovelace": BigInt(lovelace) },
)
.complete();
const txSigned = await tx.sign().complete();
const txHash = await txSigned.submit();
const success = await lucid!.awaitTx(txHash);
// Wait a little bit longer so ExhuastedUTxOError doesn't happen
// in the next Tx
setTimeout(() => {
setWaitingLockTx(false);
if (success) {
localStorage.setItem(
"cache",
JSON.stringify({
tokenName,
giftADA,
parameterizedValidators: parameterizedContracts,
lockTxHash: txHash,
}),
);
setLockTxHash(txHash);
}
}, 3000);
} catch {
setWaitingLockTx(false);
}
};
const redeemGiftCard = async (e: Event) => {
e.preventDefault();
setWaitingUnlockTx(true);
try {
const utxos = await lucid!.utxosAt(parameterizedContracts!.lockAddress);
const assetName = `${parameterizedContracts!.policyId}${
fromText(tokenName)
}`;
// Action::Burn
const burnRedeemer = Data.to(new Constr(1, []));
const tx = await lucid!
.newTx()
.collectFrom(utxos, Data.void())
.attachMintingPolicy(parameterizedContracts!.giftCard)
.attachSpendingValidator(parameterizedContracts!.redeem)
.mintAssets(
{ [assetName]: BigInt(-1) },
burnRedeemer,
)
.complete();
const txSigned = await tx.sign().complete();
const txHash = await txSigned.submit();
const success = await lucid!.awaitTx(txHash);
setWaitingUnlockTx(false);
if (success) {
localStorage.removeItem("cache");
setUnlockTxHash(txHash);
}
} catch {
setWaitingUnlockTx(false);
}
};
return (
<div>
{!lucid
? (
<Input
type="password"
id="blockfrostApiKey"
onKeyDown={async (e) => {
if (e.key === "Enter") {
await setupLucid(e.currentTarget.value);
}
}}
>
Blockfrost API KEY (PRESS ENTER)
</Input>
)
: (
<form
class="mt-10 grid grid-cols-1 gap-y-8"
onSubmit={submitTokenName}
>
<Input
type="text"
name="tokenName"
id="tokenName"
value={tokenName}
onInput={(e) => setTokenName(e.currentTarget.value)}
>
Token Name
</Input>
{tokenName && (
<Button type="submit">
Make Contracts
</Button>
)}
</form>
)}
{lucid && parameterizedContracts && (
<>
<h3 class="mt-4 mb-2">Redeem</h3>
<pre class="bg-gray-200 p-2 rounded overflow-x-scroll">
{parameterizedContracts.redeem.script}
</pre>
<h3 class="mt-4 mb-2">Gift Card</h3>
<pre class="bg-gray-200 p-2 rounded overflow-x-scroll">
{parameterizedContracts.giftCard.script}
</pre>
<div class="mt-10 grid grid-cols-1 gap-y-8">
<Input
type="text"
name="giftADA"
id="giftADA"
value={giftADA}
onInput={(e) => setGiftADA(e.currentTarget.value)}
>
ADA Amount
</Input>
<Button
onClick={createGiftCard}
disabled={waitingLockTx || !!lockTxHash}
>
{waitingLockTx
? "Waiting for Tx..."
: "Create Gift Card (Locks ADA)"}
</Button>
{lockTxHash && (
<>
<h3 class="mt-4 mb-2">ADA Locked</h3>
<a
class="mb-2"
target="_blank"
href={`https://cardanoscan.io/transaction/${lockTxHash}`}
>
{lockTxHash}
</a>
<Button
onClick={redeemGiftCard}
disabled={waitingLockTx || !!unlockTxHash}
>
{waitingUnlockTx
? "Waiting for Tx..."
: "Redeem Gift Card (Unlocks ADA)"}
</Button>
</>
)}
{unlockTxHash && (
<>
<h3 class="mt-4 mb-2">ADA Unlocked</h3>
<a
class="mb-2"
target="_blank"
href={`https://cardanoscan.io/transaction/${unlockTxHash}`}
>
{unlockTxHash}
</a>
</>
)}
</div>
</>
)}
</div>
);
}