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

Solana integration #218

Merged
merged 2 commits into from
Mar 24, 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
17,828 changes: 12,590 additions & 5,238 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@sentry/nextjs": "^7.99.0",
"@skip-router/core": "^1.4.0",
"@skip-router/core": "2.0.0-rc.1",
"@solana/spl-token": "^0.4.1",
"@solana/wallet-adapter-react": "^0.15.35",
"@solana/wallet-adapter-wallets": "^0.19.31",
"@solana/web3.js": "^1.91.1",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/query-sync-storage-persister": "^5.18.1",
"@tanstack/react-query": "^5.18.1",
Expand Down
1 change: 1 addition & 0 deletions src/components/ConnectedWalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const ConnectedWalletButton = forwardRef<HTMLButtonElement, Props>(
>
{walletLogo && (
<Image
unoptimized
height={16}
width={16}
alt={walletName}
Expand Down
24 changes: 23 additions & 1 deletion src/components/SwapWidget/useSwapWidget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GasPrice } from "@cosmjs/stargate";
import { useManager as useCosmosManager } from "@cosmos-kit/react";
import { Asset, BridgeType } from "@skip-router/core";
import { useWallet } from "@solana/wallet-adapter-react";
import { BigNumber } from "bignumber.js";
import { MouseEvent, useCallback, useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
Expand Down Expand Up @@ -59,6 +60,7 @@
},
});
const { disconnect } = useWagmiDisconnect();
const { wallets } = useWallet();

const [userTouchedDstAsset, setUserTouchedDstAsset] = useState(false);

Expand Down Expand Up @@ -119,6 +121,7 @@
assets: srcAssets,
enabled: !isAnyDisclosureOpen,
});
console.log("balances", isAnyDisclosureOpen, balances);

const customGasAmount = useSettingsStore((state) => state.customGasAmount);

Expand All @@ -140,6 +143,7 @@
if (!amountIn || !balances || !srcAsset) {
return false;
}
console.log("isAmountError", amountIn, srcAsset, balances);

const parsedAmount = BigNumber(amountIn || "0");
const parsedBalance = BigNumber(balances[srcAsset.denom] ?? "0").shiftedBy(-(srcAsset.decimals ?? 6));
Expand Down Expand Up @@ -644,13 +648,22 @@
disconnect();
}
}
if (srcChain && srcChain.chainType === "svm") {
const solanaWallet = wallets.find((w) => w.adapter.name === srcTrack?.walletName);

if (solanaWallet?.adapter.connected) {
trackWallet.track("source", srcChain.chainID, solanaWallet.adapter.name, srcChain.chainType);
} else {
trackWallet.untrack("source");
}
}
},
{
equalityFn: shallow,
fireImmediately: true,
},
);
}, [connector, evmChain, getWalletRepo, switchNetworkAsync]);
}, [connector, disconnect, evmChain, getWalletRepo, switchNetworkAsync, wallets]);

