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

[SPE-246] Add support for EVM wallets, separate source and destination wallets #51

Merged
merged 35 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
71bf627
add support for evm wallets, separate source and destination wallets
thal0x Sep 28, 2023
6705251
resolve merge conflicts
thal0x Sep 28, 2023
605d625
fix broken tests
thal0x Sep 28, 2023
56d07f0
add tests
thal0x Sep 28, 2023
122aa18
adjust tests
thal0x Sep 29, 2023
4a71855
configure all supported chains
thal0x Sep 29, 2023
4a69b8e
make it build
thal0x Sep 29, 2023
a950512
get balances
thal0x Oct 2, 2023
f23098e
execute evm txs
thal0x Oct 3, 2023
bc4df4e
testnets
thal0x Oct 3, 2023
5c4813f
update sdk version
thal0x Oct 3, 2023
2bd6646
rpc not grpc
thal0x Oct 3, 2023
3a41910
evm integration
thal0x Oct 5, 2023
b7db3af
remove Chains context
thal0x Oct 5, 2023
d4b2110
update skip sdk version
thal0x Oct 5, 2023
beeaf76
cleanup
thal0x Oct 5, 2023
8bdaa2d
update tests
thal0x Oct 5, 2023
0b1fe84
catch up to head
thal0x Oct 5, 2023
24566de
rename useChainByChainID to useChainByID
thal0x Oct 5, 2023
01b9cb1
rename useChainByChainID to useChainByID
thal0x Oct 5, 2023
624d075
handle no fee assets
thal0x Oct 5, 2023
49d54cc
add tests for new query hooks
thal0x Oct 5, 2023
c96551e
handle no route found (this was handled before where did it go???)
thal0x Oct 8, 2023
65a5bbe
handle no route found (this was handled before where did it go???)
thal0x Oct 8, 2023
9ba0c3e
feat: add gitignore for src/chains/
grikomsn Oct 16, 2023
2b515fe
feat: make generate.js script
grikomsn Oct 16, 2023
68ec36e
feat: run generate script on webpack server side
grikomsn Oct 16, 2023
16220ed
refactor: replace registry arr iter with record
grikomsn Oct 16, 2023
8fbdda7
refactor: typed chain id
grikomsn Oct 16, 2023
09be28c
fix: update generate script entrypoints
grikomsn Oct 16, 2023
55cf6af
feat: optimized chain registry as record object (#57)
Oct 19, 2023
815bfc9
remove dead code
thal0x Oct 23, 2023
4537103
catch up to main
thal0x Oct 23, 2023
a85ba40
Merge pull request #55 from skip-mev/remove-chain-context
thal0x Oct 23, 2023
d007345
Merge pull request #53 from skip-mev/finish-evm-integration
thal0x Oct 23, 2023
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
654 changes: 384 additions & 270 deletions fixtures/chains.ts

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import "@testing-library/jest-dom/extend-expect";

global.ResizeObserver = require("resize-observer-polyfill");

// eslint-disable-next-line @typescript-eslint/no-var-requires
global.TextEncoder = require("util").TextEncoder;
10 changes: 10 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ const nextConfig = {
"@buf/cosmos_cosmos-sdk.bufbuild_es",
"@buf/evmos_evmos.bufbuild_es",
"@buf/cosmos_ibc.bufbuild_es",
"wagmi",
"@tanstack/query-sync-storage-persister",
"@tanstack/react-query",
"@tanstack/query-core",
"@tanstack/react-query-persist-client",
"@tanstack/query-persist-client-core",
"@wagmi/core",
"@wagmi/connectors",
"viem",
"abitype",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all just due to weird CJS vs ESM interactions when running Jest. I think I'm going to add a task to migrate to vitest since it doesn't have these problems and I've had a good experience with it in the skip-router repo

]
: [],
};
Expand Down
2,427 changes: 2,371 additions & 56 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-tooltip": "^1.0.6",
"@skip-router/core": "^0.0.7",
"@skip-router/core": "^0.1.0-rc6",
"@tanstack/react-query": "^4.29.5",
"@types/node": "20.1.2",
"@types/react": "18.2.6",
Expand All @@ -63,7 +63,9 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "3.3.2",
"usehooks-ts": "^2.9.1"
"usehooks-ts": "^2.9.1",
"viem": "^1.12.2",
"wagmi": "^1.4.2"
},
"devDependencies": {
"@playwright/test": "^1.38.0",
Expand Down
31 changes: 31 additions & 0 deletions public/metamask-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/components/RouteDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@ const RouteDisplay: FC<Props> = ({ route }) => {
return;
}

if ("axelarTransfer" in operation) {
// TODO: Implement
return;
}

const sourceChain = operation.transfer.chainID;

let destinationChain = "";
Expand All @@ -322,6 +327,8 @@ const RouteDisplay: FC<Props> = ({ route }) => {
if ("swapOut" in nextOperation.swap) {
destinationChain = nextOperation.swap.swapOut.swapVenue.chainID;
}
} else if ("axelarTransfer" in nextOperation) {
destinationChain = nextOperation.axelarTransfer.toChain;
} else {
destinationChain = nextOperation.transfer.chainID;
}
Expand Down
27 changes: 27 additions & 0 deletions src/components/RouteLoadingBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const RouteLoadingBanner = () => (
<div className="bg-black text-white/50 font-medium uppercase text-xs p-3 rounded-md flex items-center w-full text-left">
<p className="flex-1">Finding best route...</p>
<svg
className="animate-spin h-4 w-4 inline-block text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
</div>
);

export default RouteLoadingBanner;
20 changes: 20 additions & 0 deletions src/components/RouteTransactionCountBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FC } from "react";

