@xswap-link/sdk
Version:
JavaScript SDK for XSwap platform
450 lines (393 loc) • 12.5 kB
text/typescript
import {
DEFAULT_DESTINATION_CHAIN_ID,
DEFAULT_DESTINATION_TOKEN_ADDR,
DEFAULT_SOURCE_CHAIN_ID,
DEFAULT_SOURCE_TOKEN_ADDR,
} from "@src/constants";
import { BridgeTokensDictionary, Chain, Ecosystem, Token } from "@src/models";
export const getDefaultSwapSourceChain = ({
supportedChains,
}: {
supportedChains: Chain[];
}): Chain => {
const defaultSourceChain =
supportedChains.find(
(chain) => chain.chainId === DEFAULT_SOURCE_CHAIN_ID,
) || supportedChains[0];
if (!defaultSourceChain) {
throw new Error(
`getDefaultSourceChain > couldn't find default source chain`,
);
}
return defaultSourceChain;
};
export const getDefaultDestinationChain = ({
supportedChains,
}: {
supportedChains: Chain[];
}): Chain => {
const defaultTargetChain =
supportedChains.find(
(chain) => chain.chainId === DEFAULT_DESTINATION_CHAIN_ID,
) || supportedChains[0];
if (!defaultTargetChain) {
throw new Error(
`getDefaultDestinationChain > couldn't find default destination chain`,
);
}
return defaultTargetChain;
};
export const getDefaultSwapTokenForChain = (
chain: Chain,
defaultTokenAddress: string,
sameChain?: boolean,
sourceTokenId?: string,
) => {
// First check if there are any supported tokens at all
const hasAnySupportedTokens = chain.tokens.some((token) => token.supported);
if (!hasAnySupportedTokens) {
return undefined;
}
const defaultToken = chain.tokens.find(
(token) =>
token.address.toLowerCase() === defaultTokenAddress.toLowerCase() &&
token.supported,
);
if (defaultToken) {
return defaultToken;
}
const fallbackToken = chain.tokens.find((token) =>
sameChain
? token.supported && token.tokenId !== sourceTokenId
: token.supported,
);
if (fallbackToken) {
return fallbackToken;
}
return undefined;
};
export const getDefaultBridgeTokenForChain = ({
chain,
bridgeTokensDictionary,
}: {
chain: Chain | undefined;
bridgeTokensDictionary: BridgeTokensDictionary;
}) => {
if (!chain) {
return undefined;
}
const xswapToken = chain.tokens.find((token) => token.tokenId === "XSWAP");
if (xswapToken) {
return xswapToken;
}
const fallbackTokenAddress = Object.keys(
bridgeTokensDictionary[chain.chainId] || {},
)[0];
return chain.tokens.find(
(token) =>
token.address.toLowerCase() === fallbackTokenAddress?.toLowerCase(),
);
};
const isSwapSourceValid = (sourceChain: Chain) => sourceChain.swapSupported;
const isSwapTargetChainValid = ({
sourceChain,
targetChain,
}: {
sourceChain: Chain | undefined;
targetChain: Chain;
}) => {
if (!sourceChain) {
throw new Error(`isSwapTargetChainValid > sourceChain should be defined.`);
}
return (
targetChain.swapSupported &&
sourceChain.supportedDstForSwap?.includes(targetChain.chainId)
);
};
const isBridgeSourceValid = (sourceChain: Chain | undefined) =>
!sourceChain || sourceChain.bridgeSupported;
const isBridgeTargetValid = ({
sourceChain,
targetChain,
sourceToken,
}: {
sourceChain: Chain | undefined;
targetChain: Chain | undefined;
sourceToken: Token | undefined;
}) => {
// We set it to a valid state initially.
let isSupportedDstForBridge = true;
let isSameToken = true;
let isSameChain = false;
if (sourceChain && targetChain) {
isSupportedDstForBridge = !!sourceChain.supportedDstForBridge?.includes(
targetChain.chainId,
);
}
if (sourceToken && targetChain) {
// Bridge target chain is invalid when it doesn't have the same token available as selected in the source.
isSameToken = !!targetChain.tokens.find(
(token) => token.tokenId === sourceToken.tokenId,
);
}
if (sourceChain && targetChain) {
// @TODO use bridge_tokens collection
// See: https://github.com/xswap-link/xswap-sdk/pull/200#discussion_r1905586346
isSameChain = sourceChain.chainId === targetChain.chainId;
}
return isSameToken && !isSameChain && isSupportedDstForBridge;
};
type MapToValidPathParams = {
source: { chain: Chain | undefined; token: Token | undefined };
target: { chain: Chain | undefined; token: Token | undefined };
type: "SWAP" | "BRIDGE";
supportedChains: Chain[];
bridgeTokensDictionary: BridgeTokensDictionary;
};
const mapToValidPathSwap = ({
source,
target,
supportedChains,
}: Omit<MapToValidPathParams, "type">) => {
let newSourceChain: Chain | undefined =
source.chain || getDefaultSwapSourceChain({ supportedChains });
let newTargetChain: Chain | undefined =
target.chain || getDefaultDestinationChain({ supportedChains });
let newSourceToken: Token | undefined =
source.token ||
getDefaultSwapTokenForChain(newSourceChain, DEFAULT_SOURCE_TOKEN_ADDR);
let newTargetToken: Token | undefined =
target.token ||
getDefaultSwapTokenForChain(
newTargetChain,
DEFAULT_DESTINATION_TOKEN_ADDR,
newSourceChain.chainId === newTargetChain.chainId,
newSourceToken?.tokenId,
);
if (
!newSourceChain ||
!newTargetChain ||
!newSourceToken ||
!newTargetToken
) {
// This should never occur if our app is configured properly.
return {
source: { chain: undefined, token: undefined },
target: { chain: undefined, token: undefined },
};
}
if (!isSwapSourceValid(newSourceChain)) {
newSourceChain = getDefaultSwapSourceChain({ supportedChains });
// We can't have dangling token from a different chain.
newSourceToken = undefined;
if (!isSwapSourceValid(newSourceChain)) {
// This should never occur if our app is configured properly.
console.error("[WRONG CONFIG] default swap source chain is invalid.");
// Find any chain that is valid.
newSourceChain = supportedChains.find((chain) =>
isSwapSourceValid(chain),
);
if (!newSourceChain) {
throw new Error(
"mapToValidSwap > None of the chains supports swap. Couldn't set source chain.",
);
}
}
}
if (
!isSwapTargetChainValid({
sourceChain: newSourceChain,
targetChain: newTargetChain,
})
) {
newTargetChain = getDefaultDestinationChain({ supportedChains });
// We can't have dangling token from a different chain.
newTargetToken = undefined;
if (
!isSwapTargetChainValid({
sourceChain: newSourceChain,
targetChain: newTargetChain,
})
) {
// This should never occur if our app is configured properly.
console.error("[WRONG CONFIG] default swap target chain is invalid.");
// Find any chain that is valid.
newTargetChain = supportedChains.find((chain) =>
isSwapTargetChainValid({
sourceChain: newSourceChain,
targetChain: chain,
}),
);
if (!newTargetChain) {
throw new Error(
"mapToValidSwap > None of the chains supports swap. Couldn't set target chain.",
);
}
}
}
// Setting tokens
// validate if the token is supported in the new source chain
newSourceToken = newSourceChain.tokens.find(
(token) => token.tokenId === newSourceToken?.tokenId && token.supported,
);
newTargetToken = newTargetChain.tokens.find(
(token) => token.tokenId === newTargetToken?.tokenId && token.supported,
);
if (!newSourceToken) {
newSourceToken = getDefaultSwapTokenForChain(
newSourceChain,
DEFAULT_SOURCE_TOKEN_ADDR,
);
}
if (!newTargetToken || newSourceToken === newTargetToken) {
newTargetToken = getDefaultSwapTokenForChain(
newTargetChain,
DEFAULT_DESTINATION_TOKEN_ADDR,
newSourceChain.chainId === newTargetChain.chainId,
newSourceToken?.tokenId,
);
}
return {
source: { chain: newSourceChain, token: newSourceToken },
target: { chain: newTargetChain, token: newTargetToken },
};
};
const mapToValidPathBridge = ({
source,
target,
supportedChains,
bridgeTokensDictionary,
}: Omit<MapToValidPathParams, "type">) => {
let newSourceChain = source.chain;
let newTargetChain = target.chain;
let newSourceToken = source.token;
let newTargetToken = target.token;
if (!isBridgeSourceValid(newSourceChain)) {
newSourceChain = undefined;
newSourceToken = undefined;
if (!isBridgeSourceValid(newSourceChain)) {
// This should never occur if our app is configured properly.
console.error("[WRONG CONFIG] default bridge source chain is invalid.");
// Find any chain that is valid.
newSourceChain = supportedChains.find((chain) =>
isBridgeSourceValid(chain),
);
if (!newSourceChain) {
throw new Error(
"mapToValidSwap > None of the chains supports bridge. Couldn't set source chain.",
);
}
}
}
if (
!isBridgeTargetValid({
sourceChain: newSourceChain,
targetChain: newTargetChain,
sourceToken: newSourceToken,
})
) {
// We can't have dangling token from a different chain.
newTargetToken = undefined;
if (
!isBridgeTargetValid({
sourceChain: newSourceChain,
targetChain: newTargetChain,
sourceToken: newSourceToken,
})
) {
newTargetChain = undefined;
}
}
// Setting tokens
// check if the source token is bridgeable anywhere
if (newSourceToken && newSourceChain) {
// First validate if the token is supported in the source chain
newSourceToken = newSourceChain.tokens.find(
(token) => token.tokenId === newSourceToken?.tokenId && token.supported,
);
if (newSourceToken) {
const isBridgeable =
Object.keys(
bridgeTokensDictionary[newSourceChain.chainId]?.[
newSourceChain.ecosystem === Ecosystem.SOLANA
? newSourceToken.address
: newSourceToken.address.toLowerCase()
] || {},
).length > 0;
if (!isBridgeable) {
newSourceToken = undefined;
}
}
}
// Validate if target token is supported in the target chain
if (newTargetToken && newTargetChain) {
newTargetToken = newTargetChain.tokens.find(
(token) => token.tokenId === newTargetToken?.tokenId && token.supported,
);
}
if (!newSourceToken) {
const defaultToken = getDefaultBridgeTokenForChain({
chain: newSourceChain,
bridgeTokensDictionary,
});
if (defaultToken && newSourceChain && newTargetChain) {
const isDefaultTokenBridgeable =
!!bridgeTokensDictionary[newSourceChain.chainId]?.[
defaultToken.address.toLowerCase()
]?.[newTargetChain.chainId];
if (isDefaultTokenBridgeable) {
newSourceToken = defaultToken;
} else {
newSourceToken = undefined;
}
} else {
// Default was not found, so let's keep it unset.
newSourceToken = undefined;
}
}
if (newSourceToken?.tokenId !== newTargetToken?.tokenId) {
newTargetToken = undefined;
}
if (!newTargetToken) {
if (newSourceToken && newSourceChain && newTargetChain) {
const correspondingTokenAddress =
bridgeTokensDictionary[newSourceChain.chainId]?.[
newSourceChain.ecosystem === Ecosystem.SOLANA
? newSourceToken.address
: newSourceToken.address.toLowerCase()
]?.[newTargetChain.chainId];
const correspondingToken = newTargetChain.tokens.find(
(token) =>
token.address.toLowerCase() ===
correspondingTokenAddress?.toLowerCase(),
);
newTargetToken = correspondingToken;
} else {
// If source token is not defined, destination token can't be defined as well.
newTargetToken = undefined;
}
}
if (!newTargetToken && newSourceToken) {
newTargetChain = undefined;
}
return {
source: { chain: newSourceChain, token: newSourceToken },
target: { chain: newTargetChain, token: newTargetToken },
};
};
// It takes chain and token for source and target and returns them.
// If provided data is valid, it returns the same.
// If data is not valid, it returns other available chain/token.
export const mapToValidPath = ({
type,
...params
}: MapToValidPathParams): {
source: { chain: Chain | undefined; token: Token | undefined };
target: { chain: Chain | undefined; token: Token | undefined };
} => {
if (type === "SWAP") {
return mapToValidPathSwap(params);
} else {
return mapToValidPathBridge(params);
}
};