/**
* sync destination chain wallet connections
Expand Down Expand Up @@ -706,13 +719,22 @@
disconnect();
}
}
if (dstChain && dstChain.chainType === "svm") {
const solanaWallet = wallets.find((w) => w.adapter.name === srcTrack?.walletName);

if (solanaWallet?.adapter.connected) {
trackWallet.track("destination", dstChain.chainID, solanaWallet.adapter.name, dstChain.chainType);
} else {
trackWallet.untrack("destination");
}
}
},
{
equalityFn: shallow,
fireImmediately: true,
},
);
}, [connector, evmChain, getWalletRepo, srcChain, switchNetworkAsync]);

Check warning on line 737 in src/components/SwapWidget/useSwapWidget.ts

View workflow job for this annotation

GitHub Actions / test

React Hook useEffect has missing dependencies: 'disconnect' and 'wallets'. Either include them or remove the dependency array

/**
* sync destination chain wallet connections on track wallet level
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ function TransactionDialogContent({ route, onClose, isAmountError, transactionCo
}

const estimatedFinalityTime = useFinalityTimeEstimate(route);

if (isTxComplete && txStatus.data?.isSuccess) {
return (
<TransactionSuccessView
Expand Down
137 changes: 92 additions & 45 deletions src/components/WalletModal/WalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useManager } from "@cosmos-kit/react";
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/16/solid";
import { ArrowLeftIcon, FaceFrownIcon } from "@heroicons/react/20/solid";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { useWallet } from "@solana/wallet-adapter-react";
import Image from "next/image";
import toast from "react-hot-toast";
import { useAccount, useConnect, useDisconnect } from "wagmi";
Expand All @@ -26,6 +27,7 @@ export interface MinimalWallet {
connect: () => Promise<void>;
disconnect: () => Promise<void>;
isWalletConnected: boolean;
isAvailable?: boolean;
}

interface Props {
Expand Down Expand Up @@ -71,7 +73,9 @@ export function WalletModal({ chainType, onClose, wallets }: Props) {
href={
chainType === "cosmos"
? "https://cosmos.network/wallets"
: "https://ethereum.org/en/wallets/find-wallet"
: chainType === "solana"
? "https://solana.com/ecosystem/explore?categories=wallet"
: "https://ethereum.org/en/wallets/find-wallet"
}
className="inline-flex items-center gap-1 text-red-500 hover:underline"
>
Expand All @@ -89,54 +93,66 @@ export function WalletModal({ chainType, onClose, wallets }: Props) {
)}
>
<ScrollArea.Viewport className="h-full w-full py-4">
{wallets.map((wallet) => (
<WalletListItem
key={wallet.walletName}
chainType={chainType}
walletName={wallet.walletName}
className={cn(
"group relative mb-2 data-[unsupported=true]:opacity-30",
"data-[unsupported=true]:before:absolute data-[unsupported=true]:before:inset-0 data-[unsupported=true]:before:cursor-not-allowed",
)}
>
<button
className="flex w-full items-center gap-2 rounded-lg p-2 transition-colors focus:-outline-offset-2 group-hover:bg-[#FF486E]/20"
onClick={() => onWalletConnect(wallet)}
>
{wallet.walletInfo.logo && (
<Image
height={36}
width={36}
alt={wallet.walletPrettyName}
className="h-9 w-9 object-contain"
src={
typeof wallet.walletInfo.logo === "string" ? wallet.walletInfo.logo : wallet.walletInfo.logo.major
}
aria-hidden="true"
/>
{wallets.map((wallet) => {
// currently only svm chainType that have isAvailable
return (
<WalletListItem
key={wallet.walletName}
chainType={chainType}
walletName={wallet.walletName}
className={cn(
"group relative mb-2 data-[unsupported=true]:opacity-30",
"data-[unsupported=true]:before:absolute data-[unsupported=true]:before:inset-0 data-[unsupported=true]:before:cursor-not-allowed",
)}
<p className="flex-1 text-left font-semibold">
{wallet.walletPrettyName === "Leap Cosmos MetaMask"
? "Metamask (Leap Snap)"
: wallet.walletPrettyName}
</p>
</button>
{wallet.isWalletConnected && (
>
<button
aria-label={`Disconnect ${wallet.walletPrettyName}`}
className="absolute right-4 top-1/2 flex -translate-y-1/2 items-center gap-1 rounded-lg bg-[#FF486E]/20 px-2.5 py-1 text-xs font-semibold text-[#FF486E] transition-colors focus:outline-none group-hover:bg-[#FF486E]/30"
onClick={async (event) => {
event.stopPropagation();
await wallet.disconnect();
context && trackWallet.untrack(context);
onClose();
}}
className="flex w-full items-center gap-2 rounded-lg p-2 transition-colors focus:-outline-offset-2 group-hover:bg-[#FF486E]/20"
onClick={() => onWalletConnect(wallet)}
disabled={chainType === "svm" && wallet.isAvailable !== true}
>
Disconnect
{wallet.walletInfo.logo && (
<Image
unoptimized
height={36}
width={36}
alt={wallet.walletPrettyName}
className="h-9 w-9 object-contain"
src={
typeof wallet.walletInfo.logo === "string"
? wallet.walletInfo.logo
: wallet.walletInfo.logo.major
}
aria-hidden="true"
/>
)}
<p className="flex-1 text-left font-semibold">
{wallet.walletPrettyName === "Leap Cosmos MetaMask"
? "Metamask (Leap Snap)"
: wallet.walletPrettyName}
</p>
</button>
)}
</WalletListItem>
))}
{wallet.isWalletConnected && (
<button
aria-label={`Disconnect ${wallet.walletPrettyName}`}
className="absolute right-4 top-1/2 flex -translate-y-1/2 items-center gap-1 rounded-lg bg-[#FF486E]/20 px-2.5 py-1 text-xs font-semibold text-[#FF486E] transition-colors focus:outline-none group-hover:bg-[#FF486E]/30"
onClick={async (event) => {
event.stopPropagation();
await wallet.disconnect();
context && trackWallet.untrack(context);
onClose();
}}
>
Disconnect
</button>
)}
{chainType === "svm" && wallet.isAvailable !== true && (
<div className="absolute right-4 top-1/2 flex -translate-y-1/2 items-center gap-1 rounded-lg bg-[#c2c2c2]/20 px-2.5 py-1 text-xs font-semibold text-[#909090] transition-colors focus:outline-none group-hover:bg-[#c2c2c2]/30">
Not Installed
</div>
)}
</WalletListItem>
);
})}
</ScrollArea.Viewport>
<ScrollArea.Scrollbar
className="z-20 flex touch-none select-none py-4 transition-colors ease-out data-[orientation=horizontal]:h-2 data-[orientation=vertical]:w-2 data-[orientation=horizontal]:flex-col"
Expand All @@ -154,6 +170,7 @@ function WalletModalWithContext() {
const { connector: currentConnector } = useAccount();
const { chainID, context } = useWalletModal();
const { disconnectAsync } = useDisconnect();
// evm
const { connectors, connectAsync } = useConnect({
mutation: {
onError: (err) => {
Expand All @@ -167,7 +184,10 @@ function WalletModalWithContext() {
},
},
});
// cosmos
const { getWalletRepo } = useManager();
// solana
const { wallets: solanaWallets } = useWallet();

const { setIsOpen } = useWalletModal();

Expand Down Expand Up @@ -239,6 +259,33 @@ function WalletModalWithContext() {
}
}

if (chainType === "svm") {
for (const wallet of solanaWallets) {
const minimalWallet: MinimalWallet = {
walletName: wallet.adapter.name,
walletPrettyName: wallet.adapter.name,
walletInfo: {
logo: wallet.adapter.icon,
},
connect: async () => {
try {
await wallet.adapter.connect();
context && trackWallet.track(context, chainID, wallet.adapter.name, chainType);
} catch (error) {
console.error(error);
}
},
disconnect: async () => {
await wallet.adapter.disconnect();
context && trackWallet.untrack(context);
},
isWalletConnected: wallet.adapter.connected,
isAvailable: wallet.readyState === "Installed",
};
wallets.push(minimalWallet);
}
}

return (
<DialogContent>
<WalletModal
Expand Down
17 changes: 16 additions & 1 deletion src/config/endpoints.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-check

/**
* @typedef {{ endpoint: string | undefined; isPrivate: boolean } | undefined} EndpointConfig
* @typedef {{ endpoint: string | undefined; isPrivate: boolean; isApiKey?: boolean } | undefined} EndpointConfig
*/