const RouteTransactionCountBanner: FC<{
numberOfTransactions: number;
}> = ({ numberOfTransactions }) => (
<div className="bg-black text-white/50 font-medium uppercase text-xs p-3 rounded-md flex items-center w-full text-left">
<p className="flex-1">
This route requires{" "}
{numberOfTransactions === 1 && (
<span className="text-white">1 Transaction</span>
)}
{numberOfTransactions > 1 && (
<span className="text-white">{numberOfTransactions} Transactions</span>
)}{" "}
to complete
</p>
</div>
);

export default RouteTransactionCountBanner;
115 changes: 49 additions & 66 deletions src/components/SwapWidget/SwapWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,18 @@
import { WalletStatus } from "@cosmos-kit/core";
import { useChain } from "@cosmos-kit/react";
import { ArrowsUpDownIcon } from "@heroicons/react/20/solid";
import { FC, Fragment } from "react";

import { useChains } from "@/context/chains";
import { useAccount } from "@/hooks/useAccount";

import AssetInput from "../AssetInput";
import { ConnectedWalletButton } from "../ConnectedWalletButton";
import { ConnectWalletButtonSmall } from "../ConnectWalletButtonSmall";
import RouteLoadingBanner from "../RouteLoadingBanner";
import RouteTransactionCountBanner from "../RouteTransactionCountBanner";
import TransactionDialog from "../TransactionDialog";
import { useWalletModal, WalletModal } from "../WalletModal";
import { useSwapWidget } from "./useSwapWidget";

const RouteLoading = () => (
<div className="bg-black text-white/50 font-medium uppercase text-xs p-3 rounded-md flex items-center w-full text-left">
<p className="flex-1">Finding best route...</p>
<svg
className="animate-spin h-4 w-4 inline-block text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
</div>
);

const RouteTransactionCountBanner: FC<{
numberOfTransactions: number;
}> = ({ numberOfTransactions }) => (
<div className="bg-black text-white/50 font-medium uppercase text-xs p-3 rounded-md flex items-center w-full text-left">
<p className="flex-1">
This route requires{" "}
{numberOfTransactions === 1 && (
<span className="text-white">1 Transaction</span>
)}
{numberOfTransactions > 1 && (
<span className="text-white">{numberOfTransactions} Transactions</span>
)}{" "}
to complete
</p>
</div>
);

