Skip to content

Commit

Permalink
Merge pull request #218 from skip-mev/solana-integration
Browse files Browse the repository at this point in the history
Solana integration
  • Loading branch information
codingki authored Mar 24, 2024
2 parents 1689f20 + 7e8dfd4 commit ec17960
Show file tree
Hide file tree
Showing 15 changed files with 12,894 additions and 5,318 deletions.
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 @@ export function useSwapWidget() {
},
});
const { disconnect } = useWagmiDisconnect();
const { wallets } = useWallet();

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

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

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

Expand All @@ -140,6 +143,7 @@ export function useSwapWidget() {
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 @@ export function useSwapWidget() {
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,6 +719,15 @@ export function useSwapWidget() {
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,
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

0 comments on commit ec17960

Please sign in to comment.