Comment on page
Embedded Wallet SDK React
With the Embedded Wallet SDK, you can supercharge your dApp’s UX. It is super easy to integrate and completely customisable to meet your dApp’s styles. It also helps solve the current Snaps limitations and integrating this will allow users to view their balances, manage their assets, and more.

Osmosis DEX with Account Modal
npm install @leapwallet/embedded-wallet-sdk-react
import React from "react";
import { useChain } from "@cosmos-kit/react";
import { AccountModal } from "@leapwallet/embedded-wallet-sdk-react";
import "@leapwallet/embedded-wallet-sdk-react/styles.css";
const chainId = "osmosis-1";
const chain = "osmosis";
const restUrl = "https://rest.cosmos.directory/osmosis";
const YourApp = () => {
const { address } = useChain(chain);
const [isModalOpen, setIsModalOpen] = React.useState(false);
return (
<div>
{/* other components */}
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
<AccountModal
theme="dark"
chainId={chainId}
restUrl={restUrl}
address={address}
isOpen={isModalOpen}
onClose={closeModal}
/>
{/* other components */}
</div>
);
};
Name | Type | Description |
---|---|---|
theme | "light" | "dark" | ThemeDefinition | |
chainId | string | Chain ID of the chain |
restUrl | string | REST URL of the chain |
address | string | Address of the user |
isOpen | boolean | Whether the modal is open |
onClose | () => void | Callback function when the modal is closed |
config | Config | Config of the modal |
Here is the type definition of
Config
:type Config = {
// This function is called to render the title of the modal
title?: (page: Page) => React.ReactNode;
// This function is called to render the sub-title of the modal
subTitle?: (page: Page) => React.ReactNode;
// Should the modal be closed when the backdrop is clicked
closeOnBackdropClick?: boolean;
// Should the modal be closed when the escape key is pressed
closeOnEscape?: boolean;
// Configure the action list on the home page
actionListConfig?: ActionListConfig;
};
type ActionListConfig = Record<
string,
{
label?: string;
onClick?: (chainId: string) => void;
enabled?: boolean;
}
>;
enum Page {
HOME = "home",
ACTIVITY = "activity",
}
import { Page, Actions } from '@leapwallet/snaps-sdk-react'
const config: Config = {
title: (page) => {
switch (page) {
case Page.HOME:
return "Assets";
case Page.ACTIVITY:
return "Activity";
}
},
closeOnBackdropClick: true,
closeOnEscape: true,
actionListConfig: {
[Actions.Swap]: {
label: "Swap",
onClick: (chainId) => {
console.log(chainId);
},
enabled: true,
},
[Actions.Bridge]: {
enabled: false,
},
},
};
In case you want to create your own UI in react, we have exposed the react hooks that we use internally. If you are not using react, then you can check out the next page.
For making queries, we primarily use swr, so a lot of return values from hooks will have the same return type (except for the data).
To fetch user balances, you can use
useRichBalance
hook.const balanceQuery = useRichBalance(
address,
chainId,
restUrl,
swrConfiguration
);
Parameters -
- 1.
address
- the wallet address your querying the balance for - 2.
chainId
- self explanatory - 3.
restUrl
(optional) - custom rest url for the node - 4.
To fetch user activity, you can use
useActivity
hook. The return type of a @tanstack/react-query
useInfiniteQuery
hook.const activityQuery = useActivity(address, chainId, restUrl);
Parameters -
- 1.
address
- the wallet address your querying the balance for - 2.
chainId
- self explanatory - 3.
restUrl
(optional) - custom rest url for the node
Return Value -
The
activityQuery.data.pages
field will be a list of objects with the following fields -{
sender: [] // array of activity where user is sender of tokens
recipient: [] // // array of activity where user is received of tokens
}
Usage Example -
Using the activity data
import { type ActivityCardContent } from '@leapwallet/snaps-sdk-core'
// flat list of activity
const activityList = activityQuery.data.pages
.reduce((acc, cur) => {
return [...acc, ...cur.sender.activity, ...cur.recipient.activity];
}, [] as ActivityCardContent[]);
// you can now sort this list however you want
Triggering the next (data) page load using the last element on the list
const [searchQuery, setSearchQuery] = useState<string>('')
useEffect(() => {
// track last item on the list
const lastItem = document.querySelector("#activity-bottom");
if (
!lastItem ||
activityQuery.isLoading ||
activityQuery.isFetchingNextPage ||
activityQuery.error ||
searchQuery.trim().length > 0
) {
return;
}
const handleIntersection = ([i]: IntersectionObserverEntry[]) => {
if (i.isIntersecting) {
activityQuery.fetchNextPage();
}
};
const observer = new IntersectionObserver(handleIntersection, {
root: null,
rootMargin: "0px",
threshold: 1.0,
});
observer.observe(lastItem);
return () => {
observer.disconnect();
};
}, [activityQuery, debouncedSearchQuery]);
Last modified 1mo ago