Skip to content

Commit

Permalink
chore: consolidate cowswap utils (#8331)
Browse files Browse the repository at this point in the history
  • Loading branch information
woodenfurniture authored Dec 11, 2024
1 parent 56743c0 commit 46e597a
Show file tree
Hide file tree
Showing 19 changed files with 232 additions and 247 deletions.
2 changes: 1 addition & 1 deletion packages/swapper/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assertUnreachable } from '@shapeshiftoss/utils'

import { COW_SWAP_SUPPORTED_CHAIN_IDS } from './cowswap-utils/constants'
import { arbitrumBridgeSwapper } from './swappers/ArbitrumBridgeSwapper/ArbitrumBridgeSwapper'
import { arbitrumBridgeApi } from './swappers/ArbitrumBridgeSwapper/endpoints'
import { ARBITRUM_BRIDGE_SUPPORTED_CHAIN_IDS } from './swappers/ArbitrumBridgeSwapper/utils/constants'
Expand All @@ -8,7 +9,6 @@ import { CHAINFLIP_SUPPORTED_CHAIN_IDS } from './swappers/ChainflipSwapper/const
import { chainflipApi } from './swappers/ChainflipSwapper/endpoints'
import { cowSwapper } from './swappers/CowSwapper/CowSwapper'
import { cowApi } from './swappers/CowSwapper/endpoints'
import { COW_SWAP_SUPPORTED_CHAIN_IDS } from './swappers/CowSwapper/utils/constants'
import { jupiterApi } from './swappers/JupiterSwapper/endpoints'
import { jupiterSwapper } from './swappers/JupiterSwapper/JupiterSwapper'
import { JUPITER_SUPPORTED_CHAIN_IDS } from './swappers/JupiterSwapper/utils/constants'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@ import type { ChainId } from '@shapeshiftoss/caip'
import { KnownChainIds } from '@shapeshiftoss/types'
import { zeroAddress } from 'viem'

import type { SupportedChainIds } from '../../../types'
import type { SupportedChainIds } from '../types'

export const DEFAULT_ADDRESS = zeroAddress
export const ORDER_TYPE_FIELDS = [
{ name: 'sellToken', type: 'address' },
{ name: 'buyToken', type: 'address' },
{ name: 'receiver', type: 'address' },
{ name: 'sellAmount', type: 'uint256' },
{ name: 'buyAmount', type: 'uint256' },
{ name: 'validTo', type: 'uint32' },
{ name: 'appData', type: 'bytes32' },
{ name: 'feeAmount', type: 'uint256' },
{ name: 'kind', type: 'string' },
{ name: 'partiallyFillable', type: 'bool' },
{ name: 'sellTokenBalance', type: 'string' },
{ name: 'buyTokenBalance', type: 'string' },
]

export const COW_SWAP_VAULT_RELAYER_ADDRESS = '0xc92e8bdf79f0507f65a392b0ab4667716bfe0110'
export const COW_SWAP_SETTLEMENT_ADDRESS = '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'
export const CANCELLATIONS_TYPE_FIELDS = [{ name: 'orderUids', type: 'bytes[]' }]

// Address used by CowSwap to buy ETH
// See https://github.com/gnosis/gp-v2-contracts/commit/821b5a8da213297b0f7f1d8b17c893c5627020af#diff-12bbbe13cd5cf42d639e34a39d8795021ba40d3ee1e1a8282df652eb161a11d6R13
export const COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
export const DEFAULT_ADDRESS = zeroAddress

