diff --git a/package.json b/package.json index efdc584bb..73db44740 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "checkVaultTokenDecimals": "ts-node -r dotenv/config ./scripts/checkVaultTokenDecimals.ts && prettier --write ./src/config/vault/*.json", "checkZapAddresses": "ts-node -r dotenv/config ./scripts/checkZapAddresses.ts", "makeExcludeConfig": "ts-node -r dotenv/config ./scripts/makeExcludeConfig.ts", + "fetchStargatePaths": "ts-node -r dotenv/config ./scripts/fetchStargatePaths.ts", "prepare": "husky install", "tsc": "tsc", "tsc-watch": "tsc --watch", diff --git a/scripts/common/addressbook.ts b/scripts/common/addressbook.ts index 15f307a10..38186e577 100644 --- a/scripts/common/addressbook.ts +++ b/scripts/common/addressbook.ts @@ -49,10 +49,11 @@ export const getChainAddressBook = memoize( address: bookToken.address, decimals: bookToken.decimals, symbol: bookToken.symbol, - buyUrl: null, - website: bookToken.website || null, - description: bookToken.description || null, - documentation: bookToken.documentation || null, + buyUrl: undefined, + website: bookToken.website || undefined, + description: bookToken.description || undefined, + documentation: bookToken.documentation || undefined, + risks: [], type: 'erc20', }; } else if (tokenId === nativeSymbol) { @@ -65,10 +66,10 @@ export const getChainAddressBook = memoize( address: bookToken.address, decimals: bookToken.decimals, symbol: nativeSymbol, - buyUrl: null, - website: bookToken.website || null, - description: bookToken.description || null, - documentation: bookToken.documentation || null, + buyUrl: undefined, + website: bookToken.website || undefined, + description: bookToken.description || undefined, + documentation: bookToken.documentation || undefined, type: 'native', }; } else { @@ -79,10 +80,10 @@ export const getChainAddressBook = memoize( address: 'native', decimals: bookToken.decimals, symbol: nativeSymbol, - buyUrl: null, - website: bookToken.website || null, - description: bookToken.description || null, - documentation: bookToken.documentation || null, + buyUrl: undefined, + website: bookToken.website || undefined, + description: bookToken.description || undefined, + documentation: bookToken.documentation || undefined, type: 'native', }; } @@ -94,10 +95,11 @@ export const getChainAddressBook = memoize( address: bookToken.address, decimals: bookToken.decimals, symbol: bookToken.symbol, - buyUrl: null, - website: bookToken.website || null, - description: bookToken.description || null, - documentation: bookToken.documentation || null, + buyUrl: undefined, + website: bookToken.website || undefined, + description: bookToken.description || undefined, + documentation: bookToken.documentation || undefined, + risks: [], type: 'erc20', }; } diff --git a/scripts/common/chains.ts b/scripts/common/chains.ts index 43d872d16..501c58604 100644 --- a/scripts/common/chains.ts +++ b/scripts/common/chains.ts @@ -1,7 +1,7 @@ import { config } from '../../src/config/config'; import { ChainEntity } from '../../src/features/data/entities/chain'; -export type AppChainId = keyof typeof config; +export type AppChainId = ChainEntity['id']; export const chainsByAppId: Record = Object.entries(config).reduce( (acc, [chainId, chainConfig]) => { @@ -12,7 +12,7 @@ export const chainsByAppId: Record = Object.entries(con }; return acc; }, - {} + {} as Record ); -export const allChainIds: AppChainId[] = Object.keys(chainsByAppId); +export const allChainIds: AppChainId[] = Object.keys(chainsByAppId) as AppChainId[]; diff --git a/scripts/common/tokens.ts b/scripts/common/tokens.ts index a732a1918..edf138192 100644 --- a/scripts/common/tokens.ts +++ b/scripts/common/tokens.ts @@ -30,10 +30,10 @@ async function getChainTokens(chain: ChainEntity): Promise { oracleId: chain.walletSettings.nativeCurrency.symbol, address: 'native', decimals: chain.walletSettings.nativeCurrency.decimals, - buyUrl: null, - website: null, - description: null, - documentation: null, + buyUrl: undefined, + website: undefined, + description: undefined, + documentation: undefined, }, { type: 'native', @@ -43,10 +43,10 @@ async function getChainTokens(chain: ChainEntity): Promise { oracleId: chain.walletSettings.nativeCurrency.symbol, address: 'native', decimals: chain.walletSettings.nativeCurrency.decimals, - buyUrl: null, - website: null, - description: null, - documentation: null, + buyUrl: undefined, + website: undefined, + description: undefined, + documentation: undefined, }, ]; } @@ -65,10 +65,11 @@ async function getVaultTokensForChain(chain: ChainEntity): Promise { +): Promise { const { byId } = await getTokensForChain(chainId); const address = byId[id]; - if (!address) return null; + if (!address) return undefined; return getTokenByAddress(address, chainId); } @@ -244,13 +249,13 @@ export async function getTokenById( export async function getTokenByAddress( address: TokenEntity['address'], chainId: ChainEntity['id'] -): Promise { +): Promise { const { byAddress } = await getTokensForChain(chainId); const token = byAddress[address.toLowerCase()]; - return token || null; + return token || undefined; } -export async function getNativeToken(chainId: ChainEntity['id']): Promise { +export async function getNativeToken(chainId: ChainEntity['id']): Promise { const token = await getTokenById('native', chainId); if (!token || !isTokenNative(token)) { throw new Error(`No native token found for chain ${chainId}`); @@ -260,7 +265,7 @@ export async function getNativeToken(chainId: ChainEntity['id']): Promise { +): Promise { const token = await getTokenById('wnative', chainId); if (!token || !isTokenErc20(token)) { console.warn(token); diff --git a/scripts/fetchStargatePaths.ts b/scripts/fetchStargatePaths.ts new file mode 100644 index 000000000..fbd793ab6 --- /dev/null +++ b/scripts/fetchStargatePaths.ts @@ -0,0 +1,295 @@ +import { type Address, createPublicClient, http, parseAbi, type PublicClient } from 'viem'; +import { type ChainEntity } from '../src/features/data/entities/chain'; +import { chainsByAppId } from './common/chains'; +import { buildViemChain } from '../src/features/data/apis/viem/chains'; +import { sample } from 'lodash'; +import { createDependencyFactoryWithCacheByChain } from '../src/features/data/utils/factory-utils'; +import { getTokenByAddress } from './common/tokens'; +import { groupBy } from 'lodash'; +import { saveJson } from './common/files'; + +type Factory = { + address: Address; + chainId: ChainEntity['id']; +}; + +const factories: Factory[] = [ + { chainId: 'ethereum', address: '0x06D538690AF257Da524f25D0CD52fD85b1c2173E' }, + { chainId: 'bsc', address: '0xe7Ec689f432f29383f217e36e680B5C855051f25' }, + { chainId: 'avax', address: '0x808d7c71ad2ba3FA531b068a2417C63106BC0949' }, + { chainId: 'polygon', address: '0x808d7c71ad2ba3FA531b068a2417C63106BC0949' }, + { chainId: 'arbitrum', address: '0x55bDb4164D28FBaF0898e0eF14a589ac09Ac9970' }, + { chainId: 'optimism', address: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df' }, + { chainId: 'fantom', address: '0x9d1B1669c73b033DFe47ae5a0164Ab96df25B944' }, + { chainId: 'metis', address: '0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398' }, + { chainId: 'base', address: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6' }, + { chainId: 'linea', address: '0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398' }, + { chainId: 'kava', address: '0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398' }, + { chainId: 'mantle', address: '0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398' }, +]; + +const getPublicClient = createDependencyFactoryWithCacheByChain( + async (chain): Promise => { + return createPublicClient({ + batch: { + multicall: { + batchSize: 25, + wait: 200, + }, + }, + chain: buildViemChain(chain), + transport: http(process.env[`${String(chain.id).toUpperCase()}_RPC`] || sample(chain.rpc)), + }); + }, + async () => undefined +); + +const factoryAbi = parseAbi([ + 'function allPoolsLength() view returns (uint256)', + 'function allPools(uint256) view returns (address)', + 'function router() view returns (address)', +]); + +const routerAbi = parseAbi(['function bridge() view returns (address)']); + +const bridgeAbi = parseAbi(['function layerZeroEndpoint() view returns (address)']); + +const endpointAbi = parseAbi(['function chainId() view returns (uint16)']); + +const poolAbi = parseAbi([ + 'function chainPaths(uint256) view returns (bool,uint16,uint256,uint256,uint256,uint256,uint256,uint256)', + 'function getChainPathsLength() view returns (uint256)', + 'function convertRate() view returns (uint256)', + 'function feeLibrary() view returns (address)', + 'function sharedDecimals() view returns (uint256)', + 'function symbol() view returns (string)', + 'function token() view returns (address)', + 'function poolId() view returns (uint256)', + 'function stopSwap() view returns (bool)', +]); + +function bigRange(n: bigint): bigint[] { + const result: bigint[] = []; + for (let i = BigInt(0); i < n; ++i) { + result.push(i); + } + return result; +} + +async function fetchPools(factoryAddress: Address, chain: ChainEntity) { + const client = await getPublicClient(chain); + const [allPoolsLength, routerAddress] = await Promise.all([ + client.readContract({ + address: factoryAddress, + abi: factoryAbi, + functionName: 'allPoolsLength', + args: [], + }), + client.readContract({ + address: factoryAddress, + abi: factoryAbi, + functionName: 'router', + args: [], + }), + ]); + const bridgeAddress = await client.readContract({ + address: routerAddress, + abi: routerAbi, + functionName: 'bridge', + args: [], + }); + const endpointAddress = await client.readContract({ + address: bridgeAddress, + abi: bridgeAbi, + functionName: 'layerZeroEndpoint', + args: [], + }); + const rawChainId = await client.readContract({ + address: endpointAddress, + abi: endpointAbi, + functionName: 'chainId', + args: [], + }); + const stargateChainId = rawChainId < 100 ? rawChainId + 100 : rawChainId; + + const poolAddresses = await Promise.all( + bigRange(allPoolsLength).map(async i => { + return client.readContract({ + address: factoryAddress, + abi: factoryAbi, + functionName: 'allPools', + args: [i], + }); + }) + ); + + const pools = await Promise.all( + poolAddresses.map(async poolAddress => { + const [ + chainPathsLength, + convertRate, + feeLibrary, + sharedDecimals, + symbol, + rawTokenAddress, + poolId, + stopSwap, + ] = await Promise.all([ + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'getChainPathsLength', + args: [], + }), + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'convertRate', + args: [], + }), + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'feeLibrary', + args: [], + }), + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'sharedDecimals', + args: [], + }), + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'symbol', + args: [], + }), + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'token', + args: [], + }), + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'poolId', + args: [], + }), + client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'stopSwap', + args: [], + }), + ]); + + const paths = ( + await Promise.all( + bigRange(chainPathsLength).map(async i => { + const [ready, dstChainId, dstPoolId, weight, balance, lkb, credits, idealBalance] = + await client.readContract({ + address: poolAddress, + abi: poolAbi, + functionName: 'chainPaths', + args: [i], + }); + return { + ready, + dstChainId, + dstPoolId, + weight, + balance, + lkb, + credits, + idealBalance, + }; + }) + ) + ).filter(path => path.ready && path.idealBalance > BigInt(0)); + const tokenAddress = symbol === 'S*SGETH' ? 'native' : rawTokenAddress; + const token = await getTokenByAddress(tokenAddress, chain.id); + + return { + id: `${stargateChainId}:${poolId}`, + poolId, + poolAddress, + tokenAddress, + stargateChainId, + chainId: chain.id, + symbol, + token, + convertRate, + feeLibrary, + sharedDecimals, + stopSwap, + paths, + }; + }) + ); + + return pools.filter(pool => { + if (pool.stopSwap) { + console.log(`[SKIP][${pool.chainId}] Pool ${pool.symbol}#${pool.poolId}: is disabled`); + return false; + } + if (pool.token === undefined) { + console.log( + `[SKIP][${pool.chainId}] Pool ${pool.symbol}#${pool.poolId}: token ${pool.symbol.slice( + 2 + )} ${pool.tokenAddress} not in address book` + ); + return false; + } + + return true; + }); +} + +async function start() { + const poolsPerChain = await Promise.all( + factories.map(async factory => fetchPools(factory.address, chainsByAppId[factory.chainId])) + ); + const allPools = poolsPerChain.flat(); + allPools.forEach(pool => { + pool.paths = pool.paths.filter(path => + allPools.some(p => p.poolId === path.dstPoolId && p.stargateChainId === path.dstChainId) + ); + }); + const poolsWithPaths = allPools.filter(pool => pool.paths.length > 0); + + const pools = poolsWithPaths.map(pool => ({ + id: pool.id, + chainId: pool.chainId, + symbol: pool.token!.symbol, + poolAddress: pool.poolAddress, + tokenAddress: pool.tokenAddress, + convertRate: pool.convertRate.toString(), + poolId: pool.poolId.toString(), + feeLibraryAddress: pool.feeLibrary, + })); + + const paths = poolsWithPaths.flatMap(pool => + pool.paths.map(path => ({ + source: pool.id, + dest: `${path.dstChainId}:${path.dstPoolId}`, + })) + ); + + await saveJson( + './src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-pools.json', + pools, + 'prettier' + ); + await saveJson( + './src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-paths.json', + paths, + 'prettier' + ); +} + +start().catch(e => { + console.error(e); + process.exit(1); +}); diff --git a/src/components/Stepper/components/Content/Content.tsx b/src/components/Stepper/components/Content/Content.tsx index 0b4762d60..6cbaca1e4 100644 --- a/src/components/Stepper/components/Content/Content.tsx +++ b/src/components/Stepper/components/Content/Content.tsx @@ -142,7 +142,7 @@ type SuccessContentProps = { const ZapSuccessContent = memo(function ZapSuccessContent({ step }) { const { t } = useTranslation(); const returned = useAppSelector(selectZapReturned); - + const isStargate = !!step.extraInfo?.stargate; const dust = useMemo(() => { if (returned.length) { return ( @@ -161,8 +161,16 @@ const ZapSuccessContent = memo(function ZapSuccessContent({ return ( : undefined } diff --git a/src/components/Stepper/components/Content/styles.tsx b/src/components/Stepper/components/Content/styles.tsx index 0d17ca93c..e742f1d56 100644 --- a/src/components/Stepper/components/Content/styles.tsx +++ b/src/components/Stepper/components/Content/styles.tsx @@ -6,6 +6,8 @@ export const styles = (theme: Theme) => ({ }, errorContent: { backgroundColor: 'rgba(219, 50, 50, 0.1)', + whiteSpace: 'pre-wrap', + overflowY: 'auto', }, content: { marginTop: '12px', diff --git a/src/components/Stepper/components/TransactionLink/TransactionLink.tsx b/src/components/Stepper/components/TransactionLink/TransactionLink.tsx index 3a7d58dc3..0c2a0ef46 100644 --- a/src/components/Stepper/components/TransactionLink/TransactionLink.tsx +++ b/src/components/Stepper/components/TransactionLink/TransactionLink.tsx @@ -4,8 +4,12 @@ import OpenInNewRoundedIcon from '@material-ui/icons/OpenInNewRounded'; import { selectChainById } from '../../../../features/data/selectors/chains'; import { styles } from './styles'; import { useAppSelector } from '../../../../store'; -import { selectStepperChainId } from '../../../../features/data/selectors/stepper'; +import { + selectStepperChainId, + selectStepperCurrentStepData, +} from '../../../../features/data/selectors/stepper'; import { explorerTxUrl } from '../../../../helpers/url'; +import { useMemo } from 'react'; const useStyles = makeStyles(styles); @@ -14,6 +18,7 @@ export function TransactionLink() { const { t } = useTranslation(); const chainId = useAppSelector(selectStepperChainId); + const step = useAppSelector(selectStepperCurrentStepData); const walletActionsState = useAppSelector(state => state.user.walletActions); const chain = useAppSelector(state => selectChainById(state, chainId)); @@ -24,16 +29,21 @@ export function TransactionLink() { ? walletActionsState.data.hash : ''; - if (!hash) { + const txLink = useMemo(() => { + if (hash) { + if (step.extraInfo?.stargate) { + return `https://layerzeroscan.com/tx/${hash}`; + } + return explorerTxUrl(chain, hash); + } + }, [hash, chain, step]); + + if (!hash || !txLink) { return null; } return ( - ); diff --git a/src/config/abi/BeefyStargateZapAbi.ts b/src/config/abi/BeefyStargateZapAbi.ts new file mode 100644 index 000000000..31f8501f8 --- /dev/null +++ b/src/config/abi/BeefyStargateZapAbi.ts @@ -0,0 +1,168 @@ +import type { Abi } from 'viem'; + +export const BeefyStargateZapAbi = [ + { inputs: [], name: 'FailedToSendNative', type: 'error' }, + { + inputs: [], + name: 'NotAuthorized', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'vault', type: 'address' }, + { + indexed: true, + internalType: 'address', + name: 'user', + type: 'address', + }, + ], + name: 'DepositFailed', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'vault', type: 'address' }, + { + indexed: true, + internalType: 'address', + name: 'user', + type: 'address', + }, + { indexed: false, internalType: 'uint256', name: 'shares', type: 'uint256' }, + ], + name: 'DepositSuccess', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'uint8', name: 'version', type: 'uint8' }], + name: 'Initialized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'vault', + type: 'address', + }, + { internalType: 'address', name: 'token', type: 'address' }, + { + internalType: 'address', + name: 'receiver', + type: 'address', + }, + ], + internalType: 'struct BeefyStargateZapReceiver.BridgeData', + name: '_data', + type: 'tuple', + }, + ], + name: '_depositLocal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_token', type: 'address' }], + name: 'inCaseTokensGetStuck', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '_stargate', type: 'address' }, + { + internalType: 'address', + name: '_wnative', + type: 'address', + }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '', type: 'uint16' }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { + internalType: 'bytes', + name: 'payload', + type: 'bytes', + }, + ], + name: 'sgReceive', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'stargate', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'wnative', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { stateMutability: 'payable', type: 'receive' }, +] as const satisfies Abi; diff --git a/src/config/abi/StargateComposerAbi.ts b/src/config/abi/StargateComposerAbi.ts new file mode 100644 index 000000000..57cac361a --- /dev/null +++ b/src/config/abi/StargateComposerAbi.ts @@ -0,0 +1,405 @@ +import type { Abi } from 'viem'; + +export const StargateComposerAbi = [ + { + inputs: [ + { internalType: 'address', name: '_stargateBridge', type: 'address' }, + { internalType: 'address', name: '_stargateRouter', type: 'address' }, + { internalType: 'address', name: '_stargateEthVault', type: 'address' }, + { internalType: 'uint256', name: '_wethPoolId', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint16', name: 'chainId', type: 'uint16' }, + { indexed: false, internalType: 'bytes', name: 'srcAddress', type: 'bytes' }, + { indexed: false, internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { indexed: false, internalType: 'bytes', name: 'reason', type: 'bytes' }, + ], + name: 'CachedSwapSaved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'address', name: 'token', type: 'address' }, + { indexed: false, internalType: 'address', name: 'intendedReceiver', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'amountLD', type: 'uint256' }, + ], + name: 'ComposedTokenTransferFailed', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'previousOwner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId', type: 'uint256' }, + { internalType: 'uint256', name: '_amountLD', type: 'uint256' }, + { internalType: 'address', name: '_to', type: 'address' }, + ], + name: 'addLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'addLiquidityETH', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_srcChainId', type: 'uint16' }, + { internalType: 'bytes', name: '_srcAddress', type: 'bytes' }, + { internalType: 'uint64', name: '_nonce', type: 'uint64' }, + { internalType: 'address', name: '_receiver', type: 'address' }, + { internalType: 'bytes', name: '_sgReceiveCallData', type: 'bytes' }, + ], + name: 'clearCachedSwap', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_poolId', type: 'uint256' }], + name: 'getPoolInfo', + outputs: [ + { + components: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'address', name: 'poolAddress', type: 'address' }, + { internalType: 'uint256', name: 'convertRate', type: 'uint256' }, + ], + internalType: 'struct StargateComposer.PoolInfo', + name: 'poolInfo', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_srcPoolId', type: 'uint16' }, + { internalType: 'uint256', name: '_amountLP', type: 'uint256' }, + { internalType: 'address', name: '_to', type: 'address' }, + ], + name: 'instantRedeemLocal', + outputs: [{ internalType: 'uint256', name: 'amountSD', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'isSending', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '', type: 'uint16' }, + { internalType: 'bytes', name: '', type: 'bytes' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + ], + name: 'payloadHashes', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint16', name: '', type: 'uint16' }], + name: 'peers', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'poolIdToInfo', + outputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'address', name: 'poolAddress', type: 'address' }, + { internalType: 'uint256', name: 'convertRate', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_chainId', type: 'uint16' }, + { internalType: 'uint8', name: '_functionType', type: 'uint8' }, + { internalType: 'bytes', name: '_toAddress', type: 'bytes' }, + { internalType: 'bytes', name: '_transferAndCallPayload', type: 'bytes' }, + { + components: [ + { internalType: 'uint256', name: 'dstGasForCall', type: 'uint256' }, + { internalType: 'uint256', name: 'dstNativeAmount', type: 'uint256' }, + { internalType: 'bytes', name: 'dstNativeAddr', type: 'bytes' }, + ], + internalType: 'struct IStargateRouter.lzTxObj', + name: '_lzTxParams', + type: 'tuple', + }, + ], + name: 'quoteLayerZeroFee', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '_token', type: 'address' }, + { internalType: 'address', name: '_to', type: 'address' }, + { internalType: 'uint256', name: '_amount', type: 'uint256' }, + ], + name: 'recoverToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_dstChainId', type: 'uint16' }, + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { internalType: 'uint256', name: '_dstPoolId', type: 'uint256' }, + { internalType: 'address payable', name: '_refundAddress', type: 'address' }, + { internalType: 'uint256', name: '_amountLP', type: 'uint256' }, + { internalType: 'bytes', name: '_to', type: 'bytes' }, + { + components: [ + { internalType: 'uint256', name: 'dstGasForCall', type: 'uint256' }, + { internalType: 'uint256', name: 'dstNativeAmount', type: 'uint256' }, + { internalType: 'bytes', name: 'dstNativeAddr', type: 'bytes' }, + ], + internalType: 'struct IStargateRouter.lzTxObj', + name: '_lzTxParams', + type: 'tuple', + }, + ], + name: 'redeemLocal', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_dstChainId', type: 'uint16' }, + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { internalType: 'uint256', name: '_dstPoolId', type: 'uint256' }, + { internalType: 'address payable', name: '_refundAddress', type: 'address' }, + { internalType: 'uint256', name: '_amountLP', type: 'uint256' }, + { internalType: 'uint256', name: '_minAmountLD', type: 'uint256' }, + { internalType: 'bytes', name: '_to', type: 'bytes' }, + { + components: [ + { internalType: 'uint256', name: 'dstGasForCall', type: 'uint256' }, + { internalType: 'uint256', name: 'dstNativeAmount', type: 'uint256' }, + { internalType: 'bytes', name: 'dstNativeAddr', type: 'bytes' }, + ], + internalType: 'struct IStargateRouter.lzTxObj', + name: '_lzTxParams', + type: 'tuple', + }, + ], + name: 'redeemRemote', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_dstChainId', type: 'uint16' }, + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { internalType: 'uint256', name: '_dstPoolId', type: 'uint256' }, + { internalType: 'address payable', name: '_refundAddress', type: 'address' }, + ], + name: 'sendCredits', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_dstGasReserve', type: 'uint256' }], + name: 'setDstGasReserve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_chainId', type: 'uint16' }, + { internalType: 'address', name: '_peer', type: 'address' }, + ], + name: 'setPeer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId', type: 'uint256' }, + { internalType: 'address', name: '_stargateEthVault', type: 'address' }, + ], + name: 'setStargateEthVaults', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_transferOverhead', type: 'uint256' }], + name: 'setTransferOverhead', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_wethPoolId', type: 'uint256' }], + name: 'setWethPoolId', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_srcChainId', type: 'uint16' }, + { internalType: 'bytes', name: '_srcAddress', type: 'bytes' }, + { internalType: 'uint256', name: '_nonce', type: 'uint256' }, + { internalType: 'address', name: '_token', type: 'address' }, + { internalType: 'uint256', name: '_amountLD', type: 'uint256' }, + { internalType: 'bytes', name: '_payload', type: 'bytes' }, + ], + name: 'sgReceive', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'stargateBridge', + outputs: [{ internalType: 'contract IStargateBridge', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'stargateEthVaults', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'stargateRouter', + outputs: [{ internalType: 'contract IStargateRouter', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_dstChainId', type: 'uint16' }, + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { internalType: 'uint256', name: '_dstPoolId', type: 'uint256' }, + { internalType: 'address payable', name: '_refundAddress', type: 'address' }, + { internalType: 'uint256', name: '_amountLD', type: 'uint256' }, + { internalType: 'uint256', name: '_minAmountLD', type: 'uint256' }, + { + components: [ + { internalType: 'uint256', name: 'dstGasForCall', type: 'uint256' }, + { internalType: 'uint256', name: 'dstNativeAmount', type: 'uint256' }, + { internalType: 'bytes', name: 'dstNativeAddr', type: 'bytes' }, + ], + internalType: 'struct IStargateRouter.lzTxObj', + name: '_lzTxParams', + type: 'tuple', + }, + { internalType: 'bytes', name: '_to', type: 'bytes' }, + { internalType: 'bytes', name: '_payload', type: 'bytes' }, + ], + name: 'swap', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_dstChainId', type: 'uint16' }, + { internalType: 'address payable', name: '_refundAddress', type: 'address' }, + { internalType: 'bytes', name: '_to', type: 'bytes' }, + { + components: [ + { internalType: 'uint256', name: 'amountLD', type: 'uint256' }, + { internalType: 'uint256', name: 'minAmountLD', type: 'uint256' }, + ], + internalType: 'struct StargateComposer.SwapAmount', + name: '_swapAmount', + type: 'tuple', + }, + { + components: [ + { internalType: 'uint256', name: 'dstGasForCall', type: 'uint256' }, + { internalType: 'uint256', name: 'dstNativeAmount', type: 'uint256' }, + { internalType: 'bytes', name: 'dstNativeAddr', type: 'bytes' }, + ], + internalType: 'struct IStargateRouter.lzTxObj', + name: '_lzTxParams', + type: 'tuple', + }, + { internalType: 'bytes', name: '_payload', type: 'bytes' }, + ], + name: 'swapETHAndCall', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'wethPoolId', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +] as const satisfies Abi; diff --git a/src/config/abi/StargateFeeLibraryAbi.ts b/src/config/abi/StargateFeeLibraryAbi.ts new file mode 100644 index 000000000..a8403981c --- /dev/null +++ b/src/config/abi/StargateFeeLibraryAbi.ts @@ -0,0 +1,750 @@ +import type { Abi } from 'viem'; + +export const StargateFeeLibraryAbi = [ + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { internalType: 'address', name: '_endpoint', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'uint256', name: 'poolId', type: 'uint256' }, + { + indexed: false, + internalType: 'uint256', + name: 'priceSD', + type: 'uint256', + }, + ], + name: 'PriceUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint16', + name: '_dstChainId', + type: 'uint16', + }, + { indexed: false, internalType: 'uint16', name: '_type', type: 'uint16' }, + { + indexed: false, + internalType: 'uint256', + name: '_minDstGas', + type: 'uint256', + }, + ], + name: 'SetMinDstGas', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'address', name: 'precrime', type: 'address' }], + name: 'SetPrecrime', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint16', + name: '_remoteChainId', + type: 'uint16', + }, + { indexed: false, internalType: 'bytes', name: '_path', type: 'bytes' }, + ], + name: 'SetTrustedRemote', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint16', + name: '_remoteChainId', + type: 'uint16', + }, + { indexed: false, internalType: 'bytes', name: '_remoteAddress', type: 'bytes' }, + ], + name: 'SetTrustedRemoteAddress', + type: 'event', + }, + { + inputs: [], + name: 'DELTA_1', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'DELTA_2', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'DENOMINATOR', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'EQ_REWARD_THRESHOLD', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'LAMBDA_1', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'LAMBDA_2', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'LP_FEE', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'ONE_BPS_PRICE_CHANGE_THRESHOLD', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PRICE_DEPEG_THRESHOLD', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PRICE_DRIFT_THRESHOLD', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PRICE_SHARED_DECIMALS', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PROTOCOL_FEE', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PROTOCOL_FEE_FOR_SAME_TOKEN', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PROTOCOL_SUBSIDY', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint16', name: '', type: 'uint16' }], + name: 'defaultAdapterParams', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'contract Factory', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_srcChainId', type: 'uint16' }, + { + internalType: 'bytes', + name: '_srcAddress', + type: 'bytes', + }, + ], + name: 'forceResumeReceive', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_version', type: 'uint16' }, + { + internalType: 'uint16', + name: '_chainId', + type: 'uint16', + }, + { internalType: 'address', name: '', type: 'address' }, + { + internalType: 'uint256', + name: '_configType', + type: 'uint256', + }, + ], + name: 'getConfig', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { + internalType: 'uint256', + name: '_dstPoolId', + type: 'uint256', + }, + { internalType: 'uint256', name: '_amountSD', type: 'uint256' }, + { + internalType: 'bool', + name: '_whitelisted', + type: 'bool', + }, + ], + name: 'getDriftFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { + internalType: 'uint256', + name: '_amountSD', + type: 'uint256', + }, + { internalType: 'bool', name: '_whitelisted', type: 'bool' }, + ], + name: 'getEqReward', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'srcPoolId', type: 'uint256' }, + { + internalType: 'uint256', + name: 'dstPoolId', + type: 'uint256', + }, + { internalType: 'uint16', name: 'dstChainId', type: 'uint16' }, + { + internalType: 'uint256', + name: 'amountSD', + type: 'uint256', + }, + { internalType: 'bool', name: 'whitelisted', type: 'bool' }, + { + internalType: 'bool', + name: 'hasEqReward', + type: 'bool', + }, + ], + name: 'getEquilibriumFee', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { + internalType: 'uint256', + name: '_dstPoolId', + type: 'uint256', + }, + { internalType: 'uint16', name: '_dstChainId', type: 'uint16' }, + { + internalType: 'address', + name: '_from', + type: 'address', + }, + { internalType: 'uint256', name: '_amountSD', type: 'uint256' }, + ], + name: 'getFees', + outputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { internalType: 'uint256', name: 'eqFee', type: 'uint256' }, + { + internalType: 'uint256', + name: 'eqReward', + type: 'uint256', + }, + { internalType: 'uint256', name: 'lpFee', type: 'uint256' }, + { + internalType: 'uint256', + name: 'protocolFee', + type: 'uint256', + }, + { internalType: 'uint256', name: 'lkbRemove', type: 'uint256' }, + ], + internalType: 'struct Pool.SwapObj', + name: 's', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_srcPoolId', type: 'uint256' }, + { + internalType: 'uint256', + name: '_dstPoolId', + type: 'uint256', + }, + { internalType: 'uint16', name: '', type: 'uint16' }, + { + internalType: 'uint256', + name: '_amountSD', + type: 'uint256', + }, + { internalType: 'uint256', name: '_protocolSubsidy', type: 'uint256' }, + { + internalType: 'bool', + name: '_whitelisted', + type: 'bool', + }, + ], + name: 'getProtocolAndLpFee', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId1', type: 'uint256' }, + { + internalType: 'uint256', + name: '_poolId2', + type: 'uint256', + }, + ], + name: 'getRemoteChains', + outputs: [{ internalType: 'uint16[]', name: '', type: 'uint16[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId', type: 'uint256' }, + { + internalType: 'uint256', + name: '_priceSD', + type: 'uint256', + }, + ], + name: 'initTokenPrice', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_srcChainId', type: 'uint16' }, + { + internalType: 'bytes', + name: '_srcAddress', + type: 'bytes', + }, + ], + name: 'isTrustedRemote', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'lzEndpoint', + outputs: [{ internalType: 'contract ILayerZeroEndpoint', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_srcChainId', type: 'uint16' }, + { + internalType: 'bytes', + name: '_srcAddress', + type: 'bytes', + }, + { internalType: 'uint64', name: '_nonce', type: 'uint64' }, + { + internalType: 'bytes', + name: '_payload', + type: 'bytes', + }, + ], + name: 'lzReceive', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '', type: 'uint16' }, + { + internalType: 'uint16', + name: '', + type: 'uint16', + }, + ], + name: 'minDstGasLookup', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'poolIdToLpId', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'poolIdToPriceFeed', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'poolIdToPriceSD', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'precrime', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId1', type: 'uint256' }, + { + internalType: 'uint256', + name: '_poolId2', + type: 'uint256', + }, + ], + name: 'quoteFeeForPriceUpdate', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_version', type: 'uint16' }, + { + internalType: 'uint16', + name: '_chainId', + type: 'uint16', + }, + { internalType: 'uint256', name: '_configType', type: 'uint256' }, + { + internalType: 'bytes', + name: '_config', + type: 'bytes', + }, + ], + name: 'setConfig', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_remoteChainId', type: 'uint16' }, + { + internalType: 'bytes', + name: '_adapterParams', + type: 'bytes', + }, + ], + name: 'setDefaultAdapterParams', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_dstChainId', type: 'uint16' }, + { + internalType: 'uint16', + name: '_packetType', + type: 'uint16', + }, + { internalType: 'uint256', name: '_minGas', type: 'uint256' }, + ], + name: 'setMinDstGas', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId', type: 'uint256' }, + { + internalType: 'uint256', + name: '_lpId', + type: 'uint256', + }, + ], + name: 'setPoolToLpId', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_precrime', type: 'address' }], + name: 'setPrecrime', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint16', name: '_version', type: 'uint16' }], + name: 'setReceiveVersion', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId1', type: 'uint256' }, + { + internalType: 'uint256', + name: '_poolId2', + type: 'uint256', + }, + { internalType: 'uint16[]', name: '_remoteChainIds', type: 'uint16[]' }, + ], + name: 'setRemoteChains', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint16', name: '_version', type: 'uint16' }], + name: 'setSendVersion', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId', type: 'uint256' }, + { + internalType: 'address', + name: '_lpStaking', + type: 'address', + }, + ], + name: 'setStargatePoolIdToLPStakingAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId', type: 'uint256' }, + { + internalType: 'address', + name: '_priceFeedAddress', + type: 'address', + }, + ], + name: 'setTokenPriceFeed', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: '_srcChainId', type: 'uint16' }, + { + internalType: 'bytes', + name: '_path', + type: 'bytes', + }, + ], + name: 'setTrustedRemote', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId1', type: 'uint256' }, + { + internalType: 'uint256', + name: '_poolId2', + type: 'uint256', + }, + ], + name: 'shouldCallUpdateTokenPrices', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'stargatePoolIdToLPStaking', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint16', name: '', type: 'uint16' }], + name: 'trustedRemoteLookup', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_poolId1', type: 'uint256' }, + { + internalType: 'uint256', + name: '_poolId2', + type: 'uint256', + }, + ], + name: 'updateTokenPrices', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '_from', type: 'address' }, + { + internalType: 'bool', + name: '_whiteListed', + type: 'bool', + }, + ], + name: 'whiteList', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'whitelist', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { stateMutability: 'payable', type: 'receive' }, +] as const satisfies Abi; diff --git a/src/config/vault/arbitrum.json b/src/config/vault/arbitrum.json index bcac49001..5c8227f24 100644 --- a/src/config/vault/arbitrum.json +++ b/src/config/vault/arbitrum.json @@ -341,6 +341,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -377,6 +380,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -687,6 +693,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -761,6 +770,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -797,6 +809,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -2059,6 +2074,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -2975,6 +2993,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { diff --git a/src/config/vault/optimism.json b/src/config/vault/optimism.json index 518a65721..046662c69 100644 --- a/src/config/vault/optimism.json +++ b/src/config/vault/optimism.json @@ -148,6 +148,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -1356,6 +1359,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -1385,6 +1391,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -1506,6 +1515,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -1535,6 +1547,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -1564,6 +1579,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -1593,6 +1611,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { @@ -1622,6 +1643,9 @@ "zaps": [ { "strategyId": "single" + }, + { + "strategyId": "stargate-crosschain-single" } ], "lendingOracle": { diff --git a/src/features/data/actions/transact.ts b/src/features/data/actions/transact.ts index 175c27e45..daba2dd60 100644 --- a/src/features/data/actions/transact.ts +++ b/src/features/data/actions/transact.ts @@ -66,7 +66,10 @@ export type TransactInitArgs = { vaultId: VaultEntity['id']; }; -export type TransactInitPayload = void; +export type TransactInitPayload = { + vaultId: VaultEntity['id']; + chainId: ChainEntity['id']; +}; export const transactInit = createAsyncThunk< TransactInitPayload, @@ -102,7 +105,11 @@ export const transactInit = createAsyncThunk< loaders.push(dispatch(fetchFees())); } - await Promise.all(loaders); + if (loaders.length) { + await Promise.all(loaders); + } + + return { vaultId, chainId: vault.chainId }; }, { condition({ vaultId }, { getState }) { @@ -338,7 +345,7 @@ export const transactFetchQuotesIfNeeded = createAsyncThunk) => const zapExecuteOrder = ( vaultId: VaultEntity['id'], params: UserlessZapRequest, - expectedTokens: TokenEntity[] + expectedTokens: TokenEntity[], + sourceChainId?: ChainEntity['id'] | undefined ) => { return captureWalletErrors(async (dispatch, getState) => { txStart(dispatch); @@ -1103,10 +1112,10 @@ const zapExecuteOrder = ( } const vault = selectVaultById(state, vaultId); - const chain = selectChainById(state, vault.chainId); - const zap = selectZapByChainId(state, vault.chainId); - if (!zap) { - throw new Error(`No zap found for chain ${chain.id}`); + const sourceChain = selectChainById(state, sourceChainId || vault.chainId); + const sourceZap = selectZapByChainIdOrUndefined(state, sourceChain.id); + if (!sourceZap) { + throw new Error(`No zap found for chain ${sourceChain.id}`); } const depositToken = selectTokenByAddress(state, vault.chainId, vault.depositTokenAddress); @@ -1119,10 +1128,10 @@ const zapExecuteOrder = ( const walletApi = await getWalletConnectionApi(); const web3 = await walletApi.getConnectedWeb3Instance(); - const gasPrices = await getGasPriceOptions(chain); + const gasPrices = await getGasPriceOptions(sourceChain); const nativeInput = order.inputs.find(input => input.token === ZERO_ADDRESS); - const contract = new web3.eth.Contract(viemToWeb3Abi(BeefyZapRouterAbi), zap.router); + const contract = new web3.eth.Contract(viemToWeb3Abi(BeefyZapRouterAbi), sourceZap.router); const options = { ...gasPrices, value: nativeInput ? nativeInput.amount : '0', @@ -1144,8 +1153,8 @@ const zapExecuteOrder = ( }, { walletAddress: address, - chainId: vault.chainId, - spenderAddress: zap.manager, + chainId: sourceChain.id, + spenderAddress: sourceZap.manager, tokens: selectZapTokensToRefresh(state, vault, order), clearInput: true, } @@ -1153,6 +1162,104 @@ const zapExecuteOrder = ( }); }; +const stargateBridgeZap = ( + vaultId: VaultEntity['id'], + from: TokenAmount, + bridgeCost: TokenAmount, + fromStargateConfig: StargateConfig, + toStargateConfig: StargateConfig, + fromPoolId: string, + toPoolId: string +) => { + return captureWalletErrors(async (dispatch, getState) => { + txStart(dispatch); + const state = getState(); + const address = selectWalletAddress(state); + if (!address) { + return; + } + + const vault = selectVaultById(state, vaultId); + const chain = selectChainById(state, vault.chainId); + const slippage = selectTransactSlippage(state); + const walletApi = await getWalletConnectionApi(); + const web3 = await walletApi.getConnectedWeb3Instance(); + const gasPrices = await getGasPriceOptions(chain); + const contract = new web3.eth.Contract( + viemToWeb3Abi(StargateComposerAbi), + fromStargateConfig.composerAddress + ); + const value = toWeiString( + isTokenEqual(from.token, bridgeCost.token) + ? bridgeCost.amount.plus(from.amount) + : bridgeCost.amount, + bridgeCost.token.decimals + ); + const options = { + ...gasPrices, + value, + from: address, + }; + + const lzTxObj = [ + toStargateConfig.depositGasLimit, // dstGasForCall TODO: change when withdrawing + '0', // dstNativeAmount + '0x', // dstNativeAddr (bytes) + ]; + + const payload = abiCoder.encodeParameters( + ['uint8', 'bytes'], + [ + 0, // 0 for deposit, 1 for withdraw TODO: change when withdrawing + abiCoder.encodeParameters( + ['address', 'address', 'address'], + [ + vault.earnContractAddress, // vault address + vault.depositTokenAddress === 'native' ? ZERO_ADDRESS : vault.depositTokenAddress, // deposit token address + address, + ] + ), + ] + ); + + const minAmount = slipBy(from.amount, slippage, from.token.decimals); + + txWallet(dispatch); + const transaction = contract.methods + .swap( + toStargateConfig.chainId, // _dstChainId + fromPoolId, // _srcPoolId + toPoolId, // _dstPoolId + address, // _refundAddress + toWeiString(from.amount, from.token.decimals), // _amountLD + toWeiString(minAmount, from.token.decimals), // _minAmountLD + lzTxObj, // _lzTxParams + toStargateConfig.zapReceiverAddress, // _to + payload // _payload + ) + .send(options); + + bindTransactionEvents( + dispatch, + transaction, + { + type: 'zap', + amount: from.amount, + token: from.token, + expectedTokens: [], + vaultId: vault.id, + }, + { + walletAddress: address, + chainId: vault.chainId, + spenderAddress: fromStargateConfig.composerAddress, + tokens: selectVaultTokensToRefresh(state, vault).concat(from.token), + clearInput: true, + } + ); + }); +}; + const claimMerkl = (chainId: ChainEntity['id']) => { return captureWalletErrors(async (dispatch, getState) => { txStart(dispatch); @@ -1253,6 +1360,7 @@ export const walletActions = { migrateUnstake, bridgeViaCommonInterface, claimMerkl, + stargateBridgeZap, }; export function captureWalletErrors( diff --git a/src/features/data/apis/transact/strategies/IStrategy.ts b/src/features/data/apis/transact/strategies/IStrategy.ts index 63d3a40fe..4dd72c316 100644 --- a/src/features/data/apis/transact/strategies/IStrategy.ts +++ b/src/features/data/apis/transact/strategies/IStrategy.ts @@ -36,6 +36,10 @@ export type SingleStrategyOptions = { strategyId: 'single'; } & OptionalStrategySwapOption; +export type StargateCrossChainSingleStrategyOptions = { + strategyId: 'stargate-crosschain-single'; +} & OptionalStrategySwapOption; + export type UniswapLikeStrategyOptions = { strategyId: TAmm['type']; ammId: TAmm['id']; @@ -66,6 +70,7 @@ export type ConicStrategyOptions = { export type StrategyOptions = | SingleStrategyOptions + | StargateCrossChainSingleStrategyOptions | UniswapV2StrategyOptions | SolidlyStrategyOptions | CurveStrategyOptions diff --git a/src/features/data/apis/transact/strategies/index.ts b/src/features/data/apis/transact/strategies/index.ts index 8d8755eb4..ef880e5db 100644 --- a/src/features/data/apis/transact/strategies/index.ts +++ b/src/features/data/apis/transact/strategies/index.ts @@ -20,6 +20,11 @@ function makeLazyLoader(loader: () => Promise) export const strategyBuildersById = { single: makeLazyLoader(async () => (await import('./single/SingleStrategy')).SingleStrategy), + 'stargate-crosschain-single': makeLazyLoader( + async () => + (await import('./stargate-crosschain-single/StargateCrossChainSingleStrategy')) + .StargateCrossChainSingleStrategy + ), 'uniswap-v2': makeLazyLoader( async () => (await import('./uniswap-v2/UniswapV2Strategy')).UniswapV2Strategy ), diff --git a/src/features/data/apis/transact/strategies/stargate-crosschain-single/StargateCrossChainSingleStrategy.ts b/src/features/data/apis/transact/strategies/stargate-crosschain-single/StargateCrossChainSingleStrategy.ts new file mode 100644 index 000000000..083948e12 --- /dev/null +++ b/src/features/data/apis/transact/strategies/stargate-crosschain-single/StargateCrossChainSingleStrategy.ts @@ -0,0 +1,1099 @@ +import type { IStrategy, SingleStrategyOptions, ZapTransactHelpers } from '../IStrategy'; +import { + type InputTokenAmount, + isZapQuoteStepBridge, + isZapQuoteStepSwap, + isZapQuoteStepSwapAggregator, + isZapQuoteStepWithdraw, + type StargateCrossChainSingleDepositOption, + type StargateCrossChainSingleDepositQuote, + type StargateCrossChainSingleWithdrawOption, + type StargateCrossChainSingleWithdrawQuote, + type TokenAmount, + type ZapQuoteStep, + type ZapQuoteStepBridge, + type ZapQuoteStepSwap, + type ZapQuoteStepSwapAggregator, +} from '../../transact-types'; +import type { BeefyState, BeefyThunk } from '../../../../../../redux-types'; +import { + isTokenEqual, + isTokenErc20, + type TokenEntity, + type TokenErc20, + type TokenNative, +} from '../../../../entities/token'; +import { TransactMode } from '../../../../reducers/wallet/transact-types'; +import { + createOptionId, + createQuoteId, + createSelectionId, + onlyAssetCount, + onlyOneInput, + onlyOneToken, + onlyVaultStandard, +} from '../../helpers/options'; +import { first, groupBy, uniqBy } from 'lodash-es'; +import { + BIG_ZERO, + fromWei, + fromWeiString, + toWei, + toWeiString, +} from '../../../../../../helpers/big-number'; +import { calculatePriceImpact, ZERO_FEE } from '../../helpers/quotes'; +import { selectTransactSlippage } from '../../../../selectors/transact'; +import { walletActions } from '../../../../actions/wallet-actions'; +import type { + OrderInput, + OrderOutput, + UserlessZapRequest, + ZapStep, + ZapStepResponse, +} from '../../zap/types'; +import { getInsertIndex, getTokenAddress, NO_RELAY } from '../../helpers/zap'; +import type { Step } from '../../../../reducers/wallet/stepper'; +import type { Namespace, TFunction } from 'react-i18next'; +import { getVaultWithdrawnFromState } from '../../helpers/vault'; +import { isStandardVault } from '../../../../entities/vault'; +import { mergeTokenAmounts, slipBy } from '../../helpers/amounts'; +import { uniqueTokens, wnativeToNative } from '../../helpers/tokens'; +import { + selectChainNativeToken, + selectChainWrappedNativeToken, + selectTokenByAddress, + selectTokenByAddressOrUndefined, +} from '../../../../selectors/tokens'; +import { fetchZapAggregatorSwap } from '../../zap/swap'; +import type { ChainEntity } from '../../../../entities/chain'; +import { selectChainById } from '../../../../selectors/chains'; +import { stargateConfigs, stargatePaths, stargatePools } from './config'; +import { isDefined } from '../../../../utils/array-utils'; +import { + type LibraryGetFeesResult, + LibraryGetFeesResultKey, + type StargatePath, + StargateZapType, +} from './types'; +import BigNumber from 'bignumber.js'; +import { getWeb3Instance } from '../../../instances'; +import { viemToWeb3Abi } from '../../../../../../helpers/web3'; +import { StargateFeeLibraryAbi } from '../../../../../../config/abi/StargateFeeLibraryAbi'; +import { ZERO_ADDRESS } from '../../../../../../helpers/addresses'; +import abiCoder from 'web3-eth-abi'; +import { StargateComposerAbi } from '../../../../../../config/abi/StargateComposerAbi'; +import { entries } from '../../../../../../helpers/object'; +import type { QuoteRequest } from '../../swap/ISwapProvider'; +import { Balances } from '../../helpers/Balances'; +import { selectWalletAddress } from '../../../../selectors/wallet'; +import { selectZapByChainId, selectZapByChainIdOrUndefined } from '../../../../selectors/zap'; + +type ZapHelpers = { + slippage: number; + state: BeefyState; + direction: StargateZapType; + path: StargatePath; +}; + +export class StargateCrossChainSingleStrategy implements IStrategy { + public readonly id = 'stargate-crosschain-single'; + protected readonly wnative: TokenErc20; + protected readonly native: TokenNative; + protected readonly depositToken: TokenEntity; + protected readonly paths: StargatePath[]; + + constructor(protected options: SingleStrategyOptions, protected helpers: ZapTransactHelpers) { + // Make sure zap was configured correctly for this vault + const { vault, getState } = this.helpers; + + if (!onlyVaultStandard(vault)) { + return; // type check + } + onlyAssetCount(vault, 1); + + // configure + const state = getState(); + this.native = selectChainNativeToken(state, vault.chainId); + this.wnative = selectChainWrappedNativeToken(state, vault.chainId); + this.depositToken = selectTokenByAddress(state, vault.chainId, vault.depositTokenAddress); + this.paths = this.selectPaths(state); + } + + async fetchDepositOptions(): Promise { + const { vault, vaultType } = this.helpers; + const tokens = await this.fetchDepositTokens(); + const outputs = [vaultType.depositToken]; + + return tokens.map(token => { + const inputs = [token]; + const selectionId = createSelectionId(token.chainId, inputs); + + return { + id: createOptionId('stargate-crosschain-single', vault.id, selectionId), + vaultId: vault.id, + chainId: token.chainId, + selectionId, + selectionOrder: 3, + inputs, + wantedOutputs: outputs, + strategyId: 'stargate-crosschain-single', + mode: TransactMode.Deposit, + }; + }); + } + + async fetchDepositQuote( + inputs: InputTokenAmount[], + option: StargateCrossChainSingleDepositOption + ): Promise { + const { vault, swapAggregator, vaultType, getState } = this.helpers; + + // Input + const input = onlyOneInput(inputs); + if (input.amount.lte(BIG_ZERO)) { + throw new Error('Quote called with 0 input amount'); + } + + // Token Allowances + const state = getState(); + const sourceZap = selectZapByChainId(state, input.token.chainId); + const allowances = isTokenErc20(input.token) + ? [ + { + token: input.token, + amount: input.amount, + spenderAddress: sourceZap.manager, + }, + ] + : []; + + // Swap + Output + const depositToken = vaultType.depositToken; + const stargateDepositToken = this.toNative(state, depositToken); + const paths = this.paths.filter( + p => + p.canDeposit && + p.source.chainId === input.token.chainId && + p.dest.chainId === stargateDepositToken.chainId && + p.dest.tokenAddress === stargateDepositToken.address + ); + if (!paths) { + throw new Error( + `No paths from ${input.token.chainId} to ${stargateDepositToken.symbol} on ${stargateDepositToken.chainId}` + ); + } + + // Swap quotes + const quoteRequestsPerPath: (QuoteRequest | undefined)[] = paths.map(path => + isTokenEqual(path.source.token, input.token) + ? undefined + : { + vaultId: vault.id, + fromToken: input.token, + fromAmount: input.amount, + toToken: path.source.token, + } + ); + + const quotesPerPath = await Promise.all( + quoteRequestsPerPath.map(async quoteRequest => { + if (!quoteRequest) { + return undefined; + } + + return await swapAggregator.fetchQuotes(quoteRequest, state); + }) + ); + + const quotePerPath = quotesPerPath.map((quotes, i) => { + if (quotes === undefined) { + const quoteRequest = quoteRequestsPerPath[i]; + if (quoteRequest === undefined) { + return undefined; + } else { + throw new Error( + `No quotes found for ${quoteRequest.fromToken.symbol} -> ${quoteRequest.toToken.symbol}` + ); + } + } + + // fetchQuotes is already sorted by toAmount + return first(quotes); + }); + + const swapOutputPerPath = quotePerPath.map(quote => { + if (quote === undefined) { + return { token: input.token, amount: input.amount }; + } + return { token: quote.toToken, amount: quote.toAmount }; + }); + + const bridgeOutputPerPath = await Promise.all( + swapOutputPerPath.map(async (output, i) => ({ + amount: await this.applyFees(paths[i], output.amount), + token: paths[i].dest.token, + })) + ); + + const possible = bridgeOutputPerPath + .map((amount, i) => ({ + input: input, + output: bridgeOutputPerPath[i], + swap: quotePerPath[i] + ? { input, output: swapOutputPerPath[i], quote: quotePerPath[i]! } + : undefined, + bridge: { input: swapOutputPerPath[i], output: bridgeOutputPerPath[i] }, + path: paths[i], + })) + .sort((a, b) => b.output.amount.comparedTo(a.output.amount)); + + const best = possible[0]; + + const steps: ZapQuoteStep[] = []; + + // Optional swap + if (best.swap) { + steps.push({ + type: 'swap', + fromToken: best.swap.quote.fromToken, + fromAmount: best.swap.quote.fromAmount, + toToken: best.swap.quote.toToken, + toAmount: best.swap.quote.toAmount, + via: 'aggregator', + providerId: best.swap.quote.providerId, + fee: best.swap.quote.fee, + quote: best.swap.quote, + }); + } + + // Bridge + const bridgeCost = await this.fetchBridgeCost( + best.bridge.input.token, + best.bridge.output.token, + StargateZapType.Deposit + ); + steps.push({ + type: 'bridge', + from: best.bridge.input, + to: best.bridge.output, + providerId: 'stargate', + fee: bridgeCost, + }); + + // Deposit + steps.push({ + type: 'deposit', + token: best.bridge.output.token, + amount: best.bridge.output.amount, + }); + + const inputsWithCost = [input, { ...bridgeCost, max: false }]; + const outputs = [best.bridge.output]; + + return { + id: createQuoteId(option.id), + strategyId: 'stargate-crosschain-single', + priceImpact: calculatePriceImpact(inputsWithCost, outputs, [], state), + option, + inputs, + outputs, + returned: [], + allowances, + steps, + fee: ZERO_FEE, // beefy fee + path: best.path, + }; + } + + async fetchDepositStep( + quote: StargateCrossChainSingleDepositQuote, + t: TFunction + ): Promise { + const bridgeQuote = quote.steps.find(isZapQuoteStepBridge); + const swapQuote = quote.steps.find(isZapQuoteStepSwap); + if (!bridgeQuote) { + throw new Error('Missing bridge step from quote'); + } + + const zapAction: BeefyThunk = async (dispatch, getState, extraArgument) => { + const state = getState(); + const slippage = selectTransactSlippage(state); + const minBalances = new Balances(quote.inputs); + const direction: StargateZapType = StargateZapType.Deposit; + const zapHelpers: ZapHelpers = { + slippage, + state, + path: quote.path, + direction, + }; + const steps: ZapStep[] = []; + + // Swaps + if (swapQuote) { + const swapZap = await this.fetchZapSwap(swapQuote, zapHelpers, false); + // add step to order + swapZap.zaps.forEach(zap => steps.push(zap)); + // track minimum balances for use in further steps + minBalances.subtractMany(swapZap.inputs); + minBalances.addMany(swapZap.minOutputs); + } + + // Bridge + const bridgeCost = await this.fetchBridgeCost( + bridgeQuote.from.token, + bridgeQuote.to.token, + direction + ); + const bridgeZap = await this.fetchZapBridge( + bridgeQuote, + minBalances.get(bridgeQuote.from.token), + zapHelpers, + bridgeCost, + StargateZapType.Deposit + ); + bridgeZap.zaps.forEach(zap => steps.push(zap)); + + // Build order + const inputs: OrderInput[] = mergeTokenAmounts([bridgeCost], quote.inputs).map(input => ({ + token: getTokenAddress(input.token), + amount: toWeiString(input.amount, input.token.decimals), + })); + + // Nothing is a required output on this side of the bridge + const requiredOutputs: OrderOutput[] = []; + + // Return any unused tokens + // Swap input is covered by quote input, swap output is covered by bridge input + const dustOutputs: OrderOutput[] = [ + ...quote.inputs.map(i => ({ token: getTokenAddress(i.token), minOutputAmount: '0' })), + { + token: getTokenAddress(bridgeQuote.from.token), + minOutputAmount: '0', + }, + { + token: getTokenAddress(bridgeCost.token), + minOutputAmount: '0', + }, + ]; + + // @dev uniqBy: first occurrence of each element is kept. + const outputs = uniqBy(requiredOutputs.concat(dustOutputs), output => output.token); + + // Perform TX + const zapRequest: UserlessZapRequest = { + order: { + inputs, + outputs, + relay: NO_RELAY, + }, + steps, + }; + + const walletAction = walletActions.zapExecuteOrder( + quote.option.vaultId, + zapRequest, + [], + quote.path.source.chainId + ); + + return walletAction(dispatch, getState, extraArgument); + }; + + return { + step: 'zap-in', + message: t('Vault-TxnConfirm', { type: t('Deposit-noun') }), + action: zapAction, + pending: false, + extraInfo: { + zap: true, + vaultId: quote.option.vaultId, + stargate: { from: bridgeQuote.from.token, to: bridgeQuote.to.token }, + }, + }; + } + + async fetchWithdrawOptions(): Promise { + const { vault, vaultType } = this.helpers; + const tokens = await this.fetchWithdrawTokens(); + const inputs = [vaultType.depositToken]; + + return tokens.map(token => { + const outputs = [token]; + const selectionId = createSelectionId(token.chainId, outputs); + + return { + id: createOptionId('stargate-crosschain-single', vault.id, selectionId), + vaultId: vault.id, + chainId: vault.chainId, + selectionId, + selectionChainId: token.chainId, + selectionOrder: 3, + inputs, + wantedOutputs: outputs, + strategyId: 'stargate-crosschain-single', + mode: TransactMode.Withdraw, + }; + }); + } + + async fetchWithdrawQuote( + inputs: InputTokenAmount[], + option: StargateCrossChainSingleWithdrawOption + ): Promise { + const { vault, swapAggregator, getState } = this.helpers; + if (!isStandardVault(vault)) { + throw new Error('Vault is not standard'); + } + + // Input + const input = onlyOneInput(inputs); + if (input.amount.lte(BIG_ZERO)) { + throw new Error('Quote called with 0 input amount'); + } + + // Output + const wantedToken = onlyOneToken(option.wantedOutputs); + const paths = this.paths.filter( + p => + p.canWithdraw && + p.source.chainId === input.token.chainId && + p.dest.chainId === wantedToken.chainId && + p.dest.tokenAddress === wantedToken.address + ); + if (!paths) { + throw new Error( + `No paths from ${input.token.chainId} to ${wantedToken.symbol} on ${wantedToken.chainId}` + ); + } + + // Token Allowances + const state = getState(); + const sourceZap = selectZapByChainId(state, input.token.chainId); + const { withdrawnAmountAfterFeeWei, withdrawnToken, shareToken, sharesToWithdrawWei } = + getVaultWithdrawnFromState(input, vault, state); + const withdrawnAmountAfterFee = fromWei(withdrawnAmountAfterFeeWei, withdrawnToken.decimals); + const allowances = [ + { + token: shareToken, + amount: fromWei(sharesToWithdrawWei, shareToken.decimals), + spenderAddress: sourceZap.manager, + }, + ]; + + // Step 1. Withdraw from vault + const steps: ZapQuoteStep[] = [ + { + type: 'withdraw', + token: withdrawnToken, + amount: withdrawnAmountAfterFee, + }, + ]; + + // Step 2. [Swap +] Bridge + const quoteRequestsPerPath: (QuoteRequest | undefined)[] = paths.map(path => + isTokenEqual(path.source.token, input.token) + ? undefined + : { + vaultId: vault.id, + fromToken: input.token, + fromAmount: input.amount, + toToken: path.source.token, + } + ); + + const quotesPerPath = await Promise.all( + quoteRequestsPerPath.map(async quoteRequest => { + if (!quoteRequest) { + return undefined; + } + + return await swapAggregator.fetchQuotes(quoteRequest, state); + }) + ); + + const quotePerPath = quotesPerPath.map((quotes, i) => { + if (quotes === undefined) { + const quoteRequest = quoteRequestsPerPath[i]; + if (quoteRequest === undefined) { + return undefined; + } else { + throw new Error( + `No quotes found for ${quoteRequest.fromToken.symbol} -> ${quoteRequest.toToken.symbol}` + ); + } + } + + // fetchQuotes is already sorted by toAmount + return first(quotes); + }); + + const swapOutputPerPath = quotePerPath.map(quote => { + if (quote === undefined) { + return { token: input.token, amount: input.amount }; + } + return { token: quote.toToken, amount: quote.toAmount }; + }); + + const bridgeOutputPerPath = await Promise.all( + swapOutputPerPath.map(async (output, i) => ({ + amount: await this.applyFees(paths[i], output.amount), + token: paths[i].dest.token, + })) + ); + + const possible = bridgeOutputPerPath + .map((amount, i) => ({ + input: input, + output: bridgeOutputPerPath[i], + swap: quotePerPath[i] + ? { input, output: swapOutputPerPath[i], quote: quotePerPath[i]! } + : undefined, + bridge: { input: swapOutputPerPath[i], output: bridgeOutputPerPath[i] }, + path: paths[i], + })) + .sort((a, b) => b.output.amount.comparedTo(a.output.amount)); + + const best = possible[0]; + + // Optional swap + if (best.swap) { + steps.push({ + type: 'swap', + fromToken: best.swap.quote.fromToken, + fromAmount: best.swap.quote.fromAmount, + toToken: best.swap.quote.toToken, + toAmount: best.swap.quote.toAmount, + via: 'aggregator', + providerId: best.swap.quote.providerId, + fee: best.swap.quote.fee, + quote: best.swap.quote, + }); + } + + // Bridge + const bridgeCost = await this.fetchBridgeCost( + best.bridge.input.token, + best.bridge.output.token, + StargateZapType.Withdraw + ); + steps.push({ + type: 'bridge', + from: best.bridge.input, + to: best.bridge.output, + providerId: 'stargate', + fee: bridgeCost, + }); + + const inputsWithCost = [input, { ...bridgeCost, max: false }]; + const outputs = [best.bridge.output]; + + return { + id: createQuoteId(option.id), + strategyId: 'stargate-crosschain-single', + priceImpact: calculatePriceImpact(inputsWithCost, outputs, [], state), + option, + inputs, + outputs, + returned: [], + allowances, + steps, + fee: ZERO_FEE, // beefy fee + path: best.path, + }; + } + + async fetchWithdrawStep( + quote: StargateCrossChainSingleWithdrawQuote, + t: TFunction + ): Promise { + const { vaultType } = this.helpers; + const bridgeQuote = quote.steps.find(isZapQuoteStepBridge); + const swapQuote = quote.steps.find(isZapQuoteStepSwap); + const withdrawQuote = quote.steps.find(isZapQuoteStepWithdraw); + if (!withdrawQuote) { + throw new Error('Missing withdraw step from quote'); + } + if (!bridgeQuote) { + throw new Error('Missing bridge step from quote'); + } + + const zapAction: BeefyThunk = async (dispatch, getState, extraArgument) => { + const state = getState(); + const slippage = selectTransactSlippage(state); + const minBalances = new Balances(quote.inputs); + const direction: StargateZapType = StargateZapType.Withdraw; + const zapHelpers: ZapHelpers = { + slippage, + state, + path: quote.path, + direction, + }; + const steps: ZapStep[] = []; + + // Step 1. Withdraw from vault + const vaultWithdraw = await vaultType.fetchZapWithdraw({ + inputs: quote.inputs, + }); + const withdrawOutput = first(vaultWithdraw.outputs); + if (!withdrawOutput || vaultWithdraw.outputs.length !== 1) { + throw new Error('Withdraw output count mismatch'); + } + if (withdrawOutput.amount.lt(withdrawQuote.toAmount)) { + throw new Error('Withdraw output amount mismatch'); + } + steps.push(vaultWithdraw.zap); + + // Step 2. Optional Swap + if (swapQuote) { + const swapZap = await this.fetchZapSwap(swapQuote, zapHelpers, false); + // add step to order + swapZap.zaps.forEach(zap => steps.push(zap)); + // track minimum balances for use in further steps + minBalances.subtractMany(swapZap.inputs); + minBalances.addMany(swapZap.minOutputs); + } + + // Step 3. Bridge + const bridgeCost = await this.fetchBridgeCost( + bridgeQuote.from.token, + bridgeQuote.to.token, + direction + ); + const bridgeZap = await this.fetchZapBridge( + bridgeQuote, + minBalances.get(bridgeQuote.from.token), + zapHelpers, + bridgeCost, + StargateZapType.Withdraw + ); + bridgeZap.zaps.forEach(zap => steps.push(zap)); + + // Build order (note: input to order is shares, but quote inputs are the deposit token) + const inputs: OrderInput[] = mergeTokenAmounts([bridgeCost], vaultWithdraw.inputs).map( + input => ({ + token: getTokenAddress(input.token), + amount: toWeiString(input.amount, input.token.decimals), + }) + ); + + // Nothing is a required output on this side of the bridge + const requiredOutputs: OrderOutput[] = []; + + // Return any unused tokens + // Swap input is covered by quote input, swap output is covered by bridge input + const dustOutputs: OrderOutput[] = [ + ...quote.inputs.map(i => ({ token: getTokenAddress(i.token), minOutputAmount: '0' })), + { + token: getTokenAddress(bridgeQuote.from.token), + minOutputAmount: '0', + }, + { + token: getTokenAddress(bridgeCost.token), + minOutputAmount: '0', + }, + ]; + + // @dev uniqBy: first occurrence of each element is kept. + const outputs = uniqBy(requiredOutputs.concat(dustOutputs), output => output.token); + + // Perform TX + const zapRequest: UserlessZapRequest = { + order: { + inputs, + outputs, + relay: NO_RELAY, + }, + steps, + }; + + const walletAction = walletActions.zapExecuteOrder( + quote.option.vaultId, + zapRequest, + [], + quote.path.source.chainId + ); + + return walletAction(dispatch, getState, extraArgument); + }; + + return { + step: 'zap-out', + message: t('Vault-TxnConfirm', { type: t('Withdraw-noun') }), + action: zapAction, + pending: false, + extraInfo: { + zap: true, + vaultId: quote.option.vaultId, + stargate: { from: bridgeQuote.from.token, to: bridgeQuote.to.token }, + }, + }; + } + + protected selectPaths(state: BeefyState): StargatePath[] { + return stargatePaths + .map(path => { + const source = stargatePools.get(path.source); + if (!source) { + return undefined; + } + + const dest = stargatePools.get(path.dest); + if (!dest) { + return undefined; + } + + const sourceToken = selectTokenByAddressOrUndefined( + state, + source.chainId, + source.tokenAddress + ); + if (!sourceToken) { + return undefined; + } + + const destToken = selectTokenByAddressOrUndefined(state, dest.chainId, dest.tokenAddress); + if (!destToken) { + return undefined; + } + + return { + canDeposit: + this.canSendZapFromChain(state, source.chainId) && + this.canReceiveZapDepositToChain(dest.chainId), + canWithdraw: + this.canSendZapFromChain(state, source.chainId) && + this.canReceiveZapWithdrawToChain(dest.chainId), + source: { + ...source, + token: sourceToken, + zap: selectZapByChainId(state, source.chainId), + }, + dest: { + ...dest, + token: destToken, + zap: selectZapByChainId(state, dest.chainId), + }, + }; + }) + .filter(isDefined); + } + + protected toNative(state: BeefyState, token: TokenEntity): TokenEntity { + const wnative = selectChainWrappedNativeToken(state, token.chainId); + const native = selectChainNativeToken(state, token.chainId); + return wnativeToNative(token, wnative, native); + } + + protected async fetchDepositTokens() { + // Find any token we can swap to a token that can be bridged and deposited in the vault + const { getState, vault, vaultType, swapAggregator } = this.helpers; + const state = getState(); + const depositToken = vaultType.depositToken; + const stargateToken = this.toNative(state, depositToken); + + const directPaths = this.paths.filter( + p => + p.canDeposit && + p.dest.token.address === stargateToken.address && + p.dest.token.chainId === stargateToken.chainId && + p.source.token.chainId !== stargateToken.chainId + ); + + const tokens = uniqueTokens(directPaths.map(p => p.source.token)); + const tokensByChainId = groupBy(tokens, t => t.chainId) as Record< + ChainEntity['id'], + TokenEntity[] + >; + const supportPerChain = await Promise.all( + entries(tokensByChainId).map(async ([chainId, tokens]) => + swapAggregator.fetchTokenSupport(tokens, vault.id, chainId, state, this.options.swap) + ) + ); + + return uniqueTokens([...tokens, ...supportPerChain.map(s => s.any).flat()]); + } + + protected async fetchWithdrawTokens() { + // Find any token we can swap the deposit token for that can also be bridged to another chain + const { getState, vault, vaultType, swapAggregator } = this.helpers; + const state = getState(); + // What can we swap the deposit token for + const swapSupport = await swapAggregator.fetchTokenSupport( + [vaultType.depositToken], + vault.id, + vault.chainId, + state, + this.options.swap + ); + + // Which path can accept one of those tokens + const possiblePaths = this.paths.filter( + p => + p.canWithdraw && + p.source.token.chainId === vault.chainId && + p.dest.token.chainId !== vault.chainId && + (isTokenEqual(vaultType.depositToken, p.source.token) || + swapSupport.any.some(s => isTokenEqual(s, p.source.token))) + ); + + return uniqueTokens(possiblePaths.map(p => p.dest.token).flat()); + } + + protected getStargateConfig(chainId: ChainEntity['id']) { + const config = stargateConfigs[chainId]; + if (!config) { + throw new Error(`No stargate config found for chain id ${chainId}`); + } + return config; + } + + protected getStargateChainId(chainId: ChainEntity['id']) { + const config = this.getStargateConfig(chainId); + return config.chainId; + } + + protected canReceiveZapDepositToChain(chainId: ChainEntity['id']) { + return stargateConfigs[chainId]?.zapReceiverAddress !== undefined; + } + + protected canReceiveZapWithdrawToChain(chainId: ChainEntity['id']) { + return stargateConfigs[chainId]?.composerAddress !== undefined; + } + + protected canSendZapFromChain(state: BeefyState, chainId: ChainEntity['id']) { + return ( + stargateConfigs[chainId]?.composerAddress !== undefined && + selectZapByChainIdOrUndefined(state, chainId) !== undefined + ); + } + + /** + * Returns estimate of the amount that will be received after fees + * Assumes 1:1 source:dest token before fees + */ + protected async applyFees(path: StargatePath, amount: BigNumber) { + const { getState } = this.helpers; + const state = getState(); + const amountWei = toWei(amount, path.source.token.decimals); + const amountSD = amountWei.dividedToIntegerBy(path.source.convertRate); + const chain = selectChainById(state, path.source.chainId); + const web3 = await getWeb3Instance(chain); + const contract = new web3.eth.Contract( + viemToWeb3Abi(StargateFeeLibraryAbi), + path.source.feeLibraryAddress + ); + const destChainId = this.getStargateChainId(path.dest.chainId); + if (!destChainId) { + throw new Error(`No stargate chain id found for chain id ${path.dest.chainId}`); + } + const fees: LibraryGetFeesResult = await contract.methods + .getFees( + path.source.poolId, + path.dest.poolId, + destChainId.toString(), + ZERO_ADDRESS, + amountSD.toString(10) + ) + .call(); + + const eqFee = new BigNumber(fees[LibraryGetFeesResultKey.eqFee]); + const eqReward = new BigNumber(fees[LibraryGetFeesResultKey.eqReward]); + const lpFee = new BigNumber(fees[LibraryGetFeesResultKey.lpFee]); + const protocolFee = new BigNumber(fees[LibraryGetFeesResultKey.protocolFee]); + // const lkbRemoveFee = new BigNumber(fees[LibraryGetFeesResultKey.lkbRemoveFee]); + + const feeSourceSD = eqFee.plus(protocolFee).plus(lpFee).minus(eqReward); + const feeSource = fromWei( + feeSourceSD.multipliedBy(path.source.convertRate), + path.source.token.decimals + ); + + // Assumes 1:1 source:dest token + return amount.minus(feeSource); + } + + protected makeDepositPayload(vaultAddress: string, tokenAddress: string, userAddress: string) { + if (tokenAddress === 'native') { + tokenAddress = ZERO_ADDRESS; + } + + return abiCoder.encodeParameters( + ['address', 'address', 'address'], + [vaultAddress, tokenAddress, userAddress] + ); + } + + protected async fetchBridgeCost( + fromToken: TokenEntity, + toToken: TokenEntity, + type: StargateZapType + ): Promise { + const { vault, getState } = this.helpers; + const state = getState(); + const fromChain = selectChainById(state, fromToken.chainId); + const fromStargateConfig = this.getStargateConfig(fromChain.id); + const toChain = selectChainById(state, toToken.chainId); + const toStargateConfig = this.getStargateConfig(toChain.id); + if (!toStargateConfig.composerAddress) { + throw new Error(`No stargate composer address found for chain id ${toChain.id}`); + } + const receiverAddress = + type === StargateZapType.Deposit ? toStargateConfig.zapReceiverAddress : ZERO_ADDRESS; + if (!receiverAddress) { + throw new Error(`No zap receiver address found for chain id ${toChain.id}`); + } + + const web3 = await getWeb3Instance(fromChain); + const contract = new web3.eth.Contract( + viemToWeb3Abi(StargateComposerAbi), + fromStargateConfig.composerAddress + ); + const lzTxObj = [ + type === StargateZapType.Deposit ? toStargateConfig.depositGasLimit : '0', // dstGasForCall + '0', // dstNativeAmount + '0x', // dstNativeAddr (bytes) + ]; + const payload = + type === StargateZapType.Deposit + ? this.makeDepositPayload(vault.earnContractAddress, toToken.address, receiverAddress) + : '0x'; + const { 0: gasCost }: { 0: string; 1: string } = await contract.methods + .quoteLayerZeroFee( + toStargateConfig.chainId, // _chainId (destination) + 1, // _functionType (TYPE_SWAP_REMOTE) + receiverAddress, // _toAddress + payload, // _transferAndCallPayload + lzTxObj // _lzTxParams + ) + .call(); + const native = selectChainNativeToken(state, fromToken.chainId); + + return { token: native, amount: fromWeiString(gasCost, native.decimals) }; + } + + protected async fetchZapSwap( + quoteStep: ZapQuoteStepSwap, + zapHelpers: ZapHelpers, + insertBalance: boolean + ): Promise { + if (isZapQuoteStepSwapAggregator(quoteStep)) { + return this.fetchZapSwapAggregator(quoteStep, zapHelpers, insertBalance); + } else { + throw new Error('Unknown zap quote swap step type'); + } + } + + protected async fetchZapSwapAggregator( + quoteStep: ZapQuoteStepSwapAggregator, + zapHelpers: ZapHelpers, + insertBalance: boolean + ): Promise { + const { swapAggregator } = this.helpers; + const { slippage, state, path } = zapHelpers; + + return await fetchZapAggregatorSwap( + { + quote: quoteStep.quote, + inputs: [{ token: quoteStep.fromToken, amount: quoteStep.fromAmount }], + outputs: [{ token: quoteStep.toToken, amount: quoteStep.toAmount }], + maxSlippage: slippage, + zapRouter: path.source.zap.router, + providerId: quoteStep.providerId, + insertBalance, + }, + swapAggregator, + state + ); + } + + protected async fetchZapBridge( + quoteStep: ZapQuoteStepBridge, + minInputAmount: BigNumber, + zapHelpers: ZapHelpers, + bridgeCost: TokenAmount, + direction: StargateZapType + ): Promise { + const { vault } = this.helpers; + const { state, path, slippage } = zapHelpers; + const userAddress = selectWalletAddress(state); + if (!userAddress) { + throw new Error('No wallet address found'); + } + + const input: TokenAmount = { token: quoteStep.from.token, amount: minInputAmount }; + const minLocal: TokenAmount = { + token: quoteStep.from.token, + amount: slipBy(minInputAmount, slippage, quoteStep.from.token.decimals), + }; + const output: TokenAmount = { token: quoteStep.to.token, amount: quoteStep.to.amount }; + const minOutput: TokenAmount = { + token: quoteStep.to.token, + amount: slipBy(quoteStep.to.amount, slippage, quoteStep.to.token.decimals), + }; + const fromStargateConfig = this.getStargateConfig(quoteStep.from.token.chainId); + const toStargateConfig = this.getStargateConfig(quoteStep.to.token.chainId); + const receiverAddress = + direction === StargateZapType.Deposit ? toStargateConfig.zapReceiverAddress : userAddress; + if (!receiverAddress) { + throw new Error(`No zap receiver address found for chain id ${quoteStep.to.token.chainId}`); + } + + const value = toWeiString( + bridgeCost.amount.plus( + isTokenEqual(quoteStep.from.token, bridgeCost.token) ? minInputAmount : BIG_ZERO + ), + bridgeCost.token.decimals + ); + const lzTxObj = [ + direction === StargateZapType.Deposit ? toStargateConfig.depositGasLimit : '0', // dstGasForCall + '0', // dstNativeAmount + '0x', // dstNativeAddr (bytes) + ]; + + const abi = viemToWeb3Abi(StargateComposerAbi).find( + f => f.type === 'function' && f.name === 'swap' + ); + if (!abi) { + throw new Error('No swap abi found'); + } + + const payload = + direction === StargateZapType.Deposit + ? this.makeDepositPayload(vault.earnContractAddress, vault.depositTokenAddress, userAddress) + : '0x'; + const data = abiCoder.encodeFunctionCall(abi, [ + toStargateConfig.chainId, // _dstChainId + path.source.poolId, // _srcPoolId + path.dest.poolId, // _dstPoolId + userAddress, // _refundAddress + toWeiString(input.amount, input.token.decimals), // _amountLD + toWeiString(minLocal.amount, minLocal.token.decimals), // _minAmountLD + lzTxObj, // _lzTxParams + receiverAddress, // _to + payload, // _payload + ]); + + // Fee is in native, so we can't insert into the order tokens if bridging from native + return { + inputs: [input], + outputs: [output], + minOutputs: [minOutput], + returned: [], + zaps: [ + { + target: fromStargateConfig.composerAddress, + value, + data, + tokens: isTokenErc20(input.token) + ? [ + { + token: getTokenAddress(input.token), + index: getInsertIndex(4), // 0:dstChainId,1:srcPoolId,2:dstPoolId,3:refundAddress,4:amountLD + }, + ] + : [], + }, + ], + }; + } +} diff --git a/src/features/data/apis/transact/strategies/stargate-crosschain-single/config.ts b/src/features/data/apis/transact/strategies/stargate-crosschain-single/config.ts new file mode 100644 index 000000000..e92d81804 --- /dev/null +++ b/src/features/data/apis/transact/strategies/stargate-crosschain-single/config.ts @@ -0,0 +1,74 @@ +import type { StargateConfig, StargateConfigPath, StargateConfigPool } from './types'; +import type { ChainEntity } from '../../../../entities/chain'; +import stargatePoolsJson from './stargate-pools.json'; +import stargatePathsJson from './stargate-paths.json'; + +export const stargateConfigs: Partial> = { + ethereum: { + chainId: 101, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + bsc: { + chainId: 102, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + avax: { + chainId: 106, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + polygon: { + chainId: 109, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + arbitrum: { + chainId: 110, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + zapReceiverAddress: '0x9dFa8913E5eaFD4DE0bB29033c4249268C2ae331', + }, + optimism: { + chainId: 111, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + zapReceiverAddress: '0x2B34705F5b5F37C6239dD8151d858A78f5959F2F', + }, + fantom: { + chainId: 112, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + metis: { + chainId: 151, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + base: { + chainId: 184, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + linea: { + chainId: 183, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + kava: { + chainId: 177, + composerAddress: '0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9', + depositGasLimit: '2000000', + }, + mantle: { + chainId: 181, + composerAddress: '0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97', + depositGasLimit: '2000000', + }, +}; + +export const stargatePools: Map = new Map( + stargatePoolsJson.map(pool => [pool.id, pool as StargateConfigPool]) +); +export const stargatePaths: StargateConfigPath[] = stargatePathsJson; diff --git a/src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-paths.json b/src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-paths.json new file mode 100644 index 000000000..9aa13e179 --- /dev/null +++ b/src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-paths.json @@ -0,0 +1,914 @@ +[ + { + "source": "101:1", + "dest": "111:1" + }, + { + "source": "101:1", + "dest": "106:1" + }, + { + "source": "101:1", + "dest": "106:2" + }, + { + "source": "101:1", + "dest": "102:5" + }, + { + "source": "101:1", + "dest": "102:2" + }, + { + "source": "101:1", + "dest": "109:1" + }, + { + "source": "101:1", + "dest": "109:2" + }, + { + "source": "101:1", + "dest": "110:1" + }, + { + "source": "101:1", + "dest": "110:2" + }, + { + "source": "101:1", + "dest": "184:1" + }, + { + "source": "101:1", + "dest": "181:1" + }, + { + "source": "101:2", + "dest": "111:1" + }, + { + "source": "101:2", + "dest": "106:1" + }, + { + "source": "101:2", + "dest": "106:2" + }, + { + "source": "101:2", + "dest": "102:5" + }, + { + "source": "101:2", + "dest": "102:2" + }, + { + "source": "101:2", + "dest": "109:1" + }, + { + "source": "101:2", + "dest": "109:2" + }, + { + "source": "101:2", + "dest": "110:1" + }, + { + "source": "101:2", + "dest": "110:2" + }, + { + "source": "101:2", + "dest": "184:1" + }, + { + "source": "101:2", + "dest": "177:2" + }, + { + "source": "101:2", + "dest": "181:2" + }, + { + "source": "101:11", + "dest": "102:11" + }, + { + "source": "101:13", + "dest": "111:13" + }, + { + "source": "101:13", + "dest": "110:13" + }, + { + "source": "101:13", + "dest": "184:13" + }, + { + "source": "101:13", + "dest": "183:13" + }, + { + "source": "101:3", + "dest": "111:3" + }, + { + "source": "101:3", + "dest": "109:3" + }, + { + "source": "101:7", + "dest": "111:7" + }, + { + "source": "101:7", + "dest": "106:7" + }, + { + "source": "101:7", + "dest": "110:7" + }, + { + "source": "101:15", + "dest": "111:15" + }, + { + "source": "101:15", + "dest": "110:15" + }, + { + "source": "101:19", + "dest": "151:19" + }, + { + "source": "102:2", + "dest": "111:1" + }, + { + "source": "102:2", + "dest": "106:1" + }, + { + "source": "102:2", + "dest": "106:2" + }, + { + "source": "102:2", + "dest": "101:1" + }, + { + "source": "102:2", + "dest": "101:2" + }, + { + "source": "102:2", + "dest": "109:1" + }, + { + "source": "102:2", + "dest": "109:2" + }, + { + "source": "102:2", + "dest": "110:1" + }, + { + "source": "102:2", + "dest": "110:2" + }, + { + "source": "102:2", + "dest": "112:21" + }, + { + "source": "102:2", + "dest": "184:1" + }, + { + "source": "102:2", + "dest": "177:2" + }, + { + "source": "102:2", + "dest": "181:2" + }, + { + "source": "102:5", + "dest": "111:1" + }, + { + "source": "102:5", + "dest": "106:1" + }, + { + "source": "102:5", + "dest": "106:2" + }, + { + "source": "102:5", + "dest": "101:1" + }, + { + "source": "102:5", + "dest": "101:2" + }, + { + "source": "102:5", + "dest": "109:1" + }, + { + "source": "102:5", + "dest": "109:2" + }, + { + "source": "102:5", + "dest": "110:1" + }, + { + "source": "102:5", + "dest": "110:2" + }, + { + "source": "102:11", + "dest": "101:11" + }, + { + "source": "102:16", + "dest": "101:16" + }, + { + "source": "102:16", + "dest": "110:16" + }, + { + "source": "102:19", + "dest": "151:19" + }, + { + "source": "106:1", + "dest": "111:1" + }, + { + "source": "106:1", + "dest": "102:5" + }, + { + "source": "106:1", + "dest": "102:2" + }, + { + "source": "106:1", + "dest": "101:1" + }, + { + "source": "106:1", + "dest": "101:2" + }, + { + "source": "106:1", + "dest": "109:1" + }, + { + "source": "106:1", + "dest": "109:2" + }, + { + "source": "106:1", + "dest": "110:1" + }, + { + "source": "106:1", + "dest": "110:2" + }, + { + "source": "106:1", + "dest": "112:21" + }, + { + "source": "106:1", + "dest": "184:1" + }, + { + "source": "106:1", + "dest": "181:1" + }, + { + "source": "106:2", + "dest": "111:1" + }, + { + "source": "106:2", + "dest": "102:5" + }, + { + "source": "106:2", + "dest": "102:2" + }, + { + "source": "106:2", + "dest": "101:1" + }, + { + "source": "106:2", + "dest": "101:2" + }, + { + "source": "106:2", + "dest": "109:1" + }, + { + "source": "106:2", + "dest": "109:2" + }, + { + "source": "106:2", + "dest": "110:1" + }, + { + "source": "106:2", + "dest": "110:2" + }, + { + "source": "106:2", + "dest": "112:21" + }, + { + "source": "106:2", + "dest": "184:1" + }, + { + "source": "106:2", + "dest": "177:2" + }, + { + "source": "106:2", + "dest": "181:2" + }, + { + "source": "106:7", + "dest": "111:7" + }, + { + "source": "106:7", + "dest": "101:7" + }, + { + "source": "106:7", + "dest": "110:7" + }, + { + "source": "106:19", + "dest": "151:19" + }, + { + "source": "109:1", + "dest": "111:1" + }, + { + "source": "109:1", + "dest": "106:1" + }, + { + "source": "109:1", + "dest": "106:2" + }, + { + "source": "109:1", + "dest": "102:5" + }, + { + "source": "109:1", + "dest": "102:2" + }, + { + "source": "109:1", + "dest": "101:1" + }, + { + "source": "109:1", + "dest": "101:2" + }, + { + "source": "109:1", + "dest": "110:1" + }, + { + "source": "109:1", + "dest": "110:2" + }, + { + "source": "109:1", + "dest": "112:21" + }, + { + "source": "109:1", + "dest": "184:1" + }, + { + "source": "109:1", + "dest": "181:1" + }, + { + "source": "109:2", + "dest": "111:1" + }, + { + "source": "109:2", + "dest": "106:1" + }, + { + "source": "109:2", + "dest": "106:2" + }, + { + "source": "109:2", + "dest": "102:5" + }, + { + "source": "109:2", + "dest": "102:2" + }, + { + "source": "109:2", + "dest": "101:1" + }, + { + "source": "109:2", + "dest": "101:2" + }, + { + "source": "109:2", + "dest": "110:1" + }, + { + "source": "109:2", + "dest": "110:2" + }, + { + "source": "109:2", + "dest": "112:21" + }, + { + "source": "109:2", + "dest": "177:2" + }, + { + "source": "109:2", + "dest": "181:2" + }, + { + "source": "109:3", + "dest": "111:3" + }, + { + "source": "109:3", + "dest": "101:3" + }, + { + "source": "109:16", + "dest": "106:16" + }, + { + "source": "109:16", + "dest": "111:16" + }, + { + "source": "109:16", + "dest": "102:16" + }, + { + "source": "110:1", + "dest": "112:1" + }, + { + "source": "110:1", + "dest": "111:1" + }, + { + "source": "110:1", + "dest": "106:1" + }, + { + "source": "110:1", + "dest": "106:2" + }, + { + "source": "110:1", + "dest": "102:5" + }, + { + "source": "110:1", + "dest": "102:2" + }, + { + "source": "110:1", + "dest": "101:1" + }, + { + "source": "110:1", + "dest": "101:2" + }, + { + "source": "110:1", + "dest": "109:1" + }, + { + "source": "110:1", + "dest": "109:2" + }, + { + "source": "110:1", + "dest": "112:21" + }, + { + "source": "110:1", + "dest": "184:1" + }, + { + "source": "110:1", + "dest": "181:1" + }, + { + "source": "110:2", + "dest": "111:1" + }, + { + "source": "110:2", + "dest": "106:1" + }, + { + "source": "110:2", + "dest": "106:2" + }, + { + "source": "110:2", + "dest": "102:5" + }, + { + "source": "110:2", + "dest": "102:2" + }, + { + "source": "110:2", + "dest": "101:1" + }, + { + "source": "110:2", + "dest": "101:2" + }, + { + "source": "110:2", + "dest": "109:1" + }, + { + "source": "110:2", + "dest": "109:2" + }, + { + "source": "110:2", + "dest": "112:21" + }, + { + "source": "110:2", + "dest": "177:2" + }, + { + "source": "110:2", + "dest": "181:2" + }, + { + "source": "110:13", + "dest": "111:13" + }, + { + "source": "110:13", + "dest": "101:13" + }, + { + "source": "110:13", + "dest": "184:13" + }, + { + "source": "110:13", + "dest": "183:13" + }, + { + "source": "110:7", + "dest": "111:7" + }, + { + "source": "110:7", + "dest": "106:7" + }, + { + "source": "110:7", + "dest": "101:7" + }, + { + "source": "110:15", + "dest": "111:15" + }, + { + "source": "110:15", + "dest": "101:15" + }, + { + "source": "111:1", + "dest": "110:1" + }, + { + "source": "111:1", + "dest": "110:2" + }, + { + "source": "111:1", + "dest": "106:1" + }, + { + "source": "111:1", + "dest": "106:2" + }, + { + "source": "111:1", + "dest": "102:5" + }, + { + "source": "111:1", + "dest": "102:2" + }, + { + "source": "111:1", + "dest": "101:1" + }, + { + "source": "111:1", + "dest": "101:2" + }, + { + "source": "111:1", + "dest": "109:1" + }, + { + "source": "111:1", + "dest": "109:2" + }, + { + "source": "111:1", + "dest": "112:21" + }, + { + "source": "111:1", + "dest": "184:1" + }, + { + "source": "111:1", + "dest": "177:2" + }, + { + "source": "111:1", + "dest": "181:1" + }, + { + "source": "111:13", + "dest": "110:13" + }, + { + "source": "111:13", + "dest": "101:13" + }, + { + "source": "111:13", + "dest": "184:13" + }, + { + "source": "111:13", + "dest": "183:13" + }, + { + "source": "111:3", + "dest": "109:3" + }, + { + "source": "111:3", + "dest": "101:3" + }, + { + "source": "111:7", + "dest": "110:7" + }, + { + "source": "111:7", + "dest": "106:7" + }, + { + "source": "111:7", + "dest": "101:7" + }, + { + "source": "111:15", + "dest": "101:15" + }, + { + "source": "111:15", + "dest": "110:15" + }, + { + "source": "111:16", + "dest": "109:16" + }, + { + "source": "112:20", + "dest": "102:20" + }, + { + "source": "112:21", + "dest": "111:1" + }, + { + "source": "112:21", + "dest": "110:1" + }, + { + "source": "112:21", + "dest": "110:2" + }, + { + "source": "112:21", + "dest": "106:1" + }, + { + "source": "112:21", + "dest": "106:2" + }, + { + "source": "112:21", + "dest": "102:2" + }, + { + "source": "112:21", + "dest": "109:1" + }, + { + "source": "112:21", + "dest": "109:2" + }, + { + "source": "151:19", + "dest": "101:19" + }, + { + "source": "151:19", + "dest": "106:19" + }, + { + "source": "151:19", + "dest": "102:19" + }, + { + "source": "184:13", + "dest": "111:13" + }, + { + "source": "184:13", + "dest": "110:13" + }, + { + "source": "184:13", + "dest": "101:13" + }, + { + "source": "184:13", + "dest": "183:13" + }, + { + "source": "184:1", + "dest": "102:2" + }, + { + "source": "184:1", + "dest": "106:1" + }, + { + "source": "184:1", + "dest": "106:2" + }, + { + "source": "184:1", + "dest": "111:1" + }, + { + "source": "184:1", + "dest": "110:1" + }, + { + "source": "184:1", + "dest": "110:2" + }, + { + "source": "184:1", + "dest": "101:1" + }, + { + "source": "184:1", + "dest": "101:2" + }, + { + "source": "184:1", + "dest": "109:1" + }, + { + "source": "184:1", + "dest": "109:2" + }, + { + "source": "184:1", + "dest": "181:1" + }, + { + "source": "183:13", + "dest": "184:13" + }, + { + "source": "183:13", + "dest": "110:13" + }, + { + "source": "183:13", + "dest": "111:13" + }, + { + "source": "183:13", + "dest": "101:13" + }, + { + "source": "177:2", + "dest": "109:2" + }, + { + "source": "177:2", + "dest": "111:1" + }, + { + "source": "177:2", + "dest": "101:2" + }, + { + "source": "177:2", + "dest": "110:2" + }, + { + "source": "177:2", + "dest": "106:2" + }, + { + "source": "177:2", + "dest": "102:2" + }, + { + "source": "181:1", + "dest": "111:1" + }, + { + "source": "181:1", + "dest": "184:1" + }, + { + "source": "181:1", + "dest": "106:1" + }, + { + "source": "181:1", + "dest": "109:1" + }, + { + "source": "181:1", + "dest": "101:1" + }, + { + "source": "181:1", + "dest": "110:1" + }, + { + "source": "181:2", + "dest": "106:2" + }, + { + "source": "181:2", + "dest": "109:2" + }, + { + "source": "181:2", + "dest": "101:2" + }, + { + "source": "181:2", + "dest": "110:2" + }, + { + "source": "181:2", + "dest": "102:2" + } +] diff --git a/src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-pools.json b/src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-pools.json new file mode 100644 index 000000000..b51052319 --- /dev/null +++ b/src/features/data/apis/transact/strategies/stargate-crosschain-single/stargate-pools.json @@ -0,0 +1,412 @@ +[ + { + "id": "101:1", + "chainId": "ethereum", + "symbol": "USDC", + "poolAddress": "0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56", + "tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "convertRate": "1", + "poolId": "1", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "101:2", + "chainId": "ethereum", + "symbol": "USDT", + "poolAddress": "0x38EA452219524Bb87e18dE1C24D3bB59510BD783", + "tokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "convertRate": "1", + "poolId": "2", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "101:11", + "chainId": "ethereum", + "symbol": "USDD", + "poolAddress": "0x692953e758c3669290cb1677180c64183cEe374e", + "tokenAddress": "0x0C10bF8FcB7Bf5412187A595ab97a3609160b5c6", + "convertRate": "1", + "poolId": "11", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "101:13", + "chainId": "ethereum", + "symbol": "ETH", + "poolAddress": "0x101816545F6bd2b1076434B54383a1E633390A2E", + "tokenAddress": "native", + "convertRate": "1", + "poolId": "13", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "101:3", + "chainId": "ethereum", + "symbol": "DAI", + "poolAddress": "0x0Faf1d2d3CED330824de3B8200fc8dc6E397850d", + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "convertRate": "1000000000000", + "poolId": "3", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "101:7", + "chainId": "ethereum", + "symbol": "FRAX", + "poolAddress": "0xfA0F307783AC21C39E939ACFF795e27b650F6e68", + "tokenAddress": "0x853d955aCEf822Db058eb8505911ED77F175b99e", + "convertRate": "1000000000000", + "poolId": "7", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "101:15", + "chainId": "ethereum", + "symbol": "LUSD", + "poolAddress": "0xE8F55368C82D38bbbbDb5533e7F56AfC2E978CC2", + "tokenAddress": "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0", + "convertRate": "1000000000000", + "poolId": "15", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "101:19", + "chainId": "ethereum", + "symbol": "USDT", + "poolAddress": "0x430Ebff5E3E80A6C58E7e6ADA1d90F5c28AA116d", + "tokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "convertRate": "1", + "poolId": "19", + "feeLibraryAddress": "0x8C3085D9a554884124C998CDB7f6d7219E9C1e6F" + }, + { + "id": "102:2", + "chainId": "bsc", + "symbol": "USDT", + "poolAddress": "0x9aA83081AA06AF7208Dcc7A4cB72C94d057D2cda", + "tokenAddress": "0x55d398326f99059fF775485246999027B3197955", + "convertRate": "1000000000000", + "poolId": "2", + "feeLibraryAddress": "0xCA6522116e8611A346D53Cc2005AC4192e3fc2BC" + }, + { + "id": "102:5", + "chainId": "bsc", + "symbol": "BUSD", + "poolAddress": "0x98a5737749490856b401DB5Dc27F522fC314A4e1", + "tokenAddress": "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", + "convertRate": "1000000000000", + "poolId": "5", + "feeLibraryAddress": "0xCA6522116e8611A346D53Cc2005AC4192e3fc2BC" + }, + { + "id": "102:11", + "chainId": "bsc", + "symbol": "USDD", + "poolAddress": "0x4e145a589e4c03cBe3d28520e4BF3089834289Df", + "tokenAddress": "0xd17479997F34dd9156Deef8F95A52D81D265be9c", + "convertRate": "1", + "poolId": "11", + "feeLibraryAddress": "0xCA6522116e8611A346D53Cc2005AC4192e3fc2BC" + }, + { + "id": "102:16", + "chainId": "bsc", + "symbol": "MAI", + "poolAddress": "0x7BfD7f2498C4796f10b6C611D9db393D3052510C", + "tokenAddress": "0x3F56e0c36d275367b8C502090EDF38289b3dEa0d", + "convertRate": "1000000000000", + "poolId": "16", + "feeLibraryAddress": "0xCA6522116e8611A346D53Cc2005AC4192e3fc2BC" + }, + { + "id": "102:19", + "chainId": "bsc", + "symbol": "USDT", + "poolAddress": "0x68C6c27fB0e02285829e69240BE16f32C5f8bEFe", + "tokenAddress": "0x55d398326f99059fF775485246999027B3197955", + "convertRate": "1000000000000", + "poolId": "19", + "feeLibraryAddress": "0xCA6522116e8611A346D53Cc2005AC4192e3fc2BC" + }, + { + "id": "106:1", + "chainId": "avax", + "symbol": "USDC", + "poolAddress": "0x1205f31718499dBf1fCa446663B532Ef87481fe1", + "tokenAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", + "convertRate": "1", + "poolId": "1", + "feeLibraryAddress": "0x5E8eC15ACB5Aa94D5f0589E54441b31c5e0B992d" + }, + { + "id": "106:2", + "chainId": "avax", + "symbol": "USDT", + "poolAddress": "0x29e38769f23701A2e4A8Ef0492e19dA4604Be62c", + "tokenAddress": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", + "convertRate": "1", + "poolId": "2", + "feeLibraryAddress": "0x5E8eC15ACB5Aa94D5f0589E54441b31c5e0B992d" + }, + { + "id": "106:7", + "chainId": "avax", + "symbol": "FRAX", + "poolAddress": "0x1c272232Df0bb6225dA87f4dEcD9d37c32f63Eea", + "tokenAddress": "0xD24C2Ad096400B6FBcd2ad8B24E7acBc21A1da64", + "convertRate": "1000000000000", + "poolId": "7", + "feeLibraryAddress": "0x5E8eC15ACB5Aa94D5f0589E54441b31c5e0B992d" + }, + { + "id": "106:19", + "chainId": "avax", + "symbol": "USDT", + "poolAddress": "0xEAe5c2F6B25933deB62f754f239111413A0A25ef", + "tokenAddress": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", + "convertRate": "1", + "poolId": "19", + "feeLibraryAddress": "0x5E8eC15ACB5Aa94D5f0589E54441b31c5e0B992d" + }, + { + "id": "109:1", + "chainId": "polygon", + "symbol": "pUSDCe", + "poolAddress": "0x1205f31718499dBf1fCa446663B532Ef87481fe1", + "tokenAddress": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + "convertRate": "1", + "poolId": "1", + "feeLibraryAddress": "0xb279b324Ea5648bE6402ABc727173A225383494C" + }, + { + "id": "109:2", + "chainId": "polygon", + "symbol": "USDT", + "poolAddress": "0x29e38769f23701A2e4A8Ef0492e19dA4604Be62c", + "tokenAddress": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + "convertRate": "1", + "poolId": "2", + "feeLibraryAddress": "0xb279b324Ea5648bE6402ABc727173A225383494C" + }, + { + "id": "109:3", + "chainId": "polygon", + "symbol": "DAI", + "poolAddress": "0x1c272232Df0bb6225dA87f4dEcD9d37c32f63Eea", + "tokenAddress": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + "convertRate": "1000000000000", + "poolId": "3", + "feeLibraryAddress": "0xb279b324Ea5648bE6402ABc727173A225383494C" + }, + { + "id": "109:16", + "chainId": "polygon", + "symbol": "MAI", + "poolAddress": "0x8736f92646B2542B3e5F3c63590cA7Fe313e283B", + "tokenAddress": "0xa3Fa99A148fA48D14Ed51d610c367C61876997F1", + "convertRate": "1000000000000", + "poolId": "16", + "feeLibraryAddress": "0xb279b324Ea5648bE6402ABc727173A225383494C" + }, + { + "id": "110:1", + "chainId": "arbitrum", + "symbol": "USDC.e", + "poolAddress": "0x892785f33CdeE22A30AEF750F285E18c18040c3e", + "tokenAddress": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "convertRate": "1", + "poolId": "1", + "feeLibraryAddress": "0x1cF31666c06ac3401ed0C1c6346C4A9425dd7De4" + }, + { + "id": "110:2", + "chainId": "arbitrum", + "symbol": "USDT", + "poolAddress": "0xB6CfcF89a7B22988bfC96632aC2A9D6daB60d641", + "tokenAddress": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + "convertRate": "1", + "poolId": "2", + "feeLibraryAddress": "0x1cF31666c06ac3401ed0C1c6346C4A9425dd7De4" + }, + { + "id": "110:13", + "chainId": "arbitrum", + "symbol": "ETH", + "poolAddress": "0x915A55e36A01285A14f05dE6e81ED9cE89772f8e", + "tokenAddress": "native", + "convertRate": "1", + "poolId": "13", + "feeLibraryAddress": "0x1cF31666c06ac3401ed0C1c6346C4A9425dd7De4" + }, + { + "id": "110:7", + "chainId": "arbitrum", + "symbol": "FRAX", + "poolAddress": "0xaa4BF442F024820B2C28Cd0FD72b82c63e66F56C", + "tokenAddress": "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", + "convertRate": "1000000000000", + "poolId": "7", + "feeLibraryAddress": "0x1cF31666c06ac3401ed0C1c6346C4A9425dd7De4" + }, + { + "id": "110:15", + "chainId": "arbitrum", + "symbol": "LUSD", + "poolAddress": "0x600E576F9d853c95d58029093A16EE49646F3ca5", + "tokenAddress": "0x93b346b6BC2548dA6A1E7d98E9a421B42541425b", + "convertRate": "1000000000000", + "poolId": "15", + "feeLibraryAddress": "0x1cF31666c06ac3401ed0C1c6346C4A9425dd7De4" + }, + { + "id": "111:1", + "chainId": "optimism", + "symbol": "USDCe", + "poolAddress": "0xDecC0c09c3B5f6e92EF4184125D5648a66E35298", + "tokenAddress": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", + "convertRate": "1", + "poolId": "1", + "feeLibraryAddress": "0x505eCDF2f14Cd4f1f413d04624b009A449D38D7E" + }, + { + "id": "111:13", + "chainId": "optimism", + "symbol": "ETH", + "poolAddress": "0xd22363e3762cA7339569F3d33EADe20127D5F98C", + "tokenAddress": "native", + "convertRate": "1", + "poolId": "13", + "feeLibraryAddress": "0x505eCDF2f14Cd4f1f413d04624b009A449D38D7E" + }, + { + "id": "111:3", + "chainId": "optimism", + "symbol": "DAI", + "poolAddress": "0x165137624F1f692e69659f944BF69DE02874ee27", + "tokenAddress": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", + "convertRate": "1000000000000", + "poolId": "3", + "feeLibraryAddress": "0x505eCDF2f14Cd4f1f413d04624b009A449D38D7E" + }, + { + "id": "111:7", + "chainId": "optimism", + "symbol": "FRAX", + "poolAddress": "0x368605D9C6243A80903b9e326f1Cddde088B8924", + "tokenAddress": "0x2E3D870790dC77A83DD1d18184Acc7439A53f475", + "convertRate": "1000000000000", + "poolId": "7", + "feeLibraryAddress": "0x505eCDF2f14Cd4f1f413d04624b009A449D38D7E" + }, + { + "id": "111:15", + "chainId": "optimism", + "symbol": "LUSD", + "poolAddress": "0x3533F5e279bDBf550272a199a223dA798D9eff78", + "tokenAddress": "0xc40F949F8a4e094D1b49a23ea9241D289B7b2819", + "convertRate": "1000000000000", + "poolId": "15", + "feeLibraryAddress": "0x505eCDF2f14Cd4f1f413d04624b009A449D38D7E" + }, + { + "id": "111:16", + "chainId": "optimism", + "symbol": "MAI", + "poolAddress": "0x5421FA1A48f9FF81e4580557E86C7C0D24C18036", + "tokenAddress": "0xdFA46478F9e5EA86d57387849598dbFB2e964b02", + "convertRate": "1000000000000", + "poolId": "16", + "feeLibraryAddress": "0x505eCDF2f14Cd4f1f413d04624b009A449D38D7E" + }, + { + "id": "112:20", + "chainId": "fantom", + "symbol": "WOO", + "poolAddress": "0x333b6E02eFFD8bE6075F3de0D8075FeD842dd9a3", + "tokenAddress": "0x6626c47c00F1D87902fc13EECfaC3ed06D5E8D8a", + "convertRate": "1", + "poolId": "20", + "feeLibraryAddress": "0x616a68BD6DAd19e066661C7278611487d4072839" + }, + { + "id": "112:21", + "chainId": "fantom", + "symbol": "lzUSDC", + "poolAddress": "0xc647CE76ec30033Aa319d472Ae9f4462068f2AD7", + "tokenAddress": "0x28a92dde19D9989F39A49905d7C9C2FAc7799bDf", + "convertRate": "1", + "poolId": "21", + "feeLibraryAddress": "0x616a68BD6DAd19e066661C7278611487d4072839" + }, + { + "id": "151:19", + "chainId": "metis", + "symbol": "m.USDT", + "poolAddress": "0x2b60473a7C41Deb80EDdaafD5560e963440eb632", + "tokenAddress": "0xbB06DCA3AE6887fAbF931640f67cab3e3a16F4dC", + "convertRate": "1", + "poolId": "19", + "feeLibraryAddress": "0x55bDb4164D28FBaF0898e0eF14a589ac09Ac9970" + }, + { + "id": "184:13", + "chainId": "base", + "symbol": "ETH", + "poolAddress": "0x28fc411f9e1c480AD312b3d9C60c22b965015c6B", + "tokenAddress": "native", + "convertRate": "1", + "poolId": "13", + "feeLibraryAddress": "0x9d1B1669c73b033DFe47ae5a0164Ab96df25B944" + }, + { + "id": "184:1", + "chainId": "base", + "symbol": "USDbC", + "poolAddress": "0x4c80E24119CFB836cdF0a6b53dc23F04F7e652CA", + "tokenAddress": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", + "convertRate": "1", + "poolId": "1", + "feeLibraryAddress": "0x9d1B1669c73b033DFe47ae5a0164Ab96df25B944" + }, + { + "id": "183:13", + "chainId": "linea", + "symbol": "ETH", + "poolAddress": "0xAad094F6A75A14417d39f04E690fC216f080A41a", + "tokenAddress": "native", + "convertRate": "1", + "poolId": "13", + "feeLibraryAddress": "0x45A01E4e04F14f7A4a6702c74187c5F6222033cd" + }, + { + "id": "177:2", + "chainId": "kava", + "symbol": "USDT", + "poolAddress": "0xAad094F6A75A14417d39f04E690fC216f080A41a", + "tokenAddress": "0x919C1c267BC06a7039e03fcc2eF738525769109c", + "convertRate": "1", + "poolId": "2", + "feeLibraryAddress": "0x45A01E4e04F14f7A4a6702c74187c5F6222033cd" + }, + { + "id": "181:1", + "chainId": "mantle", + "symbol": "USDC", + "poolAddress": "0xAad094F6A75A14417d39f04E690fC216f080A41a", + "tokenAddress": "0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9", + "convertRate": "1", + "poolId": "1", + "feeLibraryAddress": "0x45A01E4e04F14f7A4a6702c74187c5F6222033cd" + }, + { + "id": "181:2", + "chainId": "mantle", + "symbol": "USDT", + "poolAddress": "0x2b60473a7C41Deb80EDdaafD5560e963440eb632", + "tokenAddress": "0x201EBa5CC46D216Ce6DC03F6a759e8E766e956aE", + "convertRate": "1", + "poolId": "2", + "feeLibraryAddress": "0x45A01E4e04F14f7A4a6702c74187c5F6222033cd" + } +] diff --git a/src/features/data/apis/transact/strategies/stargate-crosschain-single/types.ts b/src/features/data/apis/transact/strategies/stargate-crosschain-single/types.ts new file mode 100644 index 000000000..97fbced96 --- /dev/null +++ b/src/features/data/apis/transact/strategies/stargate-crosschain-single/types.ts @@ -0,0 +1,66 @@ +import type { TokenEntity } from '../../../../entities/token'; +import type { ChainEntity } from '../../../../entities/chain'; +import type { ZapEntity } from '../../../../entities/zap'; + +export type StargateConfig = { + /** Stargate internal chain id */ + chainId: number; + /** StargateComposer address */ + composerAddress: string; + /** BeefyStargateZapReceiver address */ + zapReceiverAddress?: string; + /** Gas limit for zap deposit tx (deposit into vault) */ + depositGasLimit: string; +}; + +export type StargateConfigPool = { + id: string; + chainId: ChainEntity['id']; + symbol: string; + poolAddress: string; + tokenAddress: string; + convertRate: string; + poolId: string; + feeLibraryAddress: string; +}; + +export type StargateConfigPath = { + source: StargateConfigPool['id']; + dest: StargateConfigPool['id']; +}; + +export type StargatePath = { + canDeposit: boolean; + canWithdraw: boolean; + source: StargateConfigPool & { + token: TokenEntity; + zap: ZapEntity; + }; + dest: StargateConfigPool & { + token: TokenEntity; + zap: ZapEntity; + }; +}; + +export type LibraryGetFeesResult = { + 0: string; + 1: string; + 2: string; + 3: string; + 4: string; + 5: string; +}; + +export const LibraryGetFeesResultKey = { + amount: 0, + eqFee: 1, + eqReward: 2, + lpFee: 3, + protocolFee: 4, + lkbRemoveFee: 5, +} as const satisfies Record; + +export enum StargateZapType { + Deposit = 0, + Withdraw = 1, +} diff --git a/src/features/data/apis/transact/transact-types.ts b/src/features/data/apis/transact/transact-types.ts index 389ea10e4..64600db00 100644 --- a/src/features/data/apis/transact/transact-types.ts +++ b/src/features/data/apis/transact/transact-types.ts @@ -15,6 +15,7 @@ import type { } from '../../entities/zap'; import type { PlatformEntity } from '../../entities/platform'; import type { CurveTokenOption } from './strategies/curve/types'; +import type { StargatePath } from './strategies/stargate-crosschain-single/types'; export type TokenAmount = { amount: BigNumber; @@ -64,10 +65,13 @@ type BaseOption = { /** should be unique over all strategies and token selections */ id: string; vaultId: VaultEntity['id']; + /** chain the tx is executed on */ chainId: ChainEntity['id']; /** governs how selections are grouped in the UI, should be consistent for the same deposit input/withdraw output token(s) per chain */ selectionId: string; selectionOrder: number; + /** chain the output ends up on */ + selectionChainId?: ChainEntity['id']; inputs: TokenEntity[]; wantedOutputs: TokenEntity[]; }; @@ -156,6 +160,10 @@ export type SingleWithdrawOption = ZapBaseWithdrawOption & { strategyId: 'single'; }; +export type StargateCrossChainSingleDepositOption = ZapBaseDepositOption & { + strategyId: 'stargate-crosschain-single'; +}; + export type CurveDepositOption = ZapBaseDepositOption & { strategyId: 'curve'; } & ( @@ -178,6 +186,10 @@ export type ConicWithdrawOption = ZapBaseWithdrawOption & { strategyId: 'conic'; }; +export type StargateCrossChainSingleWithdrawOption = ZapBaseWithdrawOption & { + strategyId: 'stargate-crosschain-single'; +}; + export type DepositOption = | StandardVaultDepositOption | GovVaultDepositOption @@ -187,7 +199,8 @@ export type DepositOption = | SingleDepositOption | CurveDepositOption | CowcentratedDepositOption - | ConicDepositOption; + | ConicDepositOption + | StargateCrossChainSingleDepositOption; export type WithdrawOption = | StandardVaultWithdrawOption @@ -198,7 +211,8 @@ export type WithdrawOption = | SingleWithdrawOption | CurveWithdrawOption | CowcentratedWithdrawOption - | ConicWithdrawOption; + | ConicWithdrawOption + | StargateCrossChainSingleWithdrawOption; export type TransactOption = DepositOption | WithdrawOption; @@ -238,6 +252,14 @@ export type ZapQuoteStepSwapPool = BaseZapQuoteStepSwap & { export type ZapQuoteStepSwap = ZapQuoteStepSwapAggregator | ZapQuoteStepSwapPool; +export type ZapQuoteStepBridge = { + type: 'bridge'; + from: TokenAmount; + to: TokenAmount; + providerId: string; + fee: TokenAmount; +}; + export type ZapQuoteStepBuild = { type: 'build'; inputs: TokenAmount[]; @@ -276,7 +298,8 @@ export type ZapQuoteStep = | ZapQuoteStepBuild | ZapQuoteStepDeposit | ZapQuoteStepSplit - | ZapQuoteStepUnused; + | ZapQuoteStepUnused + | ZapQuoteStepBridge; export function isZapQuoteStepSwap(step: ZapQuoteStep): step is ZapQuoteStepSwap { return step.type === 'swap'; @@ -286,6 +309,10 @@ export function isZapQuoteStepWithdraw(step: ZapQuoteStep): step is ZapQuoteStep return step.type === 'withdraw'; } +export function isZapQuoteStepBridge(step: ZapQuoteStep): step is ZapQuoteStepBridge { + return step.type === 'bridge'; +} + export function isZapQuoteStepBuild(step: ZapQuoteStep): step is ZapQuoteStepBuild { return step.type === 'build'; } @@ -339,6 +366,11 @@ export type SingleDepositQuote = BaseZapQuote & { swapQuote: QuoteResponse; }; +export type StargateCrossChainSingleDepositQuote = + BaseZapQuote & { + path: StargatePath; + }; + export type UniswapLikePoolDepositQuote = BaseZapQuote< UniswapLikeDepositOption > & { @@ -386,7 +418,8 @@ export type ZapDepositQuote = | SolidlyDepositQuote | CurveDepositQuote | GammaDepositQuote - | ConicDepositQuote; + | ConicDepositQuote + | StargateCrossChainSingleDepositQuote; export type DepositQuote = VaultDepositQuote | ZapDepositQuote; @@ -404,6 +437,11 @@ export type CowcentratedVaultWithdrawQuote = BaseQuote; +export type StargateCrossChainSingleWithdrawQuote = + BaseZapQuote & { + path: StargatePath; + }; + export type UniswapLikeBreakWithdrawQuote = BaseZapQuote< UniswapLikeWithdrawOption >; @@ -449,7 +487,8 @@ export type ZapWithdrawQuote = | SolidlyWithdrawQuote | CurveWithdrawQuote | GammaWithdrawQuote - | ConicWithdrawQuote; + | ConicWithdrawQuote + | StargateCrossChainSingleWithdrawQuote; export type WithdrawQuote = VaultWithdrawQuote | ZapWithdrawQuote; diff --git a/src/features/data/apis/transact/transact.ts b/src/features/data/apis/transact/transact.ts index ba6c8ed8d..955005afc 100644 --- a/src/features/data/apis/transact/transact.ts +++ b/src/features/data/apis/transact/transact.ts @@ -26,9 +26,10 @@ import { strategyBuildersById } from './strategies'; import { vaultTypeBuildersById } from './vaults'; import { uniq } from 'lodash-es'; import { VaultStrategy } from './strategies/vault/VaultStrategy'; -import { selectZapByChainId } from '../../selectors/zap'; +import { selectZapByChainIdOrUndefined } from '../../selectors/zap'; import { getSwapAggregator } from '../instances'; import { CowcentratedStrategy } from './strategies/cowcentrated/CowcentratedStrategy'; +import { isDevelopment } from '../../utils/feature-flags'; export class TransactApi implements ITransactApi { protected async getHelpersForVault( @@ -38,7 +39,7 @@ export class TransactApi implements ITransactApi { const state = getState(); const vault = selectVaultById(state, vaultId); const vaultType = await this.getVaultTypeFor(vault, getState); - const zap = selectZapByChainId(state, vault.chainId); + const zap = selectZapByChainIdOrUndefined(state, vault.chainId); return { vault, @@ -62,11 +63,27 @@ export class TransactApi implements ITransactApi { // zaps const zapStrategies = await this.getZapStrategiesForVault(helpers); + if (vaultId === 'silo-usdce-arb') { + console.log(zapStrategies); + } if (zapStrategies.length) { const zapOptions = await allFulfilled( zapStrategies.map(zapStrategy => zapStrategy.fetchDepositOptions()) ); options.push(...zapOptions.flat()); + + if (isDevelopment) { + const strategiesWithNoOption = zapStrategies + .filter(s => !options.some(o => o.strategyId === s.id)) + .map(s => s.id); + if (strategiesWithNoOption.length > 0) { + console.debug( + vaultId, + 'no deposit options returned for', + strategiesWithNoOption.join(', ') + ); + } + } } return options; @@ -163,6 +180,18 @@ export class TransactApi implements ITransactApi { zapStrategies.map(zapStrategy => zapStrategy.fetchWithdrawOptions()) ); options.push(...zapOptions.flat()); + if (isDevelopment) { + const strategiesWithNoOption = zapStrategies + .filter(s => !options.some(o => o.strategyId === s.id)) + .map(s => s.id); + if (strategiesWithNoOption.length > 0) { + console.debug( + vaultId, + 'no withdraw options returned for', + strategiesWithNoOption.join(', ') + ); + } + } } else { console.debug('no zap strategies for', vaultId); // this is OK } @@ -262,11 +291,24 @@ export class TransactApi implements ITransactApi { return false; } - const options = await allFulfilled( - zapStrategies.map(zapStrategy => zapStrategy.fetchDepositOptions()) - ); + const options = ( + await allFulfilled(zapStrategies.map(zapStrategy => zapStrategy.fetchDepositOptions())) + ).flat(); + + if (isDevelopment) { + const strategiesWithNoOption = zapStrategies + .filter(s => !options.some(o => o.strategyId === s.id)) + .map(s => s.id); + if (strategiesWithNoOption.length > 0) { + console.debug( + vaultId, + 'no deposit options returned for', + strategiesWithNoOption.join(', ') + ); + } + } - return options.flat().length > 0; + return options.length > 0; } private async getZapStrategiesForVault(helpers: TransactHelpers): Promise { diff --git a/src/features/data/reducers/wallet/stepper.ts b/src/features/data/reducers/wallet/stepper.ts index 861a378c1..05a6f7476 100644 --- a/src/features/data/reducers/wallet/stepper.ts +++ b/src/features/data/reducers/wallet/stepper.ts @@ -41,6 +41,7 @@ export type Step = { vaultId?: VaultEntity['id']; zap?: boolean; rewards?: { token: TokenEntity; amount: BigNumber }; + stargate?: { from: TokenEntity; to: TokenEntity }; }; }; diff --git a/src/features/data/reducers/wallet/transact-types.ts b/src/features/data/reducers/wallet/transact-types.ts index 74a393bdf..bd4a3fe21 100644 --- a/src/features/data/reducers/wallet/transact-types.ts +++ b/src/features/data/reducers/wallet/transact-types.ts @@ -13,6 +13,7 @@ export enum TransactStep { Loading, Form, TokenSelect, + ChainSelect, QuoteSelect, } diff --git a/src/features/data/reducers/wallet/transact.ts b/src/features/data/reducers/wallet/transact.ts index 8d5711b9e..ab8d4cc76 100644 --- a/src/features/data/reducers/wallet/transact.ts +++ b/src/features/data/reducers/wallet/transact.ts @@ -18,6 +18,7 @@ import type { TransactState, } from './transact-types'; import { TransactMode, TransactStatus, TransactStep } from './transact-types'; +import type { ChainEntity } from '../../entities/chain'; const initialTransactTokens: TransactSelections = { allSelectionIds: [], @@ -99,6 +100,10 @@ const transactSlice = createSlice({ sliceState.dualInputMax = [false, false]; } }, + selectChain(sliceState, action: PayloadAction<{ chainId: ChainEntity['id'] }>) { + sliceState.selectedChainId = action.payload.chainId; + sliceState.step = TransactStep.TokenSelect; + }, setInputAmount(sliceState, action: PayloadAction<{ amount: BigNumber; max: boolean }>) { if (!sliceState.inputAmount.isEqualTo(action.payload.amount)) { sliceState.inputAmount = action.payload.amount; @@ -199,6 +204,7 @@ const transactSlice = createSlice({ }) .addCase(transactInit.fulfilled, (sliceState, action) => { sliceState.vaultId = action.meta.arg.vaultId; + sliceState.selectedChainId = action.payload.chainId; sliceState.step = TransactStep.Form; sliceState.mode = TransactMode.Deposit; }) @@ -226,7 +232,7 @@ const transactSlice = createSlice({ const defaultOption = first(options); if (defaultOption) { sliceState.selectedSelectionId = defaultOption.selectionId; - sliceState.selectedChainId = defaultOption.chainId; + sliceState.selectedChainId = defaultOption.selectionChainId ?? defaultOption.chainId; sliceState.forceSelection = options.length > 1; } } @@ -356,10 +362,11 @@ function addOptionsToState(sliceState: Draft, options: TransactOp } // Add chainId -> selectionId[] mapping - const byChainId = sliceState.selections.byChainId[option.chainId]; + const selectionChainId = option.selectionChainId ?? option.chainId; + const byChainId = sliceState.selections.byChainId[selectionChainId]; if (!byChainId) { - sliceState.selections.byChainId[option.chainId] = [option.selectionId]; - sliceState.selections.allChainIds.push(option.chainId); + sliceState.selections.byChainId[selectionChainId] = [option.selectionId]; + sliceState.selections.allChainIds.push(selectionChainId); } else if (!byChainId.includes(option.selectionId)) { byChainId.push(option.selectionId); } diff --git a/src/features/data/selectors/stepper.ts b/src/features/data/selectors/stepper.ts index 60fcfb317..6a1839664 100644 --- a/src/features/data/selectors/stepper.ts +++ b/src/features/data/selectors/stepper.ts @@ -104,6 +104,14 @@ export function selectMintResult(state: BeefyState) { return result; } +export function selectWalletActionSuccess(state: BeefyState): WalletActionsSuccessState { + if (isWalletActionSuccess(state.user.walletActions)) { + return state.user.walletActions; + } + + throw new Error('Not wallet action success'); +} + export function selectBridgeSuccess( state: BeefyState ): WalletActionsSuccessState { diff --git a/src/features/data/selectors/transact.ts b/src/features/data/selectors/transact.ts index 6dd7c3e6e..2123b58be 100644 --- a/src/features/data/selectors/transact.ts +++ b/src/features/data/selectors/transact.ts @@ -52,7 +52,8 @@ export const selectTransactDualMaxAmount = (state: BeefyState, index: number) => export const selectTransactDualMaxAmounts = (state: BeefyState) => state.ui.transact.dualInputMax; export const selectTransactSelectedChainId = (state: BeefyState) => - state.ui.transact.selectedChainId; + valueOrThrow(state.ui.transact.selectedChainId, 'No chain selected'); + export const selectTransactSelectedSelectionId = (state: BeefyState) => valueOrThrow(state.ui.transact.selectedSelectionId, 'No selected selection id found'); export const selectTransactSelectedQuoteId = (state: BeefyState) => @@ -153,7 +154,7 @@ export const selectTransactCowcentratedDepositNotSingleSideAllowed = (state: Bee return { noSingleSideAllowed, inputToken, neededToken }; }; -export const selectTransactTokenChains = (state: BeefyState) => +export const selectTransactTokenChainIds = (state: BeefyState) => state.ui.transact.selections.allChainIds; export const selectTransactNumTokens = (state: BeefyState) => diff --git a/src/features/data/selectors/vaults.ts b/src/features/data/selectors/vaults.ts index b6c8e9537..d4dc1a839 100644 --- a/src/features/data/selectors/vaults.ts +++ b/src/features/data/selectors/vaults.ts @@ -29,7 +29,7 @@ import { valueOrThrow } from '../utils/selector-utils'; export const selectAllVaultIds = (state: BeefyState) => state.entities.vaults.allIds; export const selectVaultById = createCachedSelector( - (state: BeefyState) => state.entities.vaults.byId, + (state: BeefyState, _vaultId: VaultEntity['id']) => state.entities.vaults.byId, (state: BeefyState, vaultId: VaultEntity['id']) => vaultId, (vaultsById, vaultId) => { const vault = vaultsById[vaultId]; diff --git a/src/features/data/selectors/zap.ts b/src/features/data/selectors/zap.ts index 8e3bf4359..916c4a951 100644 --- a/src/features/data/selectors/zap.ts +++ b/src/features/data/selectors/zap.ts @@ -8,10 +8,14 @@ import { selectPlatformByIdOrUndefined } from './platforms'; import type { TFunction } from 'react-i18next'; import { isZapQuoteStepSwap, type ZapQuoteStep } from '../apis/transact/transact-types'; import { uniqBy } from 'lodash-es'; +import { valueOrThrow } from '../utils/selector-utils'; -export const selectZapByChainId = (state: BeefyState, chainId: ChainEntity['id']) => +export const selectZapByChainIdOrUndefined = (state: BeefyState, chainId: ChainEntity['id']) => state.entities.zaps.zaps.byChainId[chainId] || undefined; +export const selectZapByChainId = (state: BeefyState, chainId: ChainEntity['id']) => + valueOrThrow(selectZapByChainIdOrUndefined(state, chainId), `No zap for chain ${chainId}`); + export const selectSwapAggregatorById = (state: BeefyState, id: SwapAggregatorEntity['id']) => state.entities.zaps.aggregators.byId[id] || undefined; diff --git a/src/features/vault/components/Actions/Transact/ChainSelectList/ChainSelectList.tsx b/src/features/vault/components/Actions/Transact/ChainSelectList/ChainSelectList.tsx new file mode 100644 index 000000000..d6c08ff68 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectList/ChainSelectList.tsx @@ -0,0 +1,63 @@ +import React, { memo, useCallback, useMemo } from 'react'; +import { makeStyles } from '@material-ui/core'; +import { styles } from './styles'; +import { useAppDispatch, useAppSelector } from '../../../../../../store'; +import { + selectTransactSelectedChainId, + selectTransactTokenChainIds, + selectTransactVaultId, +} from '../../../../../data/selectors/transact'; +import { Scrollable } from '../../../../../../components/Scrollable'; +import type { ListItemProps } from './components/ListItem'; +import { ListItem } from './components/ListItem'; +import { transactActions } from '../../../../../data/reducers/wallet/transact'; +import clsx from 'clsx'; +import { selectVaultById } from '../../../../../data/selectors/vaults'; +import { orderBy } from 'lodash-es'; + +const useStyles = makeStyles(styles); + +export type ChainSelectListProps = { + className?: string; +}; + +export const ChainSelectList = memo(function ChainSelectList({ className }) { + const dispatch = useAppDispatch(); + const classes = useStyles(); + const vaultId = useAppSelector(selectTransactVaultId); + const vault = useAppSelector(state => selectVaultById(state, vaultId)); + const availableChainIds = useAppSelector(selectTransactTokenChainIds); + const selectedChainId = useAppSelector(selectTransactSelectedChainId); + const sortedOptions = useMemo(() => { + return orderBy( + availableChainIds, + [id => (id === vault.chainId ? 1 : 0), id => id], + ['desc', 'asc'] + ); + }, [availableChainIds, vault]); + + const handleChainSelect = useCallback( + chainId => { + dispatch(transactActions.selectChain({ chainId })); + }, + [dispatch] + ); + + return ( +
+ +
+ {sortedOptions.map(chainId => ( + + ))} +
+
+
+ ); +}); diff --git a/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/ListItem.tsx b/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/ListItem.tsx new file mode 100644 index 000000000..57ef0aad4 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/ListItem.tsx @@ -0,0 +1,46 @@ +import React, { memo, useCallback } from 'react'; +import { makeStyles } from '@material-ui/core'; +import { styles } from './styles'; +import type { ChainEntity } from '../../../../../../../data/entities/chain'; +import clsx from 'clsx'; +import { ChevronRight } from '@material-ui/icons'; +import { ChainIcon } from '../../../../../../../bridge/components/Bridge/components/ChainIcon'; +import { useAppSelector } from '../../../../../../../../store'; +import { selectChainById } from '../../../../../../../data/selectors/chains'; + +const useStyles = makeStyles(styles); + +export type ListItemProps = { + chainId: ChainEntity['id']; + onSelect: (id: ChainEntity['id']) => void; + /** currently selected chain */ + selected: boolean; + /** chain is native to the vault */ + native: boolean; + className?: string; +}; +export const ListItem = memo(function ListItem({ + chainId, + className, + onSelect, + selected, + native, +}) { + const classes = useStyles(); + const chain = useAppSelector(state => selectChainById(state, chainId)); + const handleClick = useCallback(() => onSelect(chainId), [onSelect, chainId]); + + return ( + + ); +}); diff --git a/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/index.tsx b/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/index.tsx new file mode 100644 index 000000000..c85153980 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/index.tsx @@ -0,0 +1 @@ +export * from './ListItem'; diff --git a/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/styles.tsx b/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/styles.tsx new file mode 100644 index 000000000..fd9d0486d --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectList/components/ListItem/styles.tsx @@ -0,0 +1,43 @@ +import type { Theme } from '@material-ui/core'; + +export const styles = (theme: Theme) => ({ + item: { + ...theme.typography['body-lg-med'], + display: 'flex', + alignItems: 'center', + width: '100%', + color: theme.palette.text.dark, + background: 'transparent', + border: 'none', + boxShadow: 'none', + padding: 0, + margin: 0, + cursor: 'pointer', + userSelect: 'none' as const, + outline: 'none', + '&:hover, &:focus-visible': { + color: theme.palette.text.middle, + '& $arrow': { + color: '#fff', + }, + }, + '&$native': { + color: theme.palette.text.middle, + }, + }, + selected: {}, + native: {}, + icon: { + width: '24px', + height: '24px', + marginRight: '8px', + }, + name: { + marginRight: 'auto', + }, + arrow: { + marginLeft: '12px', + color: theme.palette.text.middle, + height: '24px', + }, +}); diff --git a/src/features/vault/components/Actions/Transact/ChainSelectList/index.tsx b/src/features/vault/components/Actions/Transact/ChainSelectList/index.tsx new file mode 100644 index 000000000..3c4389b68 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectList/index.tsx @@ -0,0 +1 @@ +export * from './ChainSelectList'; diff --git a/src/features/vault/components/Actions/Transact/ChainSelectList/styles.tsx b/src/features/vault/components/Actions/Transact/ChainSelectList/styles.tsx new file mode 100644 index 000000000..1d81618a6 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectList/styles.tsx @@ -0,0 +1,69 @@ +import type { Theme } from '@material-ui/core'; + +export const styles = (theme: Theme) => ({ + container: { + padding: '24px 0 0 0', + height: '469px', + width: '100%', + display: 'flex', + flexDirection: 'column' as const, + borderRadius: '0 0 12px 12px', + overflow: 'hidden', + }, + search: { + padding: '0 24px', + margin: '0 0 16px 0', + }, + searchInput: { + background: theme.palette.background.searchInputBg, + }, + chainSelector: { + padding: '0 24px', + margin: '0 0 16px 0', + }, + chainSelectorBtn: { + ...theme.typography['body-lg'], + padding: 0, + margin: 0, + background: 'none', + border: 'none', + boxShadow: 'none', + outline: 'none', + cursor: 'pointer', + color: theme.palette.text.middle, + '&:hover': { + color: theme.palette.text.light, + }, + }, + walletToggle: { + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + gap: '24px', + padding: '0 24px', + margin: '0 0 16px 0', + }, + inWallet: { + ...theme.typography['body-lg'], + color: theme.palette.text.dark, + }, + hideDust: { + textAlign: 'right' as const, + }, + listContainer: { + flexGrow: 1, + height: '100%', + }, + list: { + padding: '0 24px 24px 24px', + minHeight: '100px', + display: 'flex', + flexDirection: 'column' as const, + rowGap: '16px', + overflowY: 'auto' as const, + }, + noResults: { + padding: '8px 12px', + borderRadius: '8px', + background: theme.palette.background.contentLight, + }, +}); diff --git a/src/features/vault/components/Actions/Transact/ChainSelectStep/ChainSelectStep.tsx b/src/features/vault/components/Actions/Transact/ChainSelectStep/ChainSelectStep.tsx new file mode 100644 index 000000000..5c0bb0358 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectStep/ChainSelectStep.tsx @@ -0,0 +1,27 @@ +import React, { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { makeStyles } from '@material-ui/core'; +import { styles } from './styles'; +import { useAppDispatch } from '../../../../../../store'; +import { transactActions } from '../../../../../data/reducers/wallet/transact'; +import { StepHeader } from '../StepHeader'; +import { TransactStep } from '../../../../../data/reducers/wallet/transact-types'; +import { ChainSelectList } from '../ChainSelectList'; + +const useStyles = makeStyles(styles); + +export const ChainSelectStep = memo(function ChainSelectStep() { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const classes = useStyles(); + const handleBack = useCallback(() => { + dispatch(transactActions.switchStep(TransactStep.TokenSelect)); + }, [dispatch]); + + return ( +
+ + +
+ ); +}); diff --git a/src/features/vault/components/Actions/Transact/ChainSelectStep/index.tsx b/src/features/vault/components/Actions/Transact/ChainSelectStep/index.tsx new file mode 100644 index 000000000..41dcaa6b6 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectStep/index.tsx @@ -0,0 +1 @@ +export * from './ChainSelectStep'; diff --git a/src/features/vault/components/Actions/Transact/ChainSelectStep/styles.tsx b/src/features/vault/components/Actions/Transact/ChainSelectStep/styles.tsx new file mode 100644 index 000000000..082377ca8 --- /dev/null +++ b/src/features/vault/components/Actions/Transact/ChainSelectStep/styles.tsx @@ -0,0 +1,3 @@ +export const styles = () => ({ + container: {}, +}); diff --git a/src/features/vault/components/Actions/Transact/QuoteSelectStep/QuoteSelectStep.tsx b/src/features/vault/components/Actions/Transact/QuoteSelectStep/QuoteSelectStep.tsx index 0bbf213e7..a0220549e 100644 --- a/src/features/vault/components/Actions/Transact/QuoteSelectStep/QuoteSelectStep.tsx +++ b/src/features/vault/components/Actions/Transact/QuoteSelectStep/QuoteSelectStep.tsx @@ -30,7 +30,7 @@ export const QuoteSelectStep = memo(function QuoteSelectStep() { return (
- {t('Transact-SelectProvider')} +
diff --git a/src/features/vault/components/Actions/Transact/StepHeader/StepHeader.tsx b/src/features/vault/components/Actions/Transact/StepHeader/StepHeader.tsx index cfee07c01..a2766c4d9 100644 --- a/src/features/vault/components/Actions/Transact/StepHeader/StepHeader.tsx +++ b/src/features/vault/components/Actions/Transact/StepHeader/StepHeader.tsx @@ -7,19 +7,25 @@ import { ReactComponent as BackArrow } from '../../../../../../images/back-arrow const useStyles = makeStyles(styles); export type StepHeaderProps = { + title: string; onBack?: () => void; - children: ReactNode; + children?: ReactNode; }; -export const StepHeader = memo(function StepHeader({ onBack, children }) { +export const StepHeader = memo(function StepHeader({ title, onBack, children }) { const classes = useStyles(); return (
{onBack ? ( - ) : null} + ) : ( + title + )} {children}
); diff --git a/src/features/vault/components/Actions/Transact/StepHeader/styles.tsx b/src/features/vault/components/Actions/Transact/StepHeader/styles.tsx index 793ee136f..a29a95f36 100644 --- a/src/features/vault/components/Actions/Transact/StepHeader/styles.tsx +++ b/src/features/vault/components/Actions/Transact/StepHeader/styles.tsx @@ -23,23 +23,30 @@ export const styles = (theme: Theme) => ({ }, }, backButton: { + ...theme.typography['body-lg-med'], + display: 'flex', + gap: '8px', margin: 0, padding: 0, - borderRadius: '50%', - width: '24px', - height: '24px', - background: theme.palette.background.border, boxShadow: 'none', cursor: 'pointer', border: 'none', + background: 'none', + color: 'inherit', + }, + backIcon: { + background: theme.palette.background.border, color: theme.palette.text.light, + borderRadius: '50%', + width: '24px', + height: '24px', flexShrink: 0, flexGrow: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', }, - backIcon: { + backArrow: { fill: theme.palette.text.light, width: '12px', height: '9px', diff --git a/src/features/vault/components/Actions/Transact/TokenSelectList/DepositTokenSelectList.tsx b/src/features/vault/components/Actions/Transact/TokenSelectList/DepositTokenSelectList.tsx index a48d4d61e..f4f3e67d5 100644 --- a/src/features/vault/components/Actions/Transact/TokenSelectList/DepositTokenSelectList.tsx +++ b/src/features/vault/components/Actions/Transact/TokenSelectList/DepositTokenSelectList.tsx @@ -5,9 +5,9 @@ import { styles } from './styles'; import { useAppDispatch, useAppSelector } from '../../../../../../store'; import { selectTransactDepositTokensForChainIdWithBalances, + selectTransactSelectedChainId, selectTransactVaultId, } from '../../../../../data/selectors/transact'; -import { selectVaultById } from '../../../../../data/selectors/vaults'; import { SearchInput } from '../../../../../../components/SearchInput'; import { Scrollable } from '../../../../../../components/Scrollable'; import type { ListItemProps } from './components/ListItem'; @@ -18,8 +18,8 @@ import type { ToggleProps } from '../../../../../../components/Toggle'; import { Toggle } from '../../../../../../components/Toggle'; import clsx from 'clsx'; import buildLpIcon from '../../../../../../images/icons/build-lp.svg'; -import type { VaultEntity } from '../../../../../data/entities/vault'; import OpenInNewRoundedIcon from '@material-ui/icons/OpenInNewRounded'; +import { selectVaultById } from '../../../../../data/selectors/vaults'; const useStyles = makeStyles(styles); const DUST_HIDDEN_THRESHOLD = new BigNumber('0.01'); @@ -33,14 +33,11 @@ export const DepositTokenSelectList = memo( const { t } = useTranslation(); const dispatch = useAppDispatch(); const classes = useStyles(); - const vaultId = useAppSelector(selectTransactVaultId); - const vault = useAppSelector(state => selectVaultById(state, vaultId)); - // const availableChains = useAppSelector(selectTransactTokenChains); + const selectedChainId = useAppSelector(selectTransactSelectedChainId); const [dustHidden, setDustHidden] = useState(false); - const [selectedChain] = useState(vault.chainId); const [search, setSearch] = useState(''); const optionsForChain = useAppSelector(state => - selectTransactDepositTokensForChainIdWithBalances(state, selectedChain) + selectTransactDepositTokensForChainIdWithBalances(state, selectedChainId) ); const filteredOptionsForChain = useMemo(() => { let options = optionsForChain; @@ -61,7 +58,6 @@ export const DepositTokenSelectList = memo( return options; }, [optionsForChain, search, dustHidden]); - // const hasMultipleChains = availableChains.length > 1; const handleTokenSelect = useCallback( tokenId => { dispatch( @@ -95,7 +91,6 @@ export const DepositTokenSelectList = memo( />
- {/*hasMultipleChains ?
TODO {selectedChain}
: null*/}
{filteredOptionsForChain.length ? ( @@ -106,7 +101,7 @@ export const DepositTokenSelectList = memo( tokens={option.tokens} balance={option.balance} decimals={option.decimals} - chainId={selectedChain} + chainId={selectedChainId} onSelect={handleTokenSelect} /> )) @@ -115,13 +110,14 @@ export const DepositTokenSelectList = memo( )}
- {filteredOptionsForChain?.length > 1 && } + {optionsForChain?.length > 1 && }
); } ); -const BuildLpManually = memo(function BuildLpManually({ vaultId }: { vaultId: VaultEntity['id'] }) { +const BuildLpManually = memo(function BuildLpManually() { + const vaultId = useAppSelector(selectTransactVaultId); const vault = useAppSelector(state => selectVaultById(state, vaultId)); const { t } = useTranslation(); const classes = useStyles(); diff --git a/src/features/vault/components/Actions/Transact/TokenSelectList/WithdrawTokenSelectList.tsx b/src/features/vault/components/Actions/Transact/TokenSelectList/WithdrawTokenSelectList.tsx index d97f5dbb6..7f198263c 100644 --- a/src/features/vault/components/Actions/Transact/TokenSelectList/WithdrawTokenSelectList.tsx +++ b/src/features/vault/components/Actions/Transact/TokenSelectList/WithdrawTokenSelectList.tsx @@ -4,10 +4,9 @@ import { makeStyles } from '@material-ui/core'; import { styles } from './styles'; import { useAppDispatch, useAppSelector } from '../../../../../../store'; import { - selectTransactVaultId, + selectTransactSelectedChainId, selectTransactWithdrawSelectionsForChainWithBalances, } from '../../../../../data/selectors/transact'; -import { selectVaultById } from '../../../../../data/selectors/vaults'; import { SearchInput } from '../../../../../../components/SearchInput'; import { Scrollable } from '../../../../../../components/Scrollable'; import type { ListItemProps } from './components/ListItem'; @@ -26,13 +25,10 @@ export const WithdrawTokenSelectList = memo( const { t } = useTranslation(); const dispatch = useAppDispatch(); const classes = useStyles(); - const vaultId = useAppSelector(selectTransactVaultId); - const vault = useAppSelector(state => selectVaultById(state, vaultId)); - // const availableChains = useAppSelector(selectTransactTokenChains); - const [selectedChain] = useState(vault.chainId); + const selectedChainId = useAppSelector(selectTransactSelectedChainId); const [search, setSearch] = useState(''); const optionsForChain = useAppSelector(state => - selectTransactWithdrawSelectionsForChainWithBalances(state, selectedChain) + selectTransactWithdrawSelectionsForChainWithBalances(state, selectedChainId) ); const filteredOptionsForChain = useMemo(() => { let options = optionsForChain; @@ -49,7 +45,6 @@ export const WithdrawTokenSelectList = memo( return options; }, [optionsForChain, search]); - // const hasMultipleChains = availableChains.length > 1; const handleTokenSelect = useCallback( selectionId => { dispatch( @@ -67,7 +62,6 @@ export const WithdrawTokenSelectList = memo(
- {/*hasMultipleChains ?
TODO {selectedChain}
: null*/}
{filteredOptionsForChain.length ? ( @@ -78,7 +72,7 @@ export const WithdrawTokenSelectList = memo( tokens={option.tokens} balance={option.balance} decimals={option.decimals} - chainId={selectedChain} + chainId={selectedChainId} onSelect={handleTokenSelect} /> )) diff --git a/src/features/vault/components/Actions/Transact/TokenSelectList/styles.tsx b/src/features/vault/components/Actions/Transact/TokenSelectList/styles.tsx index 54b9ccb09..1ac150295 100644 --- a/src/features/vault/components/Actions/Transact/TokenSelectList/styles.tsx +++ b/src/features/vault/components/Actions/Transact/TokenSelectList/styles.tsx @@ -14,7 +14,7 @@ export const styles = (theme: Theme) => ({ deposit: {}, search: { padding: '0 24px', - margin: '0 0 24px 0', + margin: '0 0 16px 0', }, searchInput: { background: theme.palette.background.searchInputBg, @@ -23,6 +23,20 @@ export const styles = (theme: Theme) => ({ padding: '0 24px', margin: '0 0 16px 0', }, + chainSelectorBtn: { + ...theme.typography['body-lg'], + padding: 0, + margin: 0, + background: 'none', + border: 'none', + boxShadow: 'none', + outline: 'none', + cursor: 'pointer', + color: theme.palette.text.middle, + '&:hover': { + color: theme.palette.text.light, + }, + }, walletToggle: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', diff --git a/src/features/vault/components/Actions/Transact/TokenSelectStep/TokenSelectStep.tsx b/src/features/vault/components/Actions/Transact/TokenSelectStep/TokenSelectStep.tsx index d47d3fd6a..4a747861c 100644 --- a/src/features/vault/components/Actions/Transact/TokenSelectStep/TokenSelectStep.tsx +++ b/src/features/vault/components/Actions/Transact/TokenSelectStep/TokenSelectStep.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from 'react'; +import React, { memo, type MouseEventHandler, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { makeStyles } from '@material-ui/core'; import { styles } from './styles'; @@ -7,8 +7,15 @@ import { transactActions } from '../../../../../data/reducers/wallet/transact'; import { StepHeader } from '../StepHeader'; import { DepositTokenSelectList } from '../TokenSelectList'; import { TransactMode, TransactStep } from '../../../../../data/reducers/wallet/transact-types'; -import { selectTransactMode } from '../../../../../data/selectors/transact'; +import { + selectTransactMode, + selectTransactSelectedChainId, + selectTransactTokenChainIds, +} from '../../../../../data/selectors/transact'; import { WithdrawTokenSelectList } from '../TokenSelectList/WithdrawTokenSelectList'; +import { selectChainById } from '../../../../../data/selectors/chains'; +import { ChainIcon } from '../../../../../bridge/components/Bridge/components/ChainIcon'; +import { ExpandMore } from '@material-ui/icons'; const useStyles = makeStyles(styles); @@ -21,10 +28,25 @@ export const TokenSelectStep = memo(function TokenSelectStep() { const handleBack = useCallback(() => { dispatch(transactActions.switchStep(TransactStep.Form)); }, [dispatch]); + const availableChains = useAppSelector(selectTransactTokenChainIds); + const selectedChainId = useAppSelector(selectTransactSelectedChainId); + const selectedChain = useAppSelector(state => selectChainById(state, selectedChainId)); + const hasMultipleChains = availableChains.length > 1; + const handleSelectChain = useCallback>(() => { + dispatch(transactActions.switchStep(TransactStep.ChainSelect)); + }, [dispatch]); return (
- {t('Transact-SelectToken')} + + {hasMultipleChains ? ( + + ) : null} + {mode === TransactMode.Deposit ? : }
); diff --git a/src/features/vault/components/Actions/Transact/TokenSelectStep/styles.tsx b/src/features/vault/components/Actions/Transact/TokenSelectStep/styles.tsx index 082377ca8..61e8d2bdb 100644 --- a/src/features/vault/components/Actions/Transact/TokenSelectStep/styles.tsx +++ b/src/features/vault/components/Actions/Transact/TokenSelectStep/styles.tsx @@ -1,3 +1,24 @@ -export const styles = () => ({ +import type { Theme } from '@material-ui/core'; + +export const styles = (theme: Theme) => ({ container: {}, + chainSelectorBtn: { + ...theme.typography['body-lg'], + display: 'flex', + gap: '4px', + justifyContent: 'center', + alignItems: 'center', + padding: 0, + margin: '0 0 0 auto', + background: 'none', + border: 'none', + boxShadow: 'none', + outline: 'none', + cursor: 'pointer', + color: theme.palette.text.middle, + '&:hover': { + color: theme.palette.text.light, + }, + }, + chainSelectorIcon: {}, }); diff --git a/src/features/vault/components/Actions/Transact/Transact.tsx b/src/features/vault/components/Actions/Transact/Transact.tsx index 013367832..31073a8aa 100644 --- a/src/features/vault/components/Actions/Transact/Transact.tsx +++ b/src/features/vault/components/Actions/Transact/Transact.tsx @@ -13,11 +13,13 @@ import { Card } from '../../Card'; import { TokenSelectStep } from './TokenSelectStep'; import { TransactStep } from '../../../../data/reducers/wallet/transact-types'; import { QuoteSelectStep } from './QuoteSelectStep'; +import { ChainSelectStep } from './ChainSelectStep'; const stepToComponent: Record = { [TransactStep.Loading]: LoadingStep, [TransactStep.Form]: FormStep, [TransactStep.TokenSelect]: TokenSelectStep, + [TransactStep.ChainSelect]: ChainSelectStep, [TransactStep.QuoteSelect]: QuoteSelectStep, }; diff --git a/src/features/vault/components/Actions/Transact/ZapRoute/ZapRoute.tsx b/src/features/vault/components/Actions/Transact/ZapRoute/ZapRoute.tsx index 12f81fc21..21d2d50c5 100644 --- a/src/features/vault/components/Actions/Transact/ZapRoute/ZapRoute.tsx +++ b/src/features/vault/components/Actions/Transact/ZapRoute/ZapRoute.tsx @@ -3,6 +3,7 @@ import React, { Fragment, memo, useCallback, useMemo } from 'react'; import type { ZapQuote, ZapQuoteStep, + ZapQuoteStepBridge, ZapQuoteStepBuild, ZapQuoteStepDeposit, ZapQuoteStepSplit, @@ -23,6 +24,8 @@ import { QuoteTitle } from '../QuoteTitle'; import { transactActions } from '../../../../../data/reducers/wallet/transact'; import { TransactStep } from '../../../../../data/reducers/wallet/transact-types'; import { selectZapSwapProviderName } from '../../../../../data/selectors/zap'; +import { selectChainById } from '../../../../../data/selectors/chains'; +import { BIG_ZERO } from '../../../../../../helpers/big-number'; const useStyles = makeStyles(styles); @@ -71,6 +74,45 @@ const StepContentSwap = memo>(function StepCo ); }); +const StepContentBridge = memo>(function StepContentSwap({ + step, +}) { + const { t } = useTranslation(); + const { providerId, from, to, fee } = step; + const platformName = useAppSelector(state => + selectZapSwapProviderName(state, providerId, 'pool', t) + ); + const sourceChain = useAppSelector(state => selectChainById(state, from.token.chainId)); + const destChain = useAppSelector(state => selectChainById(state, to.token.chainId)); + const i18nKey = fee.amount.gt(BIG_ZERO) + ? 'Transact-Route-Step-Bridge-Fee' + : 'Transact-Route-Step-Bridge'; + + return ( + + ), + toAmount: , + feeAmount: ( + + ), + }} + /> + ); +}); + const StepContentBuild = memo>(function StepContentBuild({ step, }) { @@ -224,6 +266,7 @@ const StepContentComponents: Record< withdraw: StepContentWithdraw, split: StepContentSplit, unused: StepContentUnused, + bridge: StepContentBridge, }; type StepProps = { diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 1a338245d..7297b36d5 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -476,6 +476,10 @@ "Stepper-zap-out-Success-Title": "Withdraw Success", "Stepper-zap-out-Success-Content": "You have successfully zapped {{amount}} {{token}} out from the vault", "Stepper-zap-out-Building-Content": "Building zap-out transaction...", + "Stepper-zap-in-stargate-Success-Title": "Bridge Zap started", + "Stepper-zap-in-stargate-Success-Content": "Check the transaction link below to track the progress of your zap.", + "Stepper-zap-out-stargate-Success-Title": "Bridge Zap started", + "Stepper-zap-out-stargate-Success-Content": "Check the transaction link below to track the progress of your zap.", "Stepper-mint-Success-Title": "Mint Confirmed", "Stepper-mint-Building-Content": "Building mint transaction...", "Stepper-mint-mint-Success-Content": "You successfully minted {{amount}} {{token}}", @@ -568,6 +572,7 @@ "Transact-ClaimWithdrawAll": "Claim & Withdraw All", "Transact-SelectToken": "Select token", "Transact-SelectAmount": "Select amount", + "Transact-SelectChain": "Select chain", "Transact-SelectProvider": "Select provider", "Transact-Available": "Available:", "Transact-ZapRoute": "Zap Route", @@ -587,6 +592,8 @@ "Transact-Route-Step-Wrap": "Wrap {{fromToken}} to {{toToken}}", "Transact-Route-Step-Unwrap": "Unwrap {{fromToken}} to {{toToken}}", "Transact-Route-Step-Swap": "Swap {{fromToken}} for {{toToken}} via {{via}}", + "Transact-Route-Step-Bridge": "Bridge {{fromToken}} on {{fromChain}} to {{toToken}} on {{toChain}} via {{via}}", + "Transact-Route-Step-Bridge-Fee": "Bridge {{fromToken}} on {{fromChain}} to {{toToken}} on {{toChain}} via {{via}} ( {{feeToken}} fee)", "Transact-Route-Step-Build": "Build LP using on {{provider}}", "Transact-Route-Step-Withdraw": "Withdraw estimated {{token}}", "Transact-Route-Step-Deposit": "Deposit estimated {{token}}",