Skip to content

Commit

Permalink
Capsule integration (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
codingki authored Apr 21, 2024
2 parents 7f1ce68 + f33c493 commit 422e5ff
Show file tree
Hide file tree
Showing 33 changed files with 6,745 additions and 248 deletions.
7 changes: 7 additions & 0 deletions docs/docs/change-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ sidebar_position: 8

# Changelog

## Version 0.1.6

- Added capsule wallet integration
- `walletDefaultOptions` param in `GrazProvider` for setting default options for wallet.
- Added `useCapsule` hook for leap login capsule ui
- `denom` in `useBalance` param is now optional

## Version 0.1.5

- Fix typing on SigningClient hooks for single chain `opts` param
Expand Down
40 changes: 40 additions & 0 deletions docs/docs/guides/integrate-capsule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Integrate Capsule

## Install Leap Login Capsule UI

```bash
yarn add @leapwallet/cosmos-social-login-capsule-provider-ui
```

## Example Next JS

For Next JS we recommend to load the module dynamically to avoid SSR issues. And use `useCapsule` hook to get the client and other capsule related states.

```javascript
const LeapSocialLogin = dynamic(
() => import("@leapwallet/cosmos-social-login-capsule-provider-ui").then((m) => m.CustomCapsuleModalView),
{ ssr: false },
);

const HomePage = () => {
const { client, modalState, onAfterLoginSuccessful, setModalState, onLoginFailure } = useCapsule();

return (
<div>
<LeapSocialLogin
capsule={client?.getClient() || undefined}
onAfterLoginSuccessful={() => {
onAfterLoginSuccessful?.();
}}
onLoginFailure={() => {
onLoginFailure();
}}
setShowCapsuleModal={setModalState}
showCapsuleModal={modalState}
/>
</div>
);
};
```
Thats it, now you can use capsule as your wallet provider.
44 changes: 44 additions & 0 deletions docs/docs/hooks/useCapsule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# useCapsule

Hook to interact with [@leapwallet/cosmos-social-login-capsule-provider-ui](https://www.npmjs.com/package/@leapwallet/cosmos-social-login-capsule-provider-ui)

#### Usage

```javascript
const LeapSocialLogin = dynamic(
() => import("@leapwallet/cosmos-social-login-capsule-provider-ui").then((m) => m.CustomCapsuleModalView),
{ ssr: false },
);

const HomePage = () => {
const { client, modalState, onAfterLoginSuccessful, setModalState, onLoginFailure } = useCapsule();

return (
<div>
<LeapSocialLogin
capsule={client?.getClient() || undefined}
onAfterLoginSuccessful={() => {
onAfterLoginSuccessful?.();
}}
onLoginFailure={() => {
onLoginFailure();
}}
setShowCapsuleModal={setModalState}
showCapsuleModal={modalState}
/>
</div>
);
};
```
#### Return Value
```tsx
{
setModalState: (state: boolean) => void;
modalState: boolean;
client: CapsuleProvider | null;
onAfterLoginSuccessful: (() => Promise<void>) | undefined;
onLoginFailure: () => void;
}
```
2 changes: 2 additions & 0 deletions example/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"@chakra-ui/react": "^2.8.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@leapwallet/cosmos-social-login-capsule-provider": "^0.0.30",
"@leapwallet/cosmos-social-login-capsule-provider-ui": "^0.0.47",
"framer-motion": "^10.16.4",
"graz": "*",
"next": "^13.4.19",
Expand Down
4 changes: 4 additions & 0 deletions example/next/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const CustomApp: NextPage<AppProps> = ({ Component, pageProps }) => {
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,
},
},
capsuleConfig: {
apiKey: process.env.NEXT_PUBLIC_CAPSULE_API_KEY,
env: process.env.NEXT_PUBLIC_CAPSULE_ENV,
},
}}
>
<Component {...pageProps} />
Expand Down
24 changes: 22 additions & 2 deletions example/next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { Center, HStack, Spacer, Stack, Text } from "@chakra-ui/react";
import { useAccount } from "graz";
import { Center, HStack, Spacer, Stack, Text, useColorMode } from "@chakra-ui/react";
import { useAccount, useCapsule } from "graz";
import type { NextPage } from "next";
import dynamic from "next/dynamic";
import { BalanceList } from "ui/balance-list";
import { ChainSwitcher } from "ui/chain-switcher";
import { ConnectButton } from "ui/connect-button";
import { ConnectStatus } from "ui/connect-status";
import { ToggleTheme } from "ui/toggle-theme";

const LeapSocialLogin = dynamic(
() => import("@leapwallet/cosmos-social-login-capsule-provider-ui").then((m) => m.CustomCapsuleModalView),
{ ssr: false },
);