export const SwapWidget: FC = () => {
const { openWalletModal } = useWalletModal();
const { chains } = useChains();
Expand All @@ -78,35 +36,42 @@ export const SwapWidget: FC = () => {
onDestinationAssetChange,
} = useSwapWidget();

const {
status: walletConnectStatus,
address,
wallet,
} = useChain(sourceChain?.record?.chain.chain_name ?? "cosmoshub");
const { address, isWalletConnected, wallet } = useAccount(
sourceChain?.chainID ?? "cosmoshub-4",
);

const { address: destinationChainAddress } = useAccount(
destinationChain?.chainID ?? "cosmoshub-4",
);

const shouldShowDestinationWalletButton =
!!sourceChain &&
!!destinationChain &&
sourceChain.chainType !== destinationChain.chainType;

return (
<Fragment>
<div>
<div className="space-y-6">
<div className="flex items-center justify-between">
<p className="font-semibold text-2xl">From</p>
{address &&
wallet &&
walletConnectStatus === WalletStatus.Connected ? (
{address && wallet && isWalletConnected ? (
<ConnectedWalletButton
address={address}
onClick={openWalletModal}
walletName={wallet.prettyName}
onClick={() => openWalletModal(sourceChain?.chainID ?? "")}
walletName={wallet.walletPrettyName}
walletLogo={
wallet.logo
? typeof wallet.logo === "string"
? wallet.logo
: wallet.logo.major
wallet.walletInfo.logo
? typeof wallet.walletInfo.logo === "string"
? wallet.walletInfo.logo
: wallet.walletInfo.logo.major
: ""
}
/>
) : (
<ConnectWalletButtonSmall onClick={openWalletModal} />
<ConnectWalletButtonSmall
onClick={() => openWalletModal(sourceChain?.chainID ?? "")}
/>
)}
</div>
<div data-testid="source">
Expand Down Expand Up @@ -146,6 +111,24 @@ export const SwapWidget: FC = () => {
</button>
</div>
<p className="font-semibold text-2xl">To</p>
{shouldShowDestinationWalletButton ? (
<div className="absolute inset-y-0 right-0 flex items-center">
<button
className="bg-[#FF486E]/20 hover:bg-[#FF486E]/30 text-[#FF486E] text-xs font-semibold rounded-lg py-1 px-2.5 flex items-center gap-1 transition-colors focus:outline-none"
onClick={() =>
openWalletModal(destinationChain?.chainID ?? "cosmoshub-4")
}
data-testid="destination-wallet-btn"
>
{destinationChainAddress
? `${destinationChainAddress.slice(
0,
8,
)}...${destinationChainAddress.slice(-5)}`
: "Connect Wallet"}
</button>
</div>
) : null}
</div>
<div data-testid="destination">
<AssetInput
Expand All @@ -157,23 +140,23 @@ export const SwapWidget: FC = () => {
chains={chains}
/>
</div>
{routeLoading && <RouteLoading />}
{routeLoading && <RouteLoadingBanner />}
{route && !routeLoading && (
<RouteTransactionCountBanner
numberOfTransactions={numberOfTransactions}
/>
)}
{sourceChain && walletConnectStatus !== WalletStatus.Connected && (
{sourceChain && !isWalletConnected && (
<button
className="bg-[#FF486E] text-white font-semibold py-4 rounded-md w-full transition-transform hover:scale-105 hover:rotate-1"
onClick={async () => {
openWalletModal();
onClick={() => {
openWalletModal(sourceChain.chainID);
}}
>
Connect Wallet
</button>
)}
{sourceChain && walletConnectStatus === WalletStatus.Connected && (
{sourceChain && isWalletConnected && (
<div className="space-y-4">
<TransactionDialog
route={route}
Expand All @@ -189,7 +172,7 @@ export const SwapWidget: FC = () => {
)}
</div>
</div>
<WalletModal chainID={sourceChain?.chainID ?? "cosmoshub-4"} />
<WalletModal />
</Fragment>
);
};
24 changes: 24 additions & 0 deletions src/components/SwapWidget/useSwapWidget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useChain } from "@cosmos-kit/react";
import { ethers } from "ethers";
import { useEffect, useMemo, useState } from "react";
import { useNetwork, useSwitchNetwork } from "wagmi";

import { AssetWithMetadata, useAssets } from "@/context/assets";
import { Chain, useChains } from "@/context/chains";
Expand Down Expand Up @@ -102,6 +103,29 @@ export function useSwapWidget() {
return amountIn > balance;
}, [balances, formValues.amountIn, formValues.sourceAsset]);

const { chain: currentEvmChain } = useNetwork();

const { switchNetwork } = useSwitchNetwork();

useEffect(() => {
if (
!formValues.sourceChain ||
formValues.sourceChain.chainType === "cosmos"
) {
return;
}

if (!currentEvmChain || !switchNetwork) {
return;
}

const chainID = parseInt(formValues.sourceChain.chainID);

if (currentEvmChain.id !== chainID) {
switchNetwork(chainID);
}
}, [currentEvmChain, formValues.sourceChain, switchNetwork]);

return {
amountIn: formValues.amountIn,
amountOut,
Expand Down
Loading