feat: finish up end to end example

* create a gift card (nft)
* lock ADA while minting gift card
* unlock ADA while burning gift card

Co-authored-by: Kasey White <kwhitemsg@gmail.com>
This commit is contained in:
rvcas 2023-03-11 16:10:12 -05:00 committed by Lucas
parent 79fd6b4828
commit 5e062c130d
2 changed files with 226 additions and 14 deletions

View File

@ -1,10 +1,21 @@
import { useEffect, useState } from "preact/hooks";
import { Blockfrost, Lucid } from "~/vendor/lucid@0.9.4/mod.ts";
import {
Blockfrost,
Constr,
Data,
fromText,
Lucid,
} from "~/vendor/lucid@0.9.4/mod.ts";
import { Input } from "~/components/Input.tsx";
import { Button } from "~/components/Button.tsx";
import { applyParams, Validators } from "~/utils.ts";
import {
AppliedValidators,
applyParams,
LocalCache,
Validators,
} from "~/utils.ts";
export interface AppProps {
validators: Validators;
@ -13,8 +24,15 @@ export interface AppProps {
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<
{ lock: string; mint: string } | null
AppliedValidators | null
>(null);
const setupLucid = async (blockfrostApiKey: string) => {
@ -26,6 +44,22 @@ export default function App({ validators }: AppProps) {
"Mainnet",
);
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);
};
@ -45,8 +79,6 @@ export default function App({ validators }: AppProps) {
const utxos = await lucid?.wallet.getUtxos()!;
console.log(utxos);
const utxo = utxos[0];
const outputReference = {
txHash: utxo.txHash,
@ -63,6 +95,105 @@ export default function App({ validators }: AppProps) {
setParameterizedContracts(contracts);
};
const submitADAGift = async (e: Event) => {
e.preventDefault();
setWaitingLockTx(true);
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!.mint)
.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);
};
const submitRedeemADAGift = async (e: Event) => {
e.preventDefault();
setWaitingUnlockTx(true);
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!.mint)
.attachSpendingValidator(parameterizedContracts!.lock)
.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);
}
};
return (
<div>
{!lucid
@ -88,6 +219,7 @@ export default function App({ validators }: AppProps) {
type="text"
name="tokenName"
id="tokenName"
value={tokenName}
onInput={(e) => setTokenName(e.currentTarget.value)}
>
Token Name
@ -100,17 +232,75 @@ export default function App({ validators }: AppProps) {
)}
</form>
)}
{parameterizedContracts && (
{lucid && parameterizedContracts && (
<>
<h3 class="mt-4 mb-2">Lock</h3>
<pre class="bg-gray-200 p-2 rounded overflow-x-scroll">
{parameterizedContracts.lock}
{parameterizedContracts.lock.script}
</pre>
<h3 class="mt-4 mb-2">Mint</h3>
<pre class="bg-gray-200 p-2 rounded overflow-x-scroll">
{parameterizedContracts.mint}
{parameterizedContracts.mint.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={submitADAGift}
disabled={waitingLockTx || !!lockTxHash}
>
{waitingLockTx
? "Waiting for Tx..."
: "Send 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={submitRedeemADAGift}
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>

View File

@ -1,9 +1,7 @@
import {
applyDoubleCborEncoding,
applyParamsToScript,
C,
Constr,
Data,
fromText,
Lucid,
MintingPolicy,
@ -18,6 +16,13 @@ export type Validators = {
mint: MintingPolicy;
};
export type LocalCache = {
tokenName: string;
giftADA: string;
lockTxHash: string;
parameterizedValidators: AppliedValidators;
};
export async function readValidators(): Promise<Validators> {
const blueprint: Blueprint = JSON.parse(
await Deno.readTextFile("plutus.json"),
@ -47,12 +52,19 @@ export async function readValidators(): Promise<Validators> {
};
}
export type AppliedValidators = {
lock: SpendingValidator;
mint: MintingPolicy;
policyId: string;
lockAddress: string;
};
export function applyParams(
tokenName: string,
outputReference: OutRef,
validators: Validators,
lucid: Lucid,
): { lock: string; mint: string } {
): AppliedValidators {
const outRef = new Constr(0, [
new Constr(0, [outputReference.txHash]),
BigInt(outputReference.outputIndex),
@ -63,15 +75,25 @@ export function applyParams(
outRef,
]);
const policyId = lucid.utils.validatorToScriptHash(validators.mint);
const policyId = lucid.utils.validatorToScriptHash({
type: "PlutusV2",
script: mint,
});
const lock = applyParamsToScript(validators.lock.script, [
fromText(tokenName),
policyId,
]);
const lockAddress = lucid.utils.validatorToAddress({
type: "PlutusV2",
script: lock,
});
return {
lock: applyDoubleCborEncoding(lock),
mint: applyDoubleCborEncoding(mint),
lock: { type: "PlutusV2", script: applyDoubleCborEncoding(lock) },
mint: { type: "PlutusV2", script: applyDoubleCborEncoding(mint) },
policyId,
lockAddress,
};
}