Skip to content

Commit

Permalink
wip capsule
Browse files Browse the repository at this point in the history
  • Loading branch information
codingki committed Apr 13, 2024
1 parent faa9cce commit 2cdb6a7
Show file tree
Hide file tree
Showing 17 changed files with 6,416 additions and 184 deletions.
5 changes: 5 additions & 0 deletions example/next/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const nextConfig = {
typescript: {
ignoreBuildErrors: Boolean(process.env.VERCEL),
},
transpilePackages: [
"@leapwallet/cosmos-social-login-capsule-provider",
"@leapwallet/cosmos-social-login-capsule-provider-ui",
"@leapwallet/capsule-web-sdk-lite",
],
};

module.exports = nextConfig;
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
5 changes: 5 additions & 0 deletions example/next/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChakraProvider, extendTheme } from "@chakra-ui/react";
import { CapsuleEnvironment } from "@leapwallet/cosmos-social-login-capsule-provider";
import { GrazProvider } from "graz";
import type { NextPage } from "next";
import type { AppProps } from "next/app";
Expand All @@ -21,6 +22,10 @@ const CustomApp: NextPage<AppProps> = ({ Component, pageProps }) => {
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,
},
},
capsuleConfig: {
apiKey: "72c07c099c0f3d8e744bb0754a11726b",
env: CapsuleEnvironment.BETA,
},
}}
>
<Component {...pageProps} />
Expand Down
25 changes: 23 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, onSuccessfulLogin, setModalState } = 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,19 @@ const HomePage: NextPage = () => {
<ConnectButton />
</HStack>
</Stack>
<LeapSocialLogin
capsule={client?.getClient() || undefined}
onAfterLoginSuccessful={() => {
void onSuccessfulLogin?.();
console.log("login successful");
}}
onLoginFailure={() => {
console.log("login failure");
}}
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
2 changes: 2 additions & 0 deletions packages/graz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
"@cosmjs/proto-signing": "*",
"@cosmjs/stargate": "*",
"@cosmjs/tendermint-rpc": "*",
"@leapwallet/cosmos-social-login-capsule-provider": "^0.0.30",
"@leapwallet/cosmos-social-login-capsule-provider-ui": "^0.0.47",
"long": "*",
"react": ">=17"
},
Expand Down
6 changes: 5 additions & 1 deletion 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 Down Expand Up @@ -61,6 +61,10 @@ export const connect = async (args?: ConnectArgs): Promise<ConnectResult> => {
const { accounts: _account } = useGrazSessionStore.getState();
await wallet.init?.();
await wallet.enable(chainIds);
if (isCapsule(currentWalletType)) {
// ignore the return value
throw new Error("CAPSULE_OPEN_MODAL");
}
if (!isWalletConnect(currentWalletType)) {
const key = await wallet.getKey(chainIds[0]!);
const resultAcccounts: Record<string, Key> = {};
Expand Down
4 changes: 3 additions & 1 deletion packages/graz/src/actions/configure.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { ChainInfo } from "@keplr-wallet/types";

import type { ChainConfig, GrazInternalStore } from "../store";
import type { CapsuleConfig, ChainConfig, GrazInternalStore } from "../store";
import { useGrazInternalStore } from "../store";
import type { WalletType } from "../types/wallet";

export interface ConfigureGrazArgs {
defaultWallet?: WalletType;
chains: ChainInfo[];
chainsConfig?: Record<string, ChainConfig>;
capsuleConfig?: CapsuleConfig;
onNotFound?: () => void;
onReconnectFailed?: () => void;
walletConnect?: GrazInternalStore["walletConnect"];
Expand All @@ -27,6 +28,7 @@ export const configureGraz = (args: ConfigureGrazArgs): ConfigureGrazArgs => {
useGrazInternalStore.setState((prev) => ({
walletConnect: args.walletConnect || prev.walletConnect,
walletType: args.defaultWallet || prev.walletType,
capsuleConfig: args.capsuleConfig || prev.capsuleConfig,
walletDefaultOptions: args.walletDefaultOptions || prev.walletDefaultOptions,
chains: args.chains,
chainsConfig: args.chainsConfig || prev.chainsConfig,
Expand Down
114 changes: 114 additions & 0 deletions packages/graz/src/actions/wallet/capsule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { fromBech32, toBech32 } from "@cosmjs/encoding";
import type { Key } from "@keplr-wallet/types";

import { RECONNECT_SESSION_KEY } from "../../constant";
import { useGrazInternalStore, useGrazSessionStore } from "../../store";
import type { Wallet } from "../../types/wallet";
import { WalletType } from "../../types/wallet";

export const getCapsule = (): Wallet => {
if (!useGrazInternalStore.getState().capsuleConfig?.apiKey || !useGrazInternalStore.getState().capsuleConfig?.env) {
throw new Error("Capsule configuration is not set");
}

const init = async () => {
const Capsule = (await import("@leapwallet/cosmos-social-login-capsule-provider")).CapsuleProvider;
const client = new Capsule({
apiKey: useGrazInternalStore.getState().capsuleConfig?.apiKey,
env: useGrazInternalStore.getState().capsuleConfig?.env,
});
useGrazSessionStore.setState({ capsuleClient: client });
return client;
};

const enable = async (_chainId: string | string[]) => {
const chainId = typeof _chainId === "string" ? [_chainId] : _chainId;
let client = useGrazSessionStore.getState().capsuleClient;
if (!client) {
client = await init();
}
useGrazInternalStore.setState({ capsuleState: { showModal: true, chainId } });
};

const onSuccessLogin = async () => {
const client = useGrazSessionStore.getState().capsuleClient;
const { chains } = useGrazInternalStore.getState();
if (!client) throw new Error("Capsule client is not initialized");
if (!chains) throw new Error("Chains are not set");
await client.enable();
const chainIds = useGrazInternalStore.getState().capsuleState?.chainId;
if (!chainIds) throw new Error("Chain ids are not set");
const key = await client.getAccount(chainIds[0]!);
const resultAcccounts: Record<string, Key> = {};
chainIds.forEach((chainId) => {
resultAcccounts[chainId] = {
address: fromBech32(key.address).data,
bech32Address: toBech32(
chains.find((x) => x.chainId === chainId)!.bech32Config.bech32PrefixAccAddr,
fromBech32(key.address).data,
),
algo: key.algo,
name: key.username || "",
pubKey: key.pubkey,
isKeystone: false,
isNanoLedger: false,
};
});
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;
}),
}));

useGrazInternalStore.setState({
walletType: WalletType.CAPSULE,
_reconnect: false,
_reconnectConnector: WalletType.CAPSULE,
});
useGrazSessionStore.setState({
status: "connected",
});
typeof window !== "undefined" && window.sessionStorage.setItem(RECONNECT_SESSION_KEY, "Active");

useGrazInternalStore.setState({ capsuleState: null });
};
const getKey = async (chainId: string) => {
const client = useGrazSessionStore.getState().capsuleClient;
if (!client) throw new Error("Capsule client is not initialized");
const key = await client.getAccount(chainId);

return {
address: fromBech32(key.address).data,
bech32Address: key.address,
algo: key.algo,
name: key.username || "",
pubKey: key.pubkey,
isKeystone: false,
isNanoLedger: false,
};
};

const getOfflineSigner = (chainId: string) => {
const client = useGrazSessionStore.getState().capsuleClient;
if (!client) throw new Error("Capsule client is not initialized");
return client.getOfflineSigner(chainId);
};

return {
init,
enable,
onSuccessLogin,
getKey,
// @ts-expect-error - CapsuleAminoSigner | OfflineDirectSigner
getOfflineSigner,
};
};
8 changes: 8 additions & 0 deletions packages/graz/src/actions/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RECONNECT_SESSION_KEY } from "../../constant";
import { grazSessionDefaultValues, useGrazInternalStore, useGrazSessionStore } from "../../store";
import type { Wallet } from "../../types/wallet";
import { WALLET_TYPES, WalletType } from "../../types/wallet";
import { getCapsule } from "./capsule";
import { getCosmostation } from "./cosmostation";
import { getKeplr } from "./keplr";
import { getLeap } from "./leap";
Expand Down Expand Up @@ -85,6 +86,9 @@ export const getWallet = (type: WalletType = useGrazInternalStore.getState().wal
case WalletType.XDEFI: {
return getXDefi();
}
case WalletType.CAPSULE: {
return getCapsule();
}
default: {
throw new Error("Unknown wallet type");
}
Expand All @@ -102,6 +106,10 @@ export const getAvailableWallets = (): Record<WalletType, boolean> => {
return Object.fromEntries(WALLET_TYPES.map((type) => [type, checkWallet(type)])) as Record<WalletType, boolean>;
};

export const isCapsule = (type: WalletType): boolean => {
return type === WalletType.CAPSULE;
};

export const isWalletConnect = (type: WalletType): boolean => {
return (
type === WalletType.WALLETCONNECT ||
Expand Down
8 changes: 7 additions & 1 deletion packages/graz/src/hooks/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,13 @@ export type UseConnectChainArgs = MutationEventArgs<ConnectArgs, ConnectResult>;
export const useConnect = ({ onError, onLoading, onSuccess }: UseConnectChainArgs = {}) => {
const queryKey = ["USE_CONNECT", onError, onLoading, onSuccess];
const mutation = useMutation(queryKey, connect, {
onError: (err, args) => Promise.resolve(onError?.(err, args)),
onError: (err, args) =>
Promise.resolve(() => {
// @ts-expect-error - ignore
if (err?.message !== "CAPSULE_OPEN_MODAL") {
onError?.(err, args);
}
}),
onMutate: onLoading,
onSuccess: (connectResult) => Promise.resolve(onSuccess?.(connectResult)),
});
Expand Down
20 changes: 20 additions & 0 deletions packages/graz/src/hooks/capsule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getCapsule } from "../actions/wallet/capsule";
import { useGrazInternalStore, useGrazSessionStore } from "../store";

export const useCapsule = () => {
const capsuleState = useGrazInternalStore((state) => state.capsuleState);
const capsuleClient = useGrazSessionStore((state) => state.capsuleClient);
const capsule = getCapsule();

return {
setModalState: (state: boolean) =>
useGrazInternalStore.setState({
capsuleState: {
showModal: state,
},
}),
modalState: Boolean(capsuleState?.showModal),
client: capsuleClient,
onSuccessfulLogin: capsule.onSuccessLogin,
};
};
1 change: 1 addition & 0 deletions packages/graz/src/hooks/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const useActiveWalletType = () => {
isWalletConnect: x.walletType === WalletType.WALLETCONNECT,
isMetamaskSnapLeap: x.walletType === WalletType.METAMASK_SNAP_LEAP,
isStation: x.walletType === WalletType.STATION,
isCapsule: x.walletType === WalletType.CAPSULE,
}),
shallow,
);
Expand Down
1 change: 1 addition & 0 deletions packages/graz/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from "./actions/wallet/wallet-connect/keplr";
export * from "./actions/wallet/wallet-connect/leap";
export * from "./chains";
export * from "./hooks/account";
export * from "./hooks/capsule";
export * from "./hooks/chains";
export * from "./hooks/clients";
export * from "./hooks/methods";
Expand Down
17 changes: 17 additions & 0 deletions packages/graz/src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ChainInfo, Keplr, Key } from "@keplr-wallet/types";
import type { CapsuleEnvironment, CapsuleProvider } from "@leapwallet/cosmos-social-login-capsule-provider";
import type { ISignClient, SignClientTypes } from "@walletconnect/types";
import type { Web3ModalConfig } from "@web3modal/standalone";
import { create } from "zustand";
Expand All @@ -21,8 +22,20 @@ export interface WalletConnectStore {
options: SignClientTypes.Options | null;
web3Modal?: Pick<Web3ModalConfig, "themeVariables" | "themeMode" | "privacyPolicyUrl" | "termsOfServiceUrl"> | null;
}

export interface CapsuleConfig {
apiKey: string;
env: CapsuleEnvironment;
}

export interface CapsuleState {
showModal: boolean;
chainId?: string[];
}
export interface GrazInternalStore {
recentChainIds: string[] | null;
capsuleConfig: CapsuleConfig | null;
capsuleState: CapsuleState | null;
chains: ChainInfo[] | null;
chainsConfig: Record<string, ChainConfig> | null;
/**
Expand All @@ -44,6 +57,7 @@ export interface GrazSessionStore {
activeChainIds: string[] | null;
status: "connected" | "connecting" | "reconnecting" | "disconnected";
wcSignClients: Map<WalletType, ISignClient>;
capsuleClient: CapsuleProvider | null;
}

export type GrazSessionPersistedStore = Pick<GrazSessionStore, "accounts" | "activeChainIds">;
Expand All @@ -57,6 +71,8 @@ export const grazInternalDefaultValues: GrazInternalStore = {
recentChainIds: null,
chains: null,
chainsConfig: null,
capsuleConfig: null,
capsuleState: null,
multiChainFetchConcurrency: 3,
walletType: WalletType.KEPLR,
walletConnect: {
Expand All @@ -75,6 +91,7 @@ export const grazSessionDefaultValues: GrazSessionStore = {
activeChainIds: null,
status: "disconnected",
wcSignClients: new Map(),
capsuleClient: null,
};

const sessionOptions: PersistOptions<GrazSessionStore, GrazSessionPersistedStore> = {
Expand Down
Loading

0 comments on commit 2cdb6a7

Please sign in to comment.