Skip to content
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
30 changes: 29 additions & 1 deletion sdk/src/configs/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ export {
export const CONTRACTS_CHAIN_IDS = [ARBITRUM, AVALANCHE, BOTANIX] as const;
export const CONTRACTS_CHAIN_IDS_DEV = [...CONTRACTS_CHAIN_IDS, AVALANCHE_FUJI, ARBITRUM_SEPOLIA] as const;
export const SETTLEMENT_CHAIN_IDS = [ARBITRUM, AVALANCHE] as const;
export const SETTLEMENT_CHAIN_IDS_DEV = [...SETTLEMENT_CHAIN_IDS, ARBITRUM_SEPOLIA] as const;
export const SETTLEMENT_CHAIN_IDS_DEV = [...SETTLEMENT_CHAIN_IDS, ARBITRUM_SEPOLIA, AVALANCHE_FUJI] as const;
export const SOURCE_CHAIN_IDS = [
SOURCE_OPTIMISM_SEPOLIA,
SOURCE_SEPOLIA,
SOURCE_BASE_MAINNET,
SOURCE_BSC_MAINNET,
SOURCE_ETHEREUM_MAINNET,
ARBITRUM_SEPOLIA,
ARBITRUM,
AVALANCHE,
AVALANCHE_FUJI,
] as const;

export type ContractsChainId = (typeof CONTRACTS_CHAIN_IDS_DEV)[number];
Expand Down Expand Up @@ -195,6 +199,30 @@ const SOURCE_CHAIN_CONFIGS = {
slug: "ethereum-mainnet",
explorerUrl: "https://etherscan.io/",
},
[ARBITRUM]: {
chainId: ARBITRUM,
name: "Arbitrum",
slug: "arbitrum",
explorerUrl: "https://arbiscan.io/",
},
[AVALANCHE]: {
chainId: AVALANCHE,
name: "Avalanche",
slug: "avalanche",
explorerUrl: "https://snowtrace.io/",
},
[ARBITRUM_SEPOLIA]: {
chainId: ARBITRUM_SEPOLIA,
name: "Arbitrum Sepolia",
slug: "arbitrum-sepolia",
explorerUrl: "https://sepolia.arbiscan.io/",
},
[AVALANCHE_FUJI]: {
chainId: AVALANCHE_FUJI,
name: "Avalanche Fuji",
slug: "avalanche-fuji",
explorerUrl: "https://testnet.snowtrace.io/",
},
// Use this notation to correctly infer chain names, etc. from config
} as const satisfies Record<SourceChainId, SourceChainConfig>;

Expand Down
66 changes: 43 additions & 23 deletions sdk/src/configs/multichain.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
import {
ARBITRUM_SEPOLIA,
ARBITRUM,
AVALANCHE,
SOURCE_OPTIMISM_SEPOLIA,
SOURCE_SEPOLIA,
AVALANCHE_FUJI,
ARBITRUM_SEPOLIA,
SOURCE_BASE_MAINNET,
SOURCE_BSC_MAINNET,
SOURCE_ETHEREUM_MAINNET,
SOURCE_BSC_MAINNET,
SOURCE_SEPOLIA,
SOURCE_OPTIMISM_SEPOLIA,
} from "./chainIds";
import { SettlementChainId, SourceChainId } from "./chains";
import {
SETTLEMENT_CHAIN_IDS,
SETTLEMENT_CHAIN_IDS_DEV,
SettlementChainId,
SOURCE_CHAIN_IDS,
SourceChainId,
} from "./chains";