const HomePage: NextPage = () => {
const { data: accountData } = useAccount({
chainId: "cosmoshub-4",
});
const { client, modalState, onAfterLoginSuccessful, setModalState, onLoginFailure } = useCapsule();
const { colorMode } = useColorMode();
return (
<Center minH="100vh">
<Stack bgColor="whiteAlpha.100" boxShadow="md" maxW="md" p={4} rounded="md" spacing={4} w="full">
Expand All @@ -36,6 +44,18 @@ const HomePage: NextPage = () => {
<ConnectButton />
</HStack>
</Stack>
<LeapSocialLogin
capsule={client?.getClient() || undefined}
onAfterLoginSuccessful={() => {
void onAfterLoginSuccessful?.();
}}
onLoginFailure={() => {
onLoginFailure();
}}
setShowCapsuleModal={setModalState}
showCapsuleModal={modalState}
theme={colorMode}
/>
</Center>
);
};
Expand Down
1 change: 1 addition & 0 deletions example/next/ui/connect-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const ConnectButton: FC = () => {
{wallets.metamask_snap_leap ? (
<Button onClick={() => handleConnect(WalletType.METAMASK_SNAP_LEAP)}>Metamask Snap Leap</Button>
) : null}
{wallets.capsule ? <Button onClick={() => handleConnect(WalletType.CAPSULE)}>Capsule</Button> : null}
</Stack>
</ModalContent>
</Modal>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions example/starter/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ const MyApp = ({ Component, pageProps }: AppProps) => {
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,
},
},
capsuleConfig: {
apiKey: process.env.NEXT_PUBLIC_CAPSULE_API_KEY,
env: process.env.NEXT_PUBLIC_CAPSULE_ENV,
},
walletDefaultOptions: {
sign: {
preferNoSetFee: true,
},
},
}}
>
<ChakraProvider resetCSS theme={theme}>
Expand Down
36 changes: 30 additions & 6 deletions example/starter/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
import { Stack } from "@chakra-ui/react";
import { Stack, useColorMode } from "@chakra-ui/react";
import { useCapsule } from "graz";
import dynamic from "next/dynamic";
import { Card } from "src/ui/card/chain";
import { mainnetChains } from "src/utils/graz";

const LeapSocialLogin = dynamic(
() => import("@leapwallet/cosmos-social-login-capsule-provider-ui").then((m) => m.CustomCapsuleModalView),
{ ssr: false },
);

const HomePage = () => {
const { client, modalState, onAfterLoginSuccessful, setModalState, onLoginFailure } = useCapsule();

const { colorMode } = useColorMode();
return (
<Stack spacing={4}>
{mainnetChains.map((chain) => (
<Card key={chain.chainId} chain={chain} />
))}
</Stack>
<>
<Stack spacing={4}>
{mainnetChains.map((chain) => (
<Card key={chain.chainId} chain={chain} />
))}
</Stack>
<LeapSocialLogin
capsule={client?.getClient() || undefined}
onAfterLoginSuccessful={() => {
void onAfterLoginSuccessful?.();
}}
onLoginFailure={() => {
onLoginFailure();
}}
setShowCapsuleModal={setModalState}
showCapsuleModal={modalState}
theme={colorMode}
/>
</>
);
};

