UNPKG

@xswap-link/sdk

Version:
450 lines (393 loc) 12.5 kB
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); } };