diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index 7ed336509..780e8ff40 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -2,16 +2,13 @@ import { Preview } from "@storybook/react";
import { MemoryRouter } from "react-router-dom";
import { default as GlobalStyles } from "../src/components/GlobalStyles/GlobalStyles";
-import { OnboardContext, useOnboardManager } from "../src/hooks/useOnboard";
const preview: Preview = {
decorators: [
(Story) => (
-
-
-
+
),
],
diff --git a/api/_address.ts b/api/_address.ts
new file mode 100644
index 000000000..e72a6833c
--- /dev/null
+++ b/api/_address.ts
@@ -0,0 +1,42 @@
+import { Address, Hex, padHex, toBytes, toHex, trim } from "viem";
+import { getAddressDecoder, getAddressEncoder } from "@solana/kit";
+import { isAddress } from "ethers/lib/utils";
+import { isAddress as isSvmAddress } from "@solana/kit"; // @solana/web3.js@v2
+
+// exports
+export { isSvmAddress };
+
+// utils
+export function isEvmAddress(value: string): value is Address {
+ return isAddress(value);
+}
+
+export function svmToHex(pubkey: string): Hex {
+ if (!isSvmAddress(pubkey)) {
+ throw new Error("Invalid SVM Address");
+ }
+ const bytes = getAddressEncoder().encode(pubkey);
+ return toHex(new Uint8Array(bytes));
+}
+
+export function hexToBase58(address: Address) {
+ if (!isEvmAddress(address)) {
+ throw new Error("Invalid EVM Address");
+ }
+ const bytes = trim(toBytes(address));
+ return getAddressDecoder().decode(bytes);
+}
+
+export function toBytes32(value: string) {
+ if (isSvmAddress(value)) {
+ // byte length already checked at this stage
+ return svmToHex(value);
+ }
+ if (isEvmAddress(value)) {
+ return padHex(value, {
+ size: 32,
+ dir: "left",
+ });
+ }
+ throw new Error("Invalid Address type. Must be valid EVM or SVM address");
+}
diff --git a/api/_utils.ts b/api/_utils.ts
index eb11f0d03..dbb453d6a 100644
--- a/api/_utils.ts
+++ b/api/_utils.ts
@@ -34,6 +34,7 @@ import {
size,
string,
Struct,
+ union,
} from "superstruct";
import enabledMainnetRoutesAsJson from "../src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json";
import enabledSepoliaRoutesAsJson from "../src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json";
@@ -90,6 +91,7 @@ type LoggingUtility = sdk.relayFeeCalculator.Logger;
type RpcProviderName = keyof typeof rpcProvidersJson.providers.urls;
import { getEnvs } from "./_env";
+import { isEvmAddress, isSvmAddress } from "./_address";
const {
REACT_APP_HUBPOOL_CHAINID,
@@ -1693,12 +1695,28 @@ export function parsableBigNumberString() {
});
}
-export function validAddress() {
- return define("validAddress", (value) =>
- utils.isAddress(value as string)
- );
+export function validEvmAddress() {
+ return define("validEvmAddress", (value) => {
+ try {
+ return isEvmAddress(value as string);
+ } catch {
+ return false;
+ }
+ });
+}
+
+export function validSvmAddress() {
+ return define("validSvmAddress", (value) => {
+ try {
+ return isSvmAddress(value as string);
+ } catch {
+ return false;
+ }
+ });
}
+export const validAddress = () => union([validEvmAddress(), validSvmAddress()]);
+
export function validAddressOrENS() {
return define("validAddressOrENS", (value) => {
const ensDomainRegex =
diff --git a/e2e/wallet-setup/connected.setup.ts b/e2e/wallet-setup/connected.setup.ts
index b3989717c..d5863b684 100644
--- a/e2e/wallet-setup/connected.setup.ts
+++ b/e2e/wallet-setup/connected.setup.ts
@@ -20,12 +20,13 @@ export default defineWalletSetup(MM_PASSWORD, async (context, walletPage) => {
// Go to a locally hosted MetaMask Test Dapp.
await page.goto(E2E_DAPP_URL);
- // Connect via web3-onboard modal
+ // Connect via wallet
await page
.getByRole("banner")
.locator(page.getByRole("button", { name: "Connect" }))
.click();
- await page.getByRole("button", { name: "MetaMask" }).click();
+
+ await page.getByText("MetaMask").first().click();
await metamask.connectToDapp(["Account 1"]);
});
diff --git a/package.json b/package.json
index d1a00c065..7b5b05d03 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"@across-protocol/sdk": "^4.1.61",
"@amplitude/analytics-browser": "^2.3.5",
"@balancer-labs/sdk": "1.1.6-beta.16",
+ "@coral-xyz/borsh": "^0.30.1",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.14.0",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
@@ -19,6 +20,11 @@
"@safe-global/safe-apps-provider": "^0.18.0",
"@safe-global/safe-apps-sdk": "^8.1.0",
"@sentry/react": "^7.37.2",
+ "@solana/wallet-adapter-base": "^0.9.23",
+ "@solana/wallet-adapter-react": "^0.15.35",
+ "@solana/wallet-adapter-wallets": "^0.19.32",
+ "@solana/web3.js": "^1.98.0",
+ "@solana/kit": "^2.1.0",
"@tanstack/react-query": "v5",
"@tanstack/react-query-devtools": "v5",
"@uniswap/sdk-core": "^5.9.0",
@@ -26,14 +32,6 @@
"@uniswap/v3-sdk": "^3.18.1",
"@upstash/qstash": "^2.7.20",
"@vercel/kv": "^2.0.0",
- "@web3-onboard/coinbase": "^2.4.1",
- "@web3-onboard/core": "^2.24.0",
- "@web3-onboard/gnosis": "^2.2.0",
- "@web3-onboard/injected-wallets": "^2.11.1",
- "@web3-onboard/metamask": "^2.2.0",
- "@web3-onboard/phantom": "^2.1.1",
- "@web3-onboard/react": "^2.11.0",
- "@web3-onboard/walletconnect": "^2.4.6",
"axios": "^0.27.2",
"ethers": "5.7.2",
"framer-motion": "^11.3.19",
@@ -44,7 +42,7 @@
"react": "v18",
"react-dom": "v18",
"react-feather": "^2.0.9",
- "react-pro-sidebar": "^1.1.0-alpha.1",
+ "react-pro-sidebar": "^1.1.0",
"react-router-dom": "v5",
"react-tooltip": "^5.18.0",
"superstruct": "^0.15.4",
@@ -139,6 +137,7 @@
"@vercel/node": "^5.0.2",
"@vitejs/plugin-react": "^4.3.4",
"axios-mock-adapter": "^1.21.2",
+ "buffer": "^6.0.3",
"chalk": "^5.3.0",
"chromatic": "^11.25.1",
"dotenv": "^16.4.5",
diff --git a/scripts/chain-configs/index.ts b/scripts/chain-configs/index.ts
index fd49d7394..3b034a0b6 100644
--- a/scripts/chain-configs/index.ts
+++ b/scripts/chain-configs/index.ts
@@ -21,6 +21,7 @@ export { default as POLYGON_AMOY } from "./polygon-amoy";
export { default as REDSTONE } from "./redstone";
export { default as SCROLL } from "./scroll";
export { default as SEPOLIA } from "./sepolia";
+export { default as SOLANA_DEVNET } from "./solana-devnet";
export { default as SONEIUM } from "./soneium";
export { default as TATARA } from "./tatara";
export { default as UNICHAIN } from "./unichain";
diff --git a/scripts/chain-configs/solana-devnet/index.ts b/scripts/chain-configs/solana-devnet/index.ts
new file mode 100644
index 000000000..aa49cf24b
--- /dev/null
+++ b/scripts/chain-configs/solana-devnet/index.ts
@@ -0,0 +1,23 @@
+import { CHAIN_IDs, PUBLIC_NETWORKS } from "@across-protocol/constants";
+import { utils as sdkUtils } from "@across-protocol/sdk";
+import { ChainConfig } from "../types";
+
+const { getDeployedAddress, getDeployedBlockNumber } = sdkUtils;
+
+const chainId = CHAIN_IDs.SOLANA_DEVNET;
+const chainInfoBase = PUBLIC_NETWORKS[chainId];
+
+export default {
+ ...chainInfoBase,
+ logoPath: "./assets/logo.svg",
+ grayscaleLogoPath: "./assets/grayscale-logo.svg",
+ spokePool: {
+ address: getDeployedAddress("SvmSpoke", chainId),
+ blockNumber: getDeployedBlockNumber("SvmSpoke", chainId),
+ },
+ chainId,
+ publicRpcUrl: "https://api.devnet.solana.com",
+ blockTimeSeconds: 0.5,
+ tokens: ["USDC"],
+ enableCCTP: true,
+} as ChainConfig;
diff --git a/scripts/generate-routes.ts b/scripts/generate-routes.ts
index 72537d4ab..794951d5e 100644
--- a/scripts/generate-routes.ts
+++ b/scripts/generate-routes.ts
@@ -1,14 +1,38 @@
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants";
-import { utils as sdkUtils } from "@across-protocol/sdk";
-
-import { utils } from "ethers";
import { writeFileSync } from "fs";
+import { getAddress, isAddress as isEvmAddress } from "viem";
+import { utils as sdkUtils } from "@across-protocol/sdk";
+import { isAddress as isSvmAddress } from "@solana/kit";
import * as prettier from "prettier";
import path from "path";
import * as chainConfigs from "./chain-configs";
import * as externConfigs from "./extern-configs";
import assert from "assert";
+// TODO: replace with Address utilities from sdk
+export function checksumAddress(address: string) {
+ if (isEvmAddress(address)) {
+ return getAddress(address);
+ }
+ if (isSvmAddress(address)) {
+ return address;
+ }
+ throw new Error("Invalid address");
+}
+
+export const nonEthChains = [
+ CHAIN_IDs.POLYGON,
+ CHAIN_IDs.POLYGON_AMOY,
+ CHAIN_IDs.ALEPH_ZERO,
+ CHAIN_IDs.LENS_SEPOLIA,
+ CHAIN_IDs.SOLANA_DEVNET,
+ CHAIN_IDs.SOLANA,
+];
+
+export function isNonEthChain(chainId: number): boolean {
+ return nonEthChains.includes(chainId);
+}
+
function getTokenSymbolForLogo(tokenSymbol: string): string {
switch (tokenSymbol) {
case "USDC.e":
@@ -69,6 +93,7 @@ export const enabledSepoliaChainConfigs = [
chainConfigs.LISK_SEPOLIA,
chainConfigs.LENS_SEPOLIA,
chainConfigs.UNICHAIN_SEPOLIA,
+ chainConfigs.SOLANA_DEVNET,
chainConfigs.TATARA,
];
@@ -530,15 +555,15 @@ async function generateRoutes(hubPoolChainId = 1) {
const routeFileContent = {
hubPoolChain: config.hubPoolChain,
- hubPoolAddress: utils.getAddress(config.hubPoolAddress),
- hubPoolWethAddress: utils.getAddress(config.hubPoolWethAddress),
- acrossConfigStoreAddress: utils.getAddress(config.acrossConfigStoreAddress),
- acrossTokenAddress: utils.getAddress(config.acrossTokenAddress),
- acceleratingDistributorAddress: utils.getAddress(
+ hubPoolAddress: checksumAddress(config.hubPoolAddress),
+ hubPoolWethAddress: checksumAddress(config.hubPoolWethAddress),
+ acrossConfigStoreAddress: checksumAddress(config.acrossConfigStoreAddress),
+ acrossTokenAddress: checksumAddress(config.acrossTokenAddress),
+ acceleratingDistributorAddress: checksumAddress(
config.acceleratingDistributorAddress
),
- merkleDistributorAddress: utils.getAddress(config.merkleDistributorAddress),
- claimAndStakeAddress: utils.getAddress(config.claimAndStakeAddress),
+ merkleDistributorAddress: checksumAddress(config.merkleDistributorAddress),
+ claimAndStakeAddress: checksumAddress(config.claimAndStakeAddress),
swapAndBridgeAddresses: checksumAddressesOfNestedMap(
config.swapAndBridgeAddresses as Record>
),
@@ -591,7 +616,7 @@ async function generateRoutes(hubPoolChainId = 1) {
const tokenInfo =
TOKEN_SYMBOLS_MAP[tokenSymbol as keyof typeof TOKEN_SYMBOLS_MAP];
return {
- address: utils.getAddress(
+ address: checksumAddress(
tokenInfo.addresses[chainConfig.chainId] as string
),
symbol: tokenSymbol,
@@ -746,7 +771,7 @@ function transformToRoute(
toChain: toChain.chainId,
fromTokenAddress: inputToken.address,
toTokenAddress: outputToken.address,
- fromSpokeAddress: utils.getAddress(route.fromSpokeAddress),
+ fromSpokeAddress: checksumAddress(route.fromSpokeAddress),
fromTokenSymbol: inputTokenSymbol,
toTokenSymbol: outputTokenSymbol,
isNative,
@@ -787,9 +812,9 @@ function getTokenBySymbol(
return {
chainId,
- address: utils.getAddress(tokenAddress),
+ address: checksumAddress(tokenAddress),
symbol: tokenSymbol,
- l1TokenAddress: utils.getAddress(l1TokenAddress),
+ l1TokenAddress: checksumAddress(l1TokenAddress),
};
}
@@ -835,7 +860,7 @@ function getBridgedUsdcOrVariantSymbol(chainId: number) {
function checksumAddressOfMap(map: Record) {
return Object.entries(map).reduce(
- (acc, [key, value]) => ({ ...acc, [key]: utils.getAddress(value) }),
+ (acc, [key, value]) => ({ ...acc, [key]: checksumAddress(value) }),
{}
);
}
diff --git a/src/Routes.tsx b/src/Routes.tsx
index 06663fb54..e9e3d586a 100644
--- a/src/Routes.tsx
+++ b/src/Routes.tsx
@@ -63,7 +63,6 @@ const warningMessage = `
`;
function useRoutes() {
- const [openSidebar, setOpenSidebar] = useState(false);
const [enableACXBanner, setEnableACXBanner] = useState(true);
const { provider, isContractAddress } = useConnection();
const location = useLocation();
@@ -82,8 +81,6 @@ function useRoutes() {
}, [location.pathname, history]);
return {
- openSidebar,
- setOpenSidebar,
provider,
error,
removeError,
@@ -99,8 +96,6 @@ function useRoutes() {
// Need this component for useLocation hook
const Routes: React.FC = () => {
const {
- openSidebar,
- setOpenSidebar,
error,
removeError,
location,
@@ -140,12 +135,8 @@ const Routes: React.FC = () => {
{isContractAddress && (
{warningMessage}
)}
-
-
+
+
}>
diff --git a/src/assets/icons/arrow-up-right.svg b/src/assets/icons/arrow-up-right.svg
index 477397094..b64b350af 100644
--- a/src/assets/icons/arrow-up-right.svg
+++ b/src/assets/icons/arrow-up-right.svg
@@ -1,3 +1,3 @@
-