Expand Down
3 changes: 2 additions & 1 deletion example/starter/src/ui/modal/connect-wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Button,
Heading,
HStack,
Image,
Modal,
ModalBody,
ModalContent,
Expand Down Expand Up @@ -52,7 +53,7 @@ const WalletModal = ({
p={4}
spacing={4}
>
{/* <Image alt={wallet.name} boxSize="40px" src={wallet.imgSrc} /> */}
<Image alt={wallet.name} boxSize="40px" src={wallet.imgSrc} />
<Text fontWeight="bold">{wallet.name}</Text>
</HStack>
))}
Expand Down
4 changes: 4 additions & 0 deletions example/starter/src/utils/graz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,8 @@ export const listedWallets = {
imgSrc: "/assets/wallet-icon-cosmostation.png",
mobile: true,
},
[WalletType.CAPSULE]: {
name: "Capsule",
imgSrc: "/assets/wallet-icon-capsule.jpg",
},
};
6 changes: 4 additions & 2 deletions packages/graz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
"@cosmjs/proto-signing": "*",
"@cosmjs/stargate": "*",
"@cosmjs/tendermint-rpc": "*",
"long": "*",
"@leapwallet/cosmos-social-login-capsule-provider": "^0.0.30",
"@leapwallet/cosmos-social-login-capsule-provider-ui": "^0.0.47",
"long": "^4",
"react": ">=17"
},
"peerDependenciesMeta": {
Expand All @@ -70,7 +72,7 @@
"cac": "^6.7.14",
"cosmos-directory-client": "0.0.6",
"wadesta": "^0.0.5",
"zustand": "^4.4.1"
"zustand": "^4.5.2"
},
"devDependencies": {
"@types/node": "^18.17.15",
Expand Down
49 changes: 46 additions & 3 deletions packages/graz/src/actions/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { grazSessionDefaultValues, useGrazInternalStore, useGrazSessionStore } f
import type { Maybe } from "../types/core";
import type { WalletType } from "../types/wallet";
import type { ChainId } from "../utils/multi-chain";
import { checkWallet, getWallet, isWalletConnect } from "./wallet";
import { checkWallet, getWallet, isCapsule, isWalletConnect } from "./wallet";

export type ConnectArgs = Maybe<{
chainId: ChainId;
Expand All @@ -26,14 +26,12 @@ export const connect = async (args?: ConnectArgs): Promise<ConnectResult> => {
const { recentChainIds: recentChains, chains, walletType } = useGrazInternalStore.getState();

const currentWalletType = args?.walletType || walletType;

const isWalletAvailable = checkWallet(currentWalletType);
if (!isWalletAvailable) {
throw new Error(`${currentWalletType} is not available`);
}

const wallet = getWallet(currentWalletType);

const chainIds = typeof args?.chainId === "string" ? [args.chainId] : args?.chainId || recentChains;
if (!chainIds) {
throw new Error("No last known connected chain, connect action requires chain ids");
Expand All @@ -60,7 +58,52 @@ export const connect = async (args?: ConnectArgs): Promise<ConnectResult> => {

const { accounts: _account } = useGrazSessionStore.getState();
await wallet.init?.();
if (
isCapsule(currentWalletType) &&
useGrazSessionStore.getState().capsuleClient &&
Object.values(useGrazSessionStore.getState().accounts || []).length > 0
) {
const connectedChains = chainIds.map((x) => chains!.find((y) => y.chainId === x)!);
const _resAcc = useGrazSessionStore.getState().accounts;
useGrazSessionStore.setState({ status: "connecting" });

const key = await wallet.getKey(chainIds[0]!);
const resultAcccounts: Record<string, Key> = {};
chainIds.forEach((chainId) => {
resultAcccounts[chainId] = {
...key,
bech32Address: toBech32(
chains!.find((x) => x.chainId === chainId)!.bech32Config.bech32PrefixAccAddr,
fromBech32(key.bech32Address).data,
),
};
});
useGrazSessionStore.setState((prev) => ({
accounts: { ...(prev.accounts || {}), ...resultAcccounts },
}));

useGrazInternalStore.setState((prev) => ({
recentChainIds: [...(prev.recentChainIds || []), ...chainIds].filter((thing, i, arr) => {
return arr.indexOf(thing) === i;
}),
}));
useGrazSessionStore.setState((prev) => ({
activeChainIds: [...(prev.activeChainIds || []), ...chainIds].filter((thing, i, arr) => {
return arr.indexOf(thing) === i;
}),
}));
useGrazSessionStore.setState({
status: "connected",
});
return { accounts: _resAcc!, walletType: currentWalletType, chains: connectedChains };
}
await wallet.enable(chainIds);
if (isCapsule(currentWalletType)) {
const connectedChains = chainIds.map((x) => chains!.find((y) => y.chainId === x)!);
const _resAcc = useGrazSessionStore.getState().accounts;
useGrazSessionStore.setState({ status: "connecting" });
return { accounts: _resAcc!, walletType: currentWalletType, chains: connectedChains };
}
if (!isWalletConnect(currentWalletType)) {
const key = await wallet.getKey(chainIds[0]!);
const resultAcccounts: Record<string, Key> = {};
Expand Down
8 changes: 6 additions & 2 deletions packages/graz/src/actions/chains.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ChainInfo } from "@keplr-wallet/types";

import { useGrazInternalStore } from "../store";
import type { WalletType } from "../types/wallet";
import { WalletType } from "../types/wallet";
import type { ConnectResult } from "./account";
import { connect } from "./account";
import { getWallet } from "./wallet";
Expand Down Expand Up @@ -34,7 +34,11 @@ export interface SuggestChainArgs {

export const suggestChain = async ({ chainInfo, walletType }: SuggestChainArgs): Promise<ChainInfo> => {
const wallet = getWallet(walletType);
await wallet.experimentalSuggestChain(chainInfo);
if (walletType === WalletType.CAPSULE) {
await connect({ chainId: chainInfo.chainId, walletType });
} else {
await wallet.experimentalSuggestChain(chainInfo);
}
return chainInfo;
};

Expand Down
Loading

0 comments on commit 422e5ff

Please sign in to comment.