Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add metamask snap cosmos wallet integration #148

Merged
merged 1 commit into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/docs/change-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ sidebar_position: 8

# Changelog

## Version 0.1.8

- Added [Metamask Snap Cosmos](https://github.com/cosmos/snap) integration

## Version 0.1.7

- Fix Metamask Snap Leap when signing

## Version 0.1.6

- Added capsule wallet integration
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_position: 1
## Features

- 🪝 20+ hooks for interfacing with wallets, clients, signers, etc. (connecting, view balances, send tokens, etc.)
- 💳 Multiple wallet supports (Keplr, Leap, Cosmostation, Vectis, Station, XDefi, Metamask Snap, WalletConnect)
- 💳 Multiple wallet supports (Keplr, Leap, Cosmostation, Vectis, Station, XDefi, Metamask Snap, WalletConnect, Capsule)
- ⚙️ Generate mainnet & testnet `ChainInfo`
- 📚 Built-in caching, request deduplication, and all the good stuff from [`@tanstack/react-query`](https://tanstack.com/query) and [`zustand`](https://github.com/pmndrs/zustand)
- 🔄 Auto refresh on wallet and network change
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/types/walletType.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ enum WalletType {
WC_LEAP_MOBILE = "wc_leap_mobile",
WC_COSMOSTATION_MOBILE = "wc_cosmostation_mobile",
METAMASK_SNAP_LEAP = "metamask_snap_leap",
METAMASK_SNAP_COSMOS = "metamask_snap_cosmos",
STATION = "station",
XDEFI = "xdefi",
CAPSULE = "capsule",
}
```
3 changes: 3 additions & 0 deletions example/next/ui/connect-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export const ConnectButton: FC = () => {
{wallets.metamask_snap_leap ? (
<Button onClick={() => handleConnect(WalletType.METAMASK_SNAP_LEAP)}>Metamask Snap Leap</Button>
) : null}
{wallets.metamask_snap_cosmos ? (
<Button onClick={() => handleConnect(WalletType.METAMASK_SNAP_COSMOS)}>Metamask Snap Cosmos</Button>
) : null}
{wallets.capsule ? <Button onClick={() => handleConnect(WalletType.CAPSULE)}>Capsule</Button> : null}
</Stack>
</ModalContent>
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 @@ -38,6 +38,10 @@ export const listedWallets = {
name: "Metamask Snap Leap",
imgSrc: "/assets/wallet-icon-metamask.png",
},
[WalletType.METAMASK_SNAP_COSMOS]: {
name: "Metamask Snap Cosmos",
imgSrc: "/assets/wallet-icon-metamask.png",
},
[WalletType.WALLETCONNECT]: {
name: "WalletConnect",
imgSrc: "/assets/wallet-icon-walletconnect.png",
Expand Down
10 changes: 2 additions & 8 deletions packages/graz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,10 @@
"@cosmjs/tendermint-rpc": "*",
"@leapwallet/cosmos-social-login-capsule-provider": "^0.0.30",
"@leapwallet/cosmos-social-login-capsule-provider-ui": "^0.0.47",
"long": "^4",
"react": ">=17"
},
"peerDependenciesMeta": {
"long": {
"optional": true
}
},
"dependencies": {
"@clack/prompts": "^0.7.0",
"@cosmsnap/snapper": "^0.1.29",
"@keplr-wallet/cosmos": "^0.12.20",
"@keplr-wallet/types": "^0.12.23",
"@metamask/providers": "^12.0.0",
Expand All @@ -71,7 +65,7 @@
"@web3modal/standalone": "^2.4.3",
"cac": "^6.7.14",
"cosmos-directory-client": "0.0.6",
"wadesta": "^0.0.5",
"long": "4",
"zustand": "^4.5.2"
},
"devDependencies": {
Expand Down
84 changes: 84 additions & 0 deletions packages/graz/src/actions/wallet/cosmos-metamask-snap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { CosmosSnap, installSnap, isSnapInstalled } from "@cosmsnap/snapper";

import { useGrazInternalStore } from "../../../store";
import type { KnownKeys, Wallet } from "../../../types/wallet";
import type { ChainId } from "../../../utils/multi-chain";

const metamaskSnapCosmosKeysMap: KnownKeys = {};

export const getMetamaskSnapCosmos = (): Wallet => {
const ethereum = window.ethereum;
let cosmos = window.cosmos;
if (ethereum) {
const init = async () => {
const clientVersion = await ethereum.request({
method: "web3_clientVersion",
});

const isMetamask = (clientVersion as string).includes("MetaMask");

if (!isMetamask) throw new Error("Metamask is not installed");

if (typeof window.okxwallet !== "undefined") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (window.okxwallet.isOkxWallet) {
throw new Error("You have OKX Wallet installed. Please disable and reload the page to use Metamask Snap.");
}
}
const version = (clientVersion as string).split("MetaMask/v")[1]?.split(".")[0];
const isSupportMMSnap = Number(version) >= 11;
if (!isSupportMMSnap) throw new Error("Metamask Snap is not supported in this version");

const installedSnap = await isSnapInstalled();
if (!installedSnap) await installSnap();
window.cosmos = new CosmosSnap();
cosmos = window.cosmos;
return true;
};

const enable = async (chainId: ChainId) => {
const installedSnap = await isSnapInstalled();
if (!installedSnap) await installSnap();
};

const getOfflineSignerAuto = async (chainId: string) => {
const key = await cosmos.getKey(chainId);
if (key.isNanoLedger) return cosmos.getOfflineSignerOnlyAmino(chainId);
return cosmos.getOfflineSigner(chainId);
};

return {
init,
enable,
getOfflineSigner: (chainId: string) => cosmos.getOfflineSigner(chainId),
experimentalSuggestChain: async (chainInfo) => {
await cosmos.experimentalSuggestChain(chainInfo);
},
signAmino: async (chainId, signer, signDoc) => {
return cosmos.signAmino(chainId, signer, signDoc);
},
getKey: async (chainId) => {
if (typeof metamaskSnapCosmosKeysMap[chainId] !== "undefined") return metamaskSnapCosmosKeysMap[chainId]!;

return cosmos.getKey(chainId);
},
getOfflineSignerAuto,
getOfflineSignerOnlyAmino: (chainId) => {
return cosmos.getOfflineSignerOnlyAmino(chainId);
},
signDirect: async (chainId, signer, signDoc) => {
// @ts-expect-error - signDoc is not the same as SignDoc
return cosmos.signDirect(chainId, signer, signDoc);
},
signArbitrary: async (chainId, signer, data) => {
return cosmos.signArbitrary(chainId, signer, data);
},
disable: async (chainId) => {
chainId && (await cosmos.deleteChain(chainId));
},
};
}
useGrazInternalStore.getState()._notFoundFn();
throw new Error("window.ethereum is not defined");
};
6 changes: 5 additions & 1 deletion packages/graz/src/actions/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { grazSessionDefaultValues, useGrazInternalStore, useGrazSessionStore } f
import type { Wallet } from "../../types/wallet";
import { WALLET_TYPES, WalletType } from "../../types/wallet";
import { getCapsule } from "./capsule";
import { getMetamaskSnapCosmos } from "./cosmos-metamask-snap";
import { getCosmostation } from "./cosmostation";
import { getKeplr } from "./keplr";
import { getLeap } from "./leap";
import { getMetamaskSnapLeap } from "./metamask-snap/leap";
import { getMetamaskSnapLeap } from "./leap-metamask-snap/leap";
import { getStation } from "./station";
import { getVectis } from "./vectis";
import { getWalletConnect } from "./wallet-connect";
Expand Down Expand Up @@ -80,6 +81,9 @@ export const getWallet = (type: WalletType = useGrazInternalStore.getState().wal
case WalletType.METAMASK_SNAP_LEAP: {
return getMetamaskSnapLeap();
}
case WalletType.METAMASK_SNAP_COSMOS: {
return getMetamaskSnapCosmos();
}
case WalletType.STATION: {
return getStation();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
import Long from "long";

import { useGrazInternalStore } from "../../../store";
import type { SignAminoParams, SignDirectParams, Wallet } from "../../../types/wallet";
import type { KnownKeys, SignAminoParams, SignDirectParams, Wallet } from "../../../types/wallet";
import type { ChainId } from "../../../utils/multi-chain";
import type { GetSnapsResponse, Snap } from "./types";

Expand All @@ -20,11 +20,7 @@ export interface GetMetamaskSnap {
params?: Record<string, unknown>;
}

export interface KnownKeys {
[key: string]: Key
}

const knownKeysMap: KnownKeys = {};
const metamaskSnapLeapKeysMap: KnownKeys = {};

/**
* Function to return {@link Wallet} object and throws and error if it does not exist on `window`.
Expand Down Expand Up @@ -161,8 +157,9 @@ export const getMetamaskSnap = (params?: GetMetamaskSnap): Wallet => {
};

// getKey from @leapwallet/cosmos-snap-provider return type is wrong
const getKey = async (chainId: string): Promise<Key> => {
if (typeof knownKeysMap[chainId] !== 'undefined') return knownKeysMap[chainId] as Key;
const getKey = async (chainId: string): Promise<Key> => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (typeof metamaskSnapLeapKeysMap[chainId] !== "undefined") return metamaskSnapLeapKeysMap[chainId]!;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const res: any = await ethereum.request({
Expand All @@ -187,8 +184,8 @@ export const getMetamaskSnap = (params?: GetMetamaskSnap): Wallet => {
delete res.pubkey;

// Cache the key for future use
knownKeysMap[chainId] = res;
return knownKeysMap[chainId] as Key;
metamaskSnapLeapKeysMap[chainId] = res;
return metamaskSnapLeapKeysMap[chainId]!;
};

const getAccount = async (chainId: string): Promise<AccountData> => {
Expand Down
19 changes: 18 additions & 1 deletion packages/graz/src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,23 @@ const makeRecord = async (client, { filter = "" } = {}) => {
coinGeckoId: mainAsset.coingecko_id,
};

const feeCurrencies = chain.fees?.fee_tokens.map((token) => ({
coinDenom: chain.assets?.find((asset) => asset.denom === token.denom)?.denom_units.at(-1)?.denom || token.denom,
coinMinimalDenom:
chain.assets?.find((asset) => asset.denom === token.denom)?.denom_units[0]?.denom || token.denom,
coinDecimals: Number(chain.assets?.find((asset) => asset.denom === token.denom)?.decimals),
coinGeckoId: chain.assets?.find((asset) => asset.denom === token.denom)?.coingecko_id || "",
gasPriceStep: {
low: Number(token.low_gas_price),
average: Number(token.average_gas_price),
high: Number(token.high_gas_price),
},
}));

if (!feeCurrencies) {
throw new Error(`⚠️\t${chain.name} has no fee currencies, skipping codegen...`);
}

record[chain.path] = {
chainId: chain.chain_id,
currencies: chain.assets.map((asset) => ({
Expand All @@ -200,7 +217,7 @@ const makeRecord = async (client, { filter = "" } = {}) => {
rpc: apis.rpc[0].address || "",
bech32Config: Bech32Address.defaultBech32Config(chain.bech32_prefix),
chainName: chain.chain_name,
feeCurrencies: [nativeCurrency],
feeCurrencies,
stakeCurrency: nativeCurrency,
bip44: {
coinType: chain.slip44 ?? 0,
Expand Down
2 changes: 1 addition & 1 deletion packages/graz/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export * from "./actions/wallet";
export * from "./actions/wallet/cosmostation";
export * from "./actions/wallet/keplr";
export * from "./actions/wallet/leap";
export * from "./actions/wallet/metamask-snap/leap";
export * from "./actions/wallet/leap-metamask-snap/leap";
export * from "./actions/wallet/vectis";
export * from "./actions/wallet/wallet-connect";
export * from "./actions/wallet/wallet-connect/cosmostation";
Expand Down
7 changes: 6 additions & 1 deletion packages/graz/src/types/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Keplr, KeplrIntereactionOptions } from "@keplr-wallet/types";
import type { Keplr, KeplrIntereactionOptions, Key } from "@keplr-wallet/types";

export enum WalletType {
KEPLR = "keplr",
Expand All @@ -14,6 +14,8 @@ export enum WalletType {
WC_COSMOSTATION_MOBILE = "wc_cosmostation_mobile",
// eslint-disable-next-line @typescript-eslint/naming-convention
METAMASK_SNAP_LEAP = "metamask_snap_leap",
// eslint-disable-next-line @typescript-eslint/naming-convention
METAMASK_SNAP_COSMOS = "metamask_snap_cosmos",
STATION = "station",
XDEFI = "xdefi",
CAPSULE = "capsule",
Expand All @@ -32,6 +34,7 @@ export const WALLET_TYPES = [
WalletType.STATION,
WalletType.XDEFI,
WalletType.CAPSULE,
WalletType.METAMASK_SNAP_COSMOS,
];

export type Wallet = Pick<
Expand All @@ -55,3 +58,5 @@ export type Wallet = Pick<

export type SignDirectParams = Parameters<Wallet["signDirect"]>;
export type SignAminoParams = Parameters<Wallet["signAmino"]>;

export type KnownKeys = Record<string, Key>;
26 changes: 0 additions & 26 deletions packages/graz/src/utils/cli/clack.ts

This file was deleted.

Loading
Loading