A working demo can be found here: https://github.com/burnt-labs/xion.js/tree/main/apps/demo-app
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
Before we can get into setting up our abstraxion SDK in the app we'll be deploying a treasury contract here:
Login to the Developer portal
Select appropriate config - to get started here's a config that should work for most of the use cases:
NOTE: the contract address in "Grant config" is the contract your users will be interacting with
Read more about the treasury contracts here .
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' ] })
const treasuryConfig = {
treasury : "YOUR_TREASURY_CONTRACT_ADDRESS_HERE" ,
};
export default function RootLayout ({
children ,
} : {
children : React . ReactNode
}) {
return (
< html lang = "en" >
< body className = {inter.className} >
< AbstraxionProvider
config = {treasuryConfig} >
{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.
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.001" , 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.001" , 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
Setting up our first treasury contract
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 .