A working demo can be found here in the `apps/demo-app` folder.
The intention here is to help you setup a basic dapp using the Abstraxion library showcasing both query and transaction submission.
For this example we will use nextjs to scaffold the project
Run the following commands in your terminal:
# Generate project along with settings to avoid wizard.
npx create-next-app@latest nextjs-xion-abstraxion-example --use-npm --ts --eslint --tailwind --app --src-dir --import-alias "@/*"
# Enter application directory
cd nextjs-xion-abstraxion-example
Add the Abstraxion library to the project:
npm i @burnt-labs/abstraxion
Start the project in developer mode:
npm run dev
Open https://localhost:3000
in a web browser and you will see a fancy animated react logo.
Replace the contents of src/app/layout.tsx
with the following body:
{% code title="src/app/layout.tsx" %}
"use client";
import { Inter } from 'next/font/google'
import './globals.css'
import {AbstraxionProvider} from "@burnt-labs/abstraxion";
import "@burnt-labs/abstraxion/dist/index.css";
import "@burnt-labs/ui/dist/index.css";
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<AbstraxionProvider
config={{
contracts: ["xion1z70cvc08qv5764zeg3dykcyymj5z6nu4sqr7x8vl4zjef2gyp69s9mmdka"],
}}
>
{children}
</AbstraxionProvider>
</body>
</html>
)
}
{% endcode %}
{% hint style="warning" %} Without the "use client"; directive at the top, you will get a error {% endhint %}
The AbstraxionProvider
is required to provide context for the useAbstraxionAccount
and `useAbstraxionSigningClient` hooks.
{% hint style="danger" %} Be sure to provide the array of contract addresses your DAPP intends to interact with. {% endhint %}
Replace the contents of `src/app/page.tsx` with the following:
{% code title="src/app/page.tsx" %}
"use client";
import {
Abstraxion,
useAbstraxionAccount,
useModal
} from "@burnt-labs/abstraxion";
import { Button } from "@burnt-labs/ui";
import { useEffect } from "react";
export default function Page(): JSX.Element {
// Abstraxion hooks
const { data: { bech32Address }, isConnected, isConnecting } = useAbstraxionAccount();
// General state hooks
const [, setShow] = useModal();
// watch isConnected and isConnecting
// only added for testing
useEffect(() => {
console.log({ isConnected, isConnecting });
}, [isConnected, isConnecting])
return (
<main className="m-auto flex min-h-screen max-w-xs flex-col items-center justify-center gap-4 p-4">
<h1 className="text-2xl font-bold tracking-tighter text-black dark:text-white">
Abstraxion
</h1>
<Button
fullWidth
onClick={() => { setShow(true) }}
structure="base"
>
{bech32Address ? (
<div className="flex items-center justify-center">VIEW ACCOUNT</div>
) : (
"CONNECT"
)}
</Button>
{
bech32Address &&
<div className="border-2 border-primary rounded-md p-4 flex flex-row gap-4">
<div className="flex flex-row gap-6">
<div>
address
</div>
<div>
{bech32Address}
</div>
</div>
</div>
}
<Abstraxion onClose={() => setShow(false)} />
</main>
);
}
{% endcode %}
This will give a a button that initiates a meta account using social login. Click the `CONNECT` button and try it out!
Querying the chain wouldn't be of much use without a mechanism to alter chain state. Let's do that now.
Refresh the contents of src/app/page.tsx
with the following:
{% code title="src/app/page.tsx" %}
"use client";
import Link from "next/link";
import { useState } from "react";
import {
Abstraxion,
useAbstraxionAccount,
useAbstraxionSigningClient,
} from "@burnt-labs/abstraxion";
import { Button } from "@burnt-labs/ui";
import "@burnt-labs/ui/dist/index.css";
import type { ExecuteResult } from "@cosmjs/cosmwasm-stargate";
import { seatContractAddress } from "./layout";
type ExecuteResultOrUndefined = ExecuteResult | undefined;
export default function Page(): JSX.Element {
// Abstraxion hooks
const { data: account } = useAbstraxionAccount();
const { client } = useAbstraxionSigningClient();
// General state hooks
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [executeResult, setExecuteResult] =
useState<ExecuteResultOrUndefined>(undefined);
const blockExplorerUrl = `https://explorer.burnt.com/xion-testnet-1/tx/${executeResult?.transactionHash}`;
function getTimestampInSeconds(date: Date | null) {
if (!date) return 0;
const d = new Date(date);
return Math.floor(d.getTime() / 1000);
}
const now = new Date();
now.setSeconds(now.getSeconds() + 15);
const oneYearFromNow = new Date();
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);
async function claimSeat() {
setLoading(true);
const msg = {
sales: {
claim_item: {
token_id: String(getTimestampInSeconds(now)),
owner: account.bech32Address,
token_uri: "",
extension: {},
},
},
};
try {
const claimRes = await client?.execute(
account.bech32Address,
seatContractAddress,
msg,
{
amount: [{ amount: "0", denom: "uxion" }],
gas: "500000",
},
"", // memo
[],
);
setExecuteResult(claimRes);
} catch (error) {
// eslint-disable-next-line no-console -- No UI exists yet to display errors
console.log(error);
} finally {
setLoading(false);
}
}
return (
<main className="m-auto flex min-h-screen max-w-xs flex-col items-center justify-center gap-4 p-4">
<h1 className="text-2xl font-bold tracking-tighter text-white">
ABSTRAXION
</h1>
<Button
fullWidth
onClick={() => {
setIsOpen(true);
}}
structure="base"
>
{account.bech32Address ? (
<div className="flex items-center justify-center">VIEW ACCOUNT</div>
) : (
"CONNECT"
)}
</Button>
{client ? (
<Button
disabled={loading}
fullWidth
onClick={() => {
void claimSeat();
}}
structure="base"
>
{loading ? "LOADING..." : "CLAIM SEAT"}
</Button>
) : null}
<Abstraxion
isOpen={isOpen}
onClose={() => {
setIsOpen(false);
}}
/>
{executeResult ? (
<div className="flex flex-col rounded border-2 border-black p-2 dark:border-white">
<div className="mt-2">
<p className="text-zinc-500">
<span className="font-bold">Transaction Hash</span>
</p>
<p className="text-sm">{executeResult.transactionHash}</p>
</div>
<div className="mt-2">
<p className=" text-zinc-500">
<span className="font-bold">Block Height:</span>
</p>
<p className="text-sm">{executeResult.height}</p>
</div>
<div className="mt-2">
<Link
className="text-black underline visited:text-purple-600 dark:text-white"
href={blockExplorerUrl}
target="_blank"
>
View in Block Explorer
</Link>
</div>
</div>
) : null}
</main>
);
}
{% endcode %}
Inside the claimSeat
function above, the .execute()
is being called as such:
{% code lineNumbers="true" %}
const claimRes = await client?.execute(
account.bech32Address,
seatContractAddress,
msg,
{
amount: [{ amount: "0", denom: "uxion" }],
gas: "500000",
},
"", // memo
[],
);
{% endcode %}
The fourth parameter in the above function call represents the fee
config - replacing the object with auto
here will allow the SDK to handle fee configuration for you, eg.,
const claimRes = await client?.execute(
account.bech32Address,
seatContractAddress,
msg,
auto,
"", // memo
[],
);
If everything is successful you should see transaction results as shown above.
After clicking "Claim Seat" you should see the confirmation above.
We accomplished several things:
- Setup a Next.js Project Start by generating a Next.js project using
npx create-next-app@latest
. - Adding the Abstraxion Library Add the Abstraxion library (
@burnt-labs/abstraxion
) to the project vianpm
. - Setup AbstraxionProvider In the layout file, setup the
AbstraxionProvider
to provide context for theuseAbstraxionAccount
anduseAbstraxionSigningClient
hooks. - Landing Page Setup Modify the home page content to allow initiating a meta account through a social login with a button.
- Submit a Transaction Alter the chain's state by submitting a transaction.
These basic components are the majority of what is needed to create and deploy a successful dapp! Feel free to reach out to us on discord or on our Github.