/**
Expand All @@ -17,6 +17,21 @@ exports.getWhitelabelEndpoint = (chainID, type) => {
/** @type {string | undefined} */
let endpoint;

if (chainID === "solana-devnet") {
return {
endpoint: "https://devnet.helius-rpc.com",
isPrivate: false,
isApiKey: true,
};
}
if (chainID === "solana") {
return {
endpoint: "https://mainnet.helius-rpc.com",
isPrivate: false,
isApiKey: true,
};
}

if (type === "api") {
endpoint = exports.CUSTOM_API_CHAIN_IDS[chainID];
} else {
Expand Down
43 changes: 41 additions & 2 deletions src/hooks/useAccount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { WalletClient } from "@cosmos-kit/core";
import { useManager as useCosmosManager } from "@cosmos-kit/react";
import { useWallet } from "@solana/wallet-adapter-react";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import { useAccount as useWagmiAccount } from "wagmi";
Expand All @@ -22,6 +23,8 @@ export function useAccount(context: TrackWalletCtx) {

const wagmiAccount = useWagmiAccount();

const { wallets } = useWallet();

const getIsLedger = async (client: WalletClient, chainId: string) => {
const isLedger = await isWalletClientUsingLedger(client, chainId);
return isLedger;
Expand Down Expand Up @@ -107,7 +110,43 @@ export function useAccount(context: TrackWalletCtx) {
},
};
}
}, [chain, context, cosmosWallet, trackedWallet, wagmiAccount, cosmosWalletIsLedgerQuery.data]);

if (chain.chainType === "svm") {
const solanaWallet = wallets.find((w) => w.adapter.name === trackedWallet?.walletName);
return {
address: solanaWallet?.adapter.publicKey?.toBase58(),
isWalletConnected: solanaWallet?.adapter.connected && !solanaWallet.adapter.connecting,
wallet: solanaWallet
? {
walletName: solanaWallet.adapter.name,
walletPrettyName: solanaWallet.adapter.name,
walletInfo: {
logo: solanaWallet.adapter.icon,
},
}
: undefined,
chainType: chain.chainType,
connect: () => {
return solanaWallet?.adapter.connect().then(() => {
trackWallet.track(context, chain.chainID, solanaWallet.adapter.name, chain.chainType);
});
},
disconnect: () => {
return solanaWallet?.adapter.disconnect().then(() => {
trackWallet.untrack(context);
});
},
};
}
}, [
trackedWallet,
chain,
cosmosWallet,
cosmosWalletIsLedgerQuery.data,
context,
wagmiAccount.address,
wagmiAccount.isConnected,
wagmiAccount.connector,
wallets,
]);
return account;
}
Loading
Loading