diff --git a/packages/chain-adapters/src/utxo/utxoSelect/index.ts b/packages/chain-adapters/src/utxo/utxoSelect/index.ts index 0b455e111cf..ba594743ab3 100644 --- a/packages/chain-adapters/src/utxo/utxoSelect/index.ts +++ b/packages/chain-adapters/src/utxo/utxoSelect/index.ts @@ -6,6 +6,9 @@ import split from 'coinselect/split' const ZCASH_MARGINAL_FEE = 5000 const ZCASH_GRACE_ACTIONS = 2 +const ZCASH_P2PKH_STANDARD_INPUT_SIZE = 150 +const ZCASH_P2PKH_STANDARD_OUTPUT_SIZE = 34 +const TX_OUTPUT_BASE = 9 export type UTXOSelectInput = { utxos: unchained.bitcoin.Utxo[] @@ -90,8 +93,11 @@ export const utxoSelect = (input: UTXOSelectInput) => { return { ...result, outputs: result.outputs?.filter(o => !o.script) } } -const calculateZip317Fee = (numInputs: number, numOutputs: number): number => { - const logicalActions = Math.max(numInputs, numOutputs) +// https://zips.z.cash/zip-0317 +const calculateZip317Fee = (txInTotalSize: number, txOutTotalSize: number): number => { + const inputActions = Math.ceil(txInTotalSize / ZCASH_P2PKH_STANDARD_INPUT_SIZE) + const outputActions = Math.ceil(txOutTotalSize / ZCASH_P2PKH_STANDARD_OUTPUT_SIZE) + const logicalActions = Math.max(inputActions, outputActions) return ZCASH_MARGINAL_FEE * Math.max(ZCASH_GRACE_ACTIONS, logicalActions) } @@ -104,9 +110,16 @@ const coinSelectZcash = ( value: number } > => { + const opReturnOutputSize = extraOutput[0]?.script + ? TX_OUTPUT_BASE + extraOutput[0].script.length + : 0 + if (input.sendMax) { - const numOutputs = 1 + (input.opReturnData ? 1 : 0) - const feeWithoutChange = calculateZip317Fee(utxos.length, numOutputs) + const txInTotalSize = utxos.length * ZCASH_P2PKH_STANDARD_INPUT_SIZE + const txOutTotalSize = ZCASH_P2PKH_STANDARD_OUTPUT_SIZE + opReturnOutputSize + + const feeWithoutChange = calculateZip317Fee(txInTotalSize, txOutTotalSize) + const totalIn = utxos.reduce((sum, { value }) => sum + value, 0) const remainder = totalIn - feeWithoutChange @@ -126,8 +139,10 @@ const coinSelectZcash = ( inputs.push(utxo) totalIn += utxo.value - const numOutputs = 2 + (input.opReturnData ? 1 : 0) - feeWithChange = calculateZip317Fee(inputs.length, numOutputs) + const txInTotalSize = inputs.length * ZCASH_P2PKH_STANDARD_INPUT_SIZE + const txOutTotalSize = 2 * ZCASH_P2PKH_STANDARD_OUTPUT_SIZE + opReturnOutputSize + + feeWithChange = calculateZip317Fee(txInTotalSize, txOutTotalSize) if (totalIn >= Number(input.value) + feeWithChange) { const remainder = totalIn - Number(input.value) - feeWithChange diff --git a/packages/swapper/src/swappers/MayachainSwapper/generated/generatedTradableAssetMap.json b/packages/swapper/src/swappers/MayachainSwapper/generated/generatedTradableAssetMap.json index 07527cfc70c..fabb2b890d4 100644 --- a/packages/swapper/src/swappers/MayachainSwapper/generated/generatedTradableAssetMap.json +++ b/packages/swapper/src/swappers/MayachainSwapper/generated/generatedTradableAssetMap.json @@ -21,5 +21,6 @@ "ETH.USDT-0XDAC17F958D2EE523A2206206994597C13D831EC7": "eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7", "ETH.WSTETH-0X7F39C581F595B53C5CB19BD0B3F8DA6C935E2CA0": "eip155:1/erc20:0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", "THOR.RUNE": "cosmos:thorchain-1/slip44:931", + "ZEC.ZEC": "bip122:00040fe8ec8471911baa1db1266ea15d/slip44:133", "MAYA.CACAO": "cosmos:mayachain-mainnet-v1/slip44:931" } \ No newline at end of file diff --git a/scripts/generateTradableAssetMap/utils.ts b/scripts/generateTradableAssetMap/utils.ts index 35d5380692f..ac08e20f040 100644 --- a/scripts/generateTradableAssetMap/utils.ts +++ b/scripts/generateTradableAssetMap/utils.ts @@ -15,6 +15,7 @@ import { thorchainChainId, toAssetId, tronChainId, + zecChainId, } from '@shapeshiftoss/caip' import type { ThornodePoolResponse } from '@shapeshiftoss/swapper' import { KnownChainIds } from '@shapeshiftoss/types' @@ -36,6 +37,7 @@ enum Chain { LTC = 'LTC', THOR = 'THOR', TRON = 'TRON', + ZEC = 'ZEC', } const chainToChainId: Record = { @@ -52,6 +54,7 @@ const chainToChainId: Record = { [Chain.LTC]: ltcChainId, [Chain.THOR]: thorchainChainId, [Chain.TRON]: tronChainId, + [Chain.ZEC]: zecChainId, } const getFeeAssetFromChain = (chain: Chain): AssetId => { diff --git a/src/lib/utils/thorchain/hooks/useSendThorTx.tsx b/src/lib/utils/thorchain/hooks/useSendThorTx.tsx index d4b4a317b62..0b1c9e7b78c 100644 --- a/src/lib/utils/thorchain/hooks/useSendThorTx.tsx +++ b/src/lib/utils/thorchain/hooks/useSendThorTx.tsx @@ -3,7 +3,7 @@ import { fromAccountId, fromAssetId, thorchainAssetId } from '@shapeshiftoss/cai import type { FeeDataEstimate } from '@shapeshiftoss/chain-adapters' import { CONTRACT_INTERACTION, FeeDataKey } from '@shapeshiftoss/chain-adapters' import { isTrezor } from '@shapeshiftoss/hdwallet-trezor' -import { assertAndProcessMemo, depositWithExpiry } from '@shapeshiftoss/swapper' +import { assertAndProcessMemo, depositWithExpiry, SwapperName } from '@shapeshiftoss/swapper' import type { KnownChainIds } from '@shapeshiftoss/types' import { isToken } from '@shapeshiftoss/utils' import { useQuery } from '@tanstack/react-query' @@ -127,7 +127,7 @@ export const useSendThorTx = ({ const { data: inboundAddressData } = useQuery({ ...reactQueries.thornode.inboundAddresses(), staleTime: 60_000, - select: data => selectInboundAddressData(data, assetId), + select: data => selectInboundAddressData(data, assetId, SwapperName.Thorchain), enabled: Boolean(assetId && assetId !== thorchainAssetId), }) diff --git a/src/pages/Lending/Pool/components/Repay/RepayConfirm.tsx b/src/pages/Lending/Pool/components/Repay/RepayConfirm.tsx index e26eaf82bcf..fe930e524d4 100644 --- a/src/pages/Lending/Pool/components/Repay/RepayConfirm.tsx +++ b/src/pages/Lending/Pool/components/Repay/RepayConfirm.tsx @@ -18,7 +18,7 @@ import type { AccountId, AssetId } from '@shapeshiftoss/caip' import { fromAssetId } from '@shapeshiftoss/caip' import { isEvmChainId } from '@shapeshiftoss/chain-adapters' import { isLedger } from '@shapeshiftoss/hdwallet-ledger' -import { assertAndProcessMemo } from '@shapeshiftoss/swapper' +import { assertAndProcessMemo, SwapperName } from '@shapeshiftoss/swapper' import type { Asset } from '@shapeshiftoss/types' import { TxStatus } from '@shapeshiftoss/unchained-client' import { isToken } from '@shapeshiftoss/utils' @@ -244,7 +244,7 @@ export const RepayConfirm = ({ const { data: inboundAddressData, isLoading: isInboundAddressLoading } = useQuery({ ...reactQueries.thornode.inboundAddresses(), staleTime: 60_000, - select: data => selectInboundAddressData(data, repaymentAsset?.assetId), + select: data => selectInboundAddressData(data, repaymentAsset?.assetId, SwapperName.Thorchain), enabled: !!repaymentAsset?.assetId, }) diff --git a/src/pages/Lending/Pool/components/Repay/RepayInput.tsx b/src/pages/Lending/Pool/components/Repay/RepayInput.tsx index 41101fda0c3..a2785624f31 100644 --- a/src/pages/Lending/Pool/components/Repay/RepayInput.tsx +++ b/src/pages/Lending/Pool/components/Repay/RepayInput.tsx @@ -16,7 +16,7 @@ import { } from '@chakra-ui/react' import type { AccountId, AssetId } from '@shapeshiftoss/caip' import { fromAccountId } from '@shapeshiftoss/caip' -import { assertAndProcessMemo } from '@shapeshiftoss/swapper' +import { assertAndProcessMemo, SwapperName } from '@shapeshiftoss/swapper' import type { Asset } from '@shapeshiftoss/types' import { TxStatus } from '@shapeshiftoss/unchained-client' import { useQuery } from '@tanstack/react-query' @@ -131,7 +131,7 @@ export const RepayInput = ({ const { data: inboundAddressData, isLoading: isInboundAddressLoading } = useQuery({ ...reactQueries.thornode.inboundAddresses(), staleTime: 60_000, - select: data => selectInboundAddressData(data, repaymentAsset?.assetId), + select: data => selectInboundAddressData(data, repaymentAsset?.assetId, SwapperName.Thorchain), enabled: !!repaymentAsset?.assetId, }) diff --git a/src/pages/ThorChainLP/components/ReusableLpStatus/TransactionRow.tsx b/src/pages/ThorChainLP/components/ReusableLpStatus/TransactionRow.tsx index d435aeb6a75..70dfd84e508 100644 --- a/src/pages/ThorChainLP/components/ReusableLpStatus/TransactionRow.tsx +++ b/src/pages/ThorChainLP/components/ReusableLpStatus/TransactionRow.tsx @@ -420,7 +420,7 @@ export const TransactionRow: React.FC = ({ // halted by the time the user clicked the confirm button // But we still have some sane 60s stale time rather than 0 for paranoia's sake, as a balance of safety and not overfetching staleTime: 60_000, - select: data => selectInboundAddressData(data, assetId), + select: data => selectInboundAddressData(data, assetId, SwapperName.Thorchain), enabled: !!assetId, }) diff --git a/src/pages/ThorChainLP/queries/hooks/usePools.ts b/src/pages/ThorChainLP/queries/hooks/usePools.ts index 745dae2a9c0..4bf83425aad 100644 --- a/src/pages/ThorChainLP/queries/hooks/usePools.ts +++ b/src/pages/ThorChainLP/queries/hooks/usePools.ts @@ -115,7 +115,11 @@ export const usePools = () => { const assetId = thorPoolAssetIdToAssetId(pool.asset) if (!assetId) return acc - const inboundAddressesResponse = selectInboundAddressData(inboundAddressesData ?? [], assetId) + const inboundAddressesResponse = selectInboundAddressData( + inboundAddressesData ?? [], + assetId, + SwapperName.Thorchain, + ) const isTradingActive = selectIsTradingActive({ assetId, diff --git a/src/react-queries/hooks/useIsTradingActive.ts b/src/react-queries/hooks/useIsTradingActive.ts index 853ee027ac5..1209f6bd496 100644 --- a/src/react-queries/hooks/useIsTradingActive.ts +++ b/src/react-queries/hooks/useIsTradingActive.ts @@ -38,7 +38,7 @@ export const useIsTradingActive = ({ refetchOnMount: true, } : {}), - select: data => selectInboundAddressData(data, assetId), + select: data => selectInboundAddressData(data, assetId, swapperName), }) const { diff --git a/src/react-queries/selectors/index.ts b/src/react-queries/selectors/index.ts index 7c2d9020c62..46c67b3af09 100644 --- a/src/react-queries/selectors/index.ts +++ b/src/react-queries/selectors/index.ts @@ -2,6 +2,7 @@ import type { AssetId } from '@shapeshiftoss/caip' import type { evm } from '@shapeshiftoss/chain-adapters' import type { InboundAddressResponse } from '@shapeshiftoss/swapper' import { + assetIdToMayaPoolAssetId, assetIdToThorPoolAssetId, isNativeAsset, isRuji, @@ -18,10 +19,21 @@ import type { ThorchainMimir } from '@/lib/utils/thorchain/types' export const selectInboundAddressData = ( data: InboundAddressResponse[], assetId: AssetId | undefined, + swapperName: SwapperName, ): InboundAddressResponse | undefined => { if (!assetId) throw new Error(`AssetId is required: ${assetId}`) - const assetPoolId = assetIdToThorPoolAssetId({ assetId }) + const assetPoolId = (() => { + switch (swapperName) { + case SwapperName.Thorchain: + return assetIdToThorPoolAssetId({ assetId }) + case SwapperName.Mayachain: + return assetIdToMayaPoolAssetId({ assetId }) + default: + throw new Error(`Invalid swapper name: ${swapperName}`) + } + })() + const assetChainSymbol = assetPoolId?.slice(0, assetPoolId.indexOf('.')) return data.find(inbound => inbound.chain === assetChainSymbol) diff --git a/src/state/apis/swapper/helpers/swapperApiHelpers.ts b/src/state/apis/swapper/helpers/swapperApiHelpers.ts index 6d040d05dff..0efe5011364 100644 --- a/src/state/apis/swapper/helpers/swapperApiHelpers.ts +++ b/src/state/apis/swapper/helpers/swapperApiHelpers.ts @@ -112,7 +112,11 @@ export const checkTradingActivity = async ( gcTime: 0, }) - const inboundAddressResponse = selectInboundAddressData(inboundAddresses, assetId) + const inboundAddressResponse = selectInboundAddressData( + inboundAddresses, + assetId, + swapperName, + ) const mimir = await queryClient.fetchQuery({ ...getMimirQuery(chainId),