function ensureExhaustive<T extends number>(value: Record<T, true>): T[] {
return Object.keys(value).map(Number) as T[];
export function isSettlementChain(chainId: number): chainId is SettlementChainId {
return SETTLEMENT_CHAIN_IDS.includes(chainId as any) || SETTLEMENT_CHAIN_IDS_DEV.includes(chainId as any);
}

export const SETTLEMENT_CHAINS: SettlementChainId[] = ensureExhaustive<SettlementChainId>({
[ARBITRUM_SEPOLIA]: true,
[ARBITRUM]: true,
[AVALANCHE]: true,
});
const SOURCE_CHAIN_IDS_MAP: Record<SettlementChainId, SourceChainId[]> = {
[ARBITRUM]: [SOURCE_BASE_MAINNET, SOURCE_ETHEREUM_MAINNET, SOURCE_BSC_MAINNET],
[AVALANCHE]: [SOURCE_BASE_MAINNET, SOURCE_BSC_MAINNET],
[AVALANCHE_FUJI]: [ARBITRUM_SEPOLIA],
[ARBITRUM_SEPOLIA]: [SOURCE_SEPOLIA, SOURCE_OPTIMISM_SEPOLIA],
};

export const SOURCE_CHAINS: SourceChainId[] = ensureExhaustive<SourceChainId>({
[SOURCE_OPTIMISM_SEPOLIA]: true,
[SOURCE_SEPOLIA]: true,
[SOURCE_BASE_MAINNET]: true,
[SOURCE_BSC_MAINNET]: true,
[SOURCE_ETHEREUM_MAINNET]: true,
});
export function isSourceChain(
chainId: number | undefined,
forSettlementChain: number | undefined
): chainId is SourceChainId {
if (!chainId || !forSettlementChain) {
return false;
}

export function isSettlementChain(chainId: number): chainId is SettlementChainId {
return SETTLEMENT_CHAINS.includes(chainId as SettlementChainId);
const sourceChainIds = SOURCE_CHAIN_IDS_MAP[forSettlementChain as SettlementChainId];
if (!sourceChainIds) {
return false;
}

return sourceChainIds.includes(chainId as any);
}

export function isSourceChain(chainId: number | undefined): chainId is SourceChainId {
return SOURCE_CHAINS.includes(chainId as SourceChainId);
/**
* Check if a chain is a source chain for any settlement chain
* Useful when settlement chain context is not available
*/
export function isSourceChainForAnySettlementChain(chainId: number | undefined): chainId is SourceChainId {
if (!chainId) {
return false;
}

return SOURCE_CHAIN_IDS.includes(chainId as any);
}
4 changes: 2 additions & 2 deletions src/components/BridgeModal/BridgeInModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export function BridgeInModal({
const firstChainWithBalance = Object.entries(multichainMarketTokenBalances.balances).find(([chainIdStr, data]) => {
const chainIdNum = Number(chainIdStr);
if (
!isSourceChain(chainIdNum) ||
!isSourceChain(chainIdNum, chainId) ||
(chainIdNum as number) === chainId ||
(chainIdNum as number) === GMX_ACCOUNT_PSEUDO_CHAIN_ID
) {
Expand Down Expand Up @@ -330,7 +330,7 @@ export function BridgeInModal({
paySource={"sourceChain"}
label={t`Deposit`}
onSelectTokenAddress={(newBridgeInChain) => {
if (!isSourceChain(newBridgeInChain)) {
if (!isSourceChain(newBridgeInChain, chainId)) {
return;
}
setBridgeInChain(newBridgeInChain);
Expand Down
50 changes: 35 additions & 15 deletions src/components/DropdownSelector/DropdownSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Listbox } from "@headlessui/react";
import cx from "classnames";

import { NoopWrapper } from "components/NoopWrapper/NoopWrapper";
import TooltipWithPortal from "components/Tooltip/TooltipWithPortal";

import ChevronDownIcon from "img/ic_chevron_down.svg?react";

type Primitive = string | number;
Expand All @@ -18,6 +21,8 @@ export const DropdownSelector = <Id extends Primitive, Option>({
placeholder,
slim = false,
variant,
itemDisabled,
itemDisabledMessage,
}: {
value: Id | undefined;
onChange: (value: Id) => void;
Expand All @@ -27,6 +32,8 @@ export const DropdownSelector = <Id extends Primitive, Option>({
placeholder?: string;
slim?: boolean;
variant?: "ghost";
itemDisabled?: (option: Option) => boolean;
itemDisabledMessage?: (option: Option) => string;
} & WithConditionalItemKey<Id, Option>) => {
return (
<Listbox value={value ?? null} onChange={onChange}>
Expand Down Expand Up @@ -54,21 +61,34 @@ export const DropdownSelector = <Id extends Primitive, Option>({
"border-1/2 border-slate-600 bg-slate-800"
)}
>
{options.map((option) => (
<Listbox.Option
key={itemKey ? itemKey(option) : (option as Primitive)}
value={itemKey ? itemKey(option) : (option as Primitive)}
className={({ active, selected }) =>
cx(
"cursor-pointer",
slim ? "text-body-medium p-4" : "text-body-large px-14 py-8",
(active || selected) && (variant === "ghost" ? "bg-fill-surfaceHover" : "bg-fill-surfaceHover")
)
}
>
<Item option={option} />
</Listbox.Option>
))}
{options.map((option) => {
const isDisabled = itemDisabled?.(option);
const disabledMessage = itemDisabledMessage?.(option);
const Wrapper = isDisabled && disabledMessage ? TooltipWithPortal : NoopWrapper;

return (
<Listbox.Option
key={itemKey ? itemKey(option) : (option as Primitive)}
value={itemKey ? itemKey(option) : (option as Primitive)}
className="p-0"
disabled={isDisabled}
>
{({ active, selected, disabled }) => (
<Wrapper variant="none" as="div" position="bottom" content={disabledMessage}>
<div
className={cx(
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
slim ? "text-body-medium p-4" : "text-body-large px-14 py-8",
(active || selected) && "bg-fill-surfaceHover"
)}
>
<Item option={option} />
</div>
</Wrapper>
)}
</Listbox.Option>
);
})}
</Listbox.Options>
</div>
</Listbox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,11 @@ export function GmSwapBoxDepositWithdrawal() {
}

setPaySource(
isSourceChain(newSrcChainId) ? "sourceChain" : isGmxAccount ? "gmxAccount" : "settlementChain"
isSourceChain(newSrcChainId, chainId)
? "sourceChain"
: isGmxAccount
? "gmxAccount"
: "settlementChain"
);
handleFirstTokenSelect(tokenAddress as ERC20Address | NativeTokenSupportedAddress);
}}
Expand Down
Loading