A working demo can be found here in the `apps/demo-app` folder.
Goal
The intention here is to help you setup a basic dapp using the Abstraxion library showcasing both query and transaction submission.
Requirements
Setup Project
For this example we will use nextjs to scaffold the project
Run the following commands in your terminal:
Copy # 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:
Copy npm i @burnt-labs/abstraxion
Start the project in developer mode:
Open https://localhost:3000
in a web browser and you will see a fancy animated react logo.
Setup Abstraxion Library
Replace the contents of src/app/layout.tsx
with the following body:
Copy "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 >
)
}
Without the "use client"; directive at the top, you will get a error
The AbstraxionProvider
is required to provide context for the useAbstraxionAccount
and `useAbstraxionSigningClient` hooks.
Be sure to provide the array of contract addresses your DAPP intends to interact with.
Add Hooks to the homepage
Replace the contents of `src/app/page.tsx` with the following:
Copy "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 >
);
}
This will give a a button that initiates a meta account using social login. Click the `CONNECT` button and try it out!
Transaction submission
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:
Copy "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 >
);
}
Quick Note on Fee Config
Inside the claimSeat
function above, the .execute()
is being called as such:
Copy const claimRes = await client ?.execute (
account .bech32Address ,
seatContractAddress ,
msg ,
{
amount : [{ amount : "0" , denom : "uxion" }] ,
gas : "500000" ,
} ,
"" , // memo
[] ,
);
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.,
Copy const claimRes = await client ?.execute (
account .bech32Address ,
seatContractAddress ,
msg ,
auto ,
"" , // memo
[] ,
);
If everything is successful you should see transaction results as shown above.
Summary
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 via npm
.
Setup AbstraxionProvider In the layout file, setup the AbstraxionProvider
to provide context for the useAbstraxionAccount
and useAbstraxionSigningClient
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 .