export const SUPPORTED_CHAIN_IDS: ChainId[] = [
KnownChainIds.EthereumMainnet,
Expand All @@ -23,3 +33,10 @@ export const COW_SWAP_SUPPORTED_CHAIN_IDS: SupportedChainIds = {
sell: SUPPORTED_CHAIN_IDS,
buy: SUPPORTED_CHAIN_IDS,
}

export const COW_SWAP_VAULT_RELAYER_ADDRESS = '0xc92e8bdf79f0507f65a392b0ab4667716bfe0110'
export const COW_SWAP_SETTLEMENT_ADDRESS = '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'

// Address used by CowSwap to buy ETH
// See https://github.com/gnosis/gp-v2-contracts/commit/821b5a8da213297b0f7f1d8b17c893c5627020af#diff-12bbbe13cd5cf42d639e34a39d8795021ba40d3ee1e1a8282df652eb161a11d6R13
export const COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
116 changes: 97 additions & 19 deletions packages/swapper/src/cowswap-utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import type { LatestAppDataDocVersion } from '@cowprotocol/app-data'
import { MetadataApi, stringifyDeterministic } from '@cowprotocol/app-data'
import type { OrderClass, OrderClass1 } from '@cowprotocol/app-data/dist/generatedTypes/v1.3.0'
import type { ChainId } from '@shapeshiftoss/caip'
import { fromChainId } from '@shapeshiftoss/caip'
import { ASSET_NAMESPACE, fromChainId, toAssetId } from '@shapeshiftoss/caip'
import type { EvmChainAdapter, SignTypedDataInput } from '@shapeshiftoss/chain-adapters'
import { toAddressNList } from '@shapeshiftoss/chain-adapters'
import type { ETHSignTypedData, HDWallet } from '@shapeshiftoss/hdwallet-core'
import type { AccountMetadata, TypedDataTypes, UnsignedOrderCreation } from '@shapeshiftoss/types'
import { CowNetwork, KnownChainIds, TypedDataPrimaryType } from '@shapeshiftoss/types'
import {
bnOrZero,
convertDecimalPercentageToBasisPoints,
getNativeFeeAssetReference,
} from '@shapeshiftoss/utils'
import type { TypedData } from 'eip-712'
import type { TypedDataDomain } from 'ethers'
import { ethers } from 'ethers'
import type { Address } from 'viem'
import { keccak256, stringToBytes } from 'viem'

import { COW_SWAP_SETTLEMENT_ADDRESS } from '../swappers/CowSwapper'

export const ORDER_TYPE_FIELDS = [
{ name: 'sellToken', type: 'address' },
{ name: 'buyToken', type: 'address' },
{ name: 'receiver', type: 'address' },
{ name: 'sellAmount', type: 'uint256' },
{ name: 'buyAmount', type: 'uint256' },
{ name: 'validTo', type: 'uint32' },
{ name: 'appData', type: 'bytes32' },
{ name: 'feeAmount', type: 'uint256' },
{ name: 'kind', type: 'string' },
{ name: 'partiallyFillable', type: 'bool' },
{ name: 'sellTokenBalance', type: 'string' },
{ name: 'buyTokenBalance', type: 'string' },
]

export const CANCELLATIONS_TYPE_FIELDS = [{ name: 'orderUids', type: 'bytes[]' }]
import type { AffiliateAppDataFragment } from '../swappers/CowSwapper'
import { getTreasuryAddressFromChainId } from '../swappers/utils/helpers/helpers'
import {
CANCELLATIONS_TYPE_FIELDS,
COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS,
COW_SWAP_SETTLEMENT_ADDRESS,
ORDER_TYPE_FIELDS,
} from './constants'

export * from './constants'

export const getCowNetwork = (chainId: ChainId): CowNetwork | undefined => {
switch (chainId) {
Expand Down Expand Up @@ -157,3 +159,79 @@ export const signCowOrderCancellation = async (

return signature
}

export const cowSwapTokenToAssetId = (chainId: ChainId, cowSwapToken: Address) => {
const { chainNamespace, chainReference } = fromChainId(chainId)
return cowSwapToken.toLowerCase() === COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS.toLowerCase()
? toAssetId({
chainId,
assetNamespace: 'slip44',
assetReference: getNativeFeeAssetReference(chainNamespace, chainReference),
})
: toAssetId({
chainId,
assetNamespace: ASSET_NAMESPACE.erc20,
assetReference: cowSwapToken,
})
}

export const getAffiliateAppDataFragmentByChainId = ({
affiliateBps,
chainId,
}: {
affiliateBps: string
chainId: ChainId
}): AffiliateAppDataFragment => {
const hasAffiliateFee = bnOrZero(affiliateBps).gt(0)
if (!hasAffiliateFee) return {}

return {
partnerFee: {
bps: Number(affiliateBps),
recipient: getTreasuryAddressFromChainId(chainId),
},
}
}

type AppDataInfo = {
doc: LatestAppDataDocVersion
fullAppData: string
appDataKeccak256: string
env?: string
}

const generateAppDataFromDoc = async (
doc: LatestAppDataDocVersion,
): Promise<Pick<AppDataInfo, 'fullAppData' | 'appDataKeccak256'>> => {
const appData = await stringifyDeterministic(doc)
const appDataKeccak256 = keccak256(stringToBytes(appData))

return { fullAppData: appData, appDataKeccak256 }
}

const metadataApi = new MetadataApi()

// See https://api.cow.fi/docs/#/default/post_api_v1_quote / https://github.com/cowprotocol/app-data
export const getFullAppData = async (
slippageTolerancePercentage: string,
affiliateAppDataFragment: AffiliateAppDataFragment,
orderClass1: OrderClass1,
) => {
const APP_CODE = 'shapeshift'
const orderClass: OrderClass = { orderClass: orderClass1 }
const quote = {
slippageBips: convertDecimalPercentageToBasisPoints(slippageTolerancePercentage).toNumber(),
}

const appDataDoc = await metadataApi.generateAppDataDoc({
appCode: APP_CODE,
metadata: {
quote,
orderClass,
...affiliateAppDataFragment,
},
})

const { fullAppData, appDataKeccak256 } = await generateAppDataFromDoc(appDataDoc)
return { appDataHash: appDataKeccak256, appData: fullAppData }
}
13 changes: 6 additions & 7 deletions packages/swapper/src/swappers/CowSwapper/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import type { InterpolationOptions } from 'node-polyglot'
import { v4 as uuid } from 'uuid'

import { getDefaultSlippageDecimalPercentageForSwapper } from '../../constants'
import { assertGetCowNetwork } from '../../cowswap-utils'
import {
assertGetCowNetwork,
getAffiliateAppDataFragmentByChainId,
getFullAppData,
} from '../../cowswap-utils'
import type {
CommonTradeQuoteInput,
EvmMessageToSign,
Expand All @@ -31,12 +35,7 @@ import { getCowSwapTradeQuote } from './getCowSwapTradeQuote/getCowSwapTradeQuot
import { getCowSwapTradeRate } from './getCowSwapTradeRate/getCowSwapTradeRate'
import type { CowSwapGetTradesResponse, CowSwapGetTransactionsResponse } from './types'
import { cowService } from './utils/cowService'
import {
deductAffiliateFeesFromAmount,
deductSlippageFromAmount,
getAffiliateAppDataFragmentByChainId,
getFullAppData,
} from './utils/helpers/helpers'
import { deductAffiliateFeesFromAmount, deductSlippageFromAmount } from './utils/helpers/helpers'

const tradeQuoteMetadata: Map<string, { chainId: EvmChainId }> = new Map()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { AssetId } from '@shapeshiftoss/caip'
import type { Asset } from '@shapeshiftoss/types'

import { SUPPORTED_CHAIN_IDS } from '../../../cowswap-utils/constants'
import { isNativeEvmAsset } from '../../utils/helpers/helpers'
import { COWSWAP_UNSUPPORTED_ASSETS } from '../utils/blacklist'
import { SUPPORTED_CHAIN_IDS } from '../utils/constants'

export const filterAssetIdsBySellable = (assets: Asset[]): AssetId[] => {
return assets
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { AssetId } from '@shapeshiftoss/caip'

import { SUPPORTED_CHAIN_IDS } from '../../../cowswap-utils/constants'
import type { BuyAssetBySellIdInput } from '../../../types'
import { isNativeEvmAsset } from '../../utils/helpers/helpers'
import { COWSWAP_UNSUPPORTED_ASSETS } from '../utils/blacklist'
import { SUPPORTED_CHAIN_IDS } from '../utils/constants'

export const filterBuyAssetsBySellAssetId = ({
assets,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { Ok } from '@sniptt/monads'
import type { AxiosResponse } from 'axios'
import { describe, expect, it, vi } from 'vitest'

import {
COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS,
DEFAULT_ADDRESS,
} from '../../../cowswap-utils/constants'
import type { GetTradeQuoteInput, SwapperConfig, TradeQuote } from '../../../types'
import { SwapperName, TradeQuoteError } from '../../../types'
import {
Expand All @@ -15,7 +19,6 @@ import {
WETH,
XDAI,
} from '../../utils/test-data/assets'
import { COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS, DEFAULT_ADDRESS } from '../utils/constants'
import { cowService } from '../utils/cowService'
import type { CowSwapSellQuoteApiInput } from '../utils/helpers/helpers'
import { getCowSwapTradeQuote } from './getCowSwapTradeQuote'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ import type { AxiosError } from 'axios'
import { zeroAddress } from 'viem'

import { getDefaultSlippageDecimalPercentageForSwapper } from '../../../constants'
import { getCowNetwork } from '../../../cowswap-utils'
import {
getAffiliateAppDataFragmentByChainId,
getCowNetwork,
getFullAppData,
} from '../../../cowswap-utils'
import {
COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS,
COW_SWAP_VAULT_RELAYER_ADDRESS,
SUPPORTED_CHAIN_IDS,
} from '../../../cowswap-utils/constants'
import type {
GetEvmTradeQuoteInputBase,
SwapErrorRight,
Expand All @@ -18,16 +27,9 @@ import type {
import { SwapperName, TradeQuoteError } from '../../../types'
import { createTradeAmountTooSmallErr, makeSwapErrorRight } from '../../../utils'
import { isNativeEvmAsset } from '../../utils/helpers/helpers'
import {
COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS,
COW_SWAP_VAULT_RELAYER_ADDRESS,
SUPPORTED_CHAIN_IDS,
} from '../utils/constants'
import { cowService } from '../utils/cowService'
import {
assertValidTrade,
getAffiliateAppDataFragmentByChainId,
getFullAppData,
getNowPlusThirtyMinutesTimestamp,
getValuesFromQuoteResponse,
} from '../utils/helpers/helpers'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ import type { AxiosError } from 'axios'
import { zeroAddress } from 'viem'

import { getDefaultSlippageDecimalPercentageForSwapper } from '../../../constants'
import { getCowNetwork } from '../../../cowswap-utils'
import type { GetEvmTradeRateInput, SwapErrorRight, SwapperConfig, TradeRate } from '../../../types'
import { SwapperName, TradeQuoteError } from '../../../types'
import { createTradeAmountTooSmallErr, makeSwapErrorRight } from '../../../utils'
import { isNativeEvmAsset } from '../../utils/helpers/helpers'
import {
getAffiliateAppDataFragmentByChainId,
getCowNetwork,
getFullAppData,
} from '../../../cowswap-utils'
import {
COW_SWAP_NATIVE_ASSET_MARKER_ADDRESS,
COW_SWAP_VAULT_RELAYER_ADDRESS,
SUPPORTED_CHAIN_IDS,
} from '../utils/constants'
} from '../../../cowswap-utils/constants'
import type { GetEvmTradeRateInput, SwapErrorRight, SwapperConfig, TradeRate } from '../../../types'
import { SwapperName, TradeQuoteError } from '../../../types'
import { createTradeAmountTooSmallErr, makeSwapErrorRight } from '../../../utils'
import { isNativeEvmAsset } from '../../utils/helpers/helpers'
import { cowService } from '../utils/cowService'
import {
assertValidTrade,
getAffiliateAppDataFragmentByChainId,
getFullAppData,
getNowPlusThirtyMinutesTimestamp,
getValuesFromQuoteResponse,
} from '../utils/helpers/helpers'
Expand Down
1 change: 0 additions & 1 deletion packages/swapper/src/swappers/CowSwapper/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './types'
export * from './utils'
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { AxiosStatic } from 'axios'
import type { Awaitable, HookCleanupCallback } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import { getFullAppData, getNowPlusThirtyMinutesTimestamp } from './helpers'
import { getFullAppData } from '../../../../cowswap-utils'
import { getNowPlusThirtyMinutesTimestamp } from './helpers'

vi.mock('../cowService', async () => {
const axios: AxiosStatic = await vi.importMock('axios')
Expand Down
Loading

0 comments on commit 46e597a

Please sign in to comment.