@cowprotocol/cow-sdk 0.0.8-RC.0 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -23
- package/dist/CowSdk.d.ts +2 -1
- package/dist/api/cow/index.d.ts +1 -1
- package/dist/api/cow/types.d.ts +34 -1
- package/dist/api/cow-subgraph/graphql.d.ts +2603 -0
- package/dist/api/cow-subgraph/index.d.ts +17 -0
- package/dist/api/cow-subgraph/queries.d.ts +3 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/metadata/index.d.ts +3 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +28 -1
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +28 -1
- package/dist/index.module.js.map +1 -1
- package/dist/utils/context.d.ts +12 -3
- package/dist/utils/ipfs.d.ts +8 -0
- package/package.json +27 -6
- package/.babelrc +0 -4
- package/.eslintrc.json +0 -15
- package/.github/workflows/build.yml +0 -50
- package/.github/workflows/lint.yml +0 -19
- package/.github/workflows/publish.yml +0 -20
- package/.github/workflows/test.yml +0 -47
- package/.nvmrc +0 -1
- package/.prettierignore +0 -1
- package/.prettierrc +0 -5
- package/COPYRIGHT.md +0 -13
- package/LICENSE-APACHE +0 -201
- package/LICENSE-MIT +0 -21
- package/babel.config.js +0 -3
- package/docs/images/CoW.png +0 -0
- package/src/CowSdk.ts +0 -53
- package/src/api/cow/errors/OperatorError.ts +0 -142
- package/src/api/cow/errors/QuoteError.ts +0 -115
- package/src/api/cow/index.ts +0 -367
- package/src/api/cow/types.ts +0 -82
- package/src/api/index.ts +0 -2
- package/src/api/metadata/index.ts +0 -37
- package/src/api/metadata/types.ts +0 -17
- package/src/constants/chains.ts +0 -11
- package/src/constants/index.ts +0 -16
- package/src/constants/tokens.ts +0 -16
- package/src/index.ts +0 -4
- package/src/schemas/appData.schema.json +0 -70
- package/src/types/index.ts +0 -6
- package/src/utils/appData.spec.ts +0 -109
- package/src/utils/appData.ts +0 -58
- package/src/utils/common.ts +0 -35
- package/src/utils/context.ts +0 -89
- package/src/utils/price.ts +0 -44
- package/src/utils/sign.ts +0 -224
- package/src/utils/tokens.ts +0 -12
- package/src/workflows/publish.sh +0 -49
- package/tsconfig.json +0 -17
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import log from 'loglevel'
|
|
2
|
-
import { CowError, logPrefix } from '../../../utils/common'
|
|
3
|
-
|
|
4
|
-
type ApiActionType = 'get' | 'create' | 'delete'
|
|
5
|
-
|
|
6
|
-
export interface ApiErrorObject {
|
|
7
|
-
errorType: ApiErrorCodes
|
|
8
|
-
description: string
|
|
9
|
-
data?: any
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Conforms to backend API
|
|
13
|
-
// https://github.com/gnosis/gp-v2-services/blob/d932e11c9a2125fdba239530be7684799f694909/crates/orderbook/openapi.yml#L801
|
|
14
|
-
// and
|
|
15
|
-
// https://github.com/gnosis/gp-v2-services/blob/d932e11c9a2125fdba239530be7684799f694909/crates/orderbook/openapi.yml#L740
|
|
16
|
-
export enum ApiErrorCodes {
|
|
17
|
-
DuplicateOrder = 'DuplicateOrder',
|
|
18
|
-
InvalidSignature = 'InvalidSignature',
|
|
19
|
-
MissingOrderData = 'MissingOrderData',
|
|
20
|
-
InsufficientValidTo = 'InsufficientValidTo',
|
|
21
|
-
InsufficientAllowance = 'InsufficientAllowance',
|
|
22
|
-
InsufficientBalance = 'InsufficientBalance',
|
|
23
|
-
InsufficientFee = 'InsufficientFee',
|
|
24
|
-
WrongOwner = 'WrongOwner',
|
|
25
|
-
NotFound = 'NotFound',
|
|
26
|
-
OrderNotFound = 'OrderNotFound',
|
|
27
|
-
AlreadyCancelled = 'AlreadyCancelled',
|
|
28
|
-
OrderFullyExecuted = 'OrderFullyExecuted',
|
|
29
|
-
OrderExpired = 'OrderExpired',
|
|
30
|
-
NoLiquidity = 'NoLiquidity',
|
|
31
|
-
UnsupportedToken = 'UnsupportedToken',
|
|
32
|
-
AmountIsZero = 'AmountIsZero',
|
|
33
|
-
SellAmountDoesNotCoverFee = 'SellAmountDoesNotCoverFee',
|
|
34
|
-
TransferEthToContract = 'TransferEthToContract',
|
|
35
|
-
UNHANDLED_GET_ERROR = 'UNHANDLED_GET_ERROR',
|
|
36
|
-
UNHANDLED_CREATE_ERROR = 'UNHANDLED_CREATE_ERROR',
|
|
37
|
-
UNHANDLED_DELETE_ERROR = 'UNHANDLED_DELETE_ERROR',
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export enum ApiErrorCodeDetails {
|
|
41
|
-
DuplicateOrder = 'There was another identical order already submitted. Please try again.',
|
|
42
|
-
InsufficientFee = "The signed fee is insufficient. It's possible that is higher now due to a change in the gas price, ether price, or the sell token price. Please try again to get an updated fee quote.",
|
|
43
|
-
InvalidSignature = 'The order signature is invalid. Check whether your Wallet app supports off-chain signing.',
|
|
44
|
-
MissingOrderData = 'The order has missing information',
|
|
45
|
-
InsufficientValidTo = 'The order you are signing is already expired. This can happen if you set a short expiration in the settings and waited too long before signing the transaction. Please try again.',
|
|
46
|
-
InsufficientAllowance = "The account doesn't have enough funds",
|
|
47
|
-
InsufficientBalance = 'The account needs to approve the selling token in order to trade',
|
|
48
|
-
WrongOwner = "The signature is invalid.\n\nIt's likely that the signing method provided by your wallet doesn't comply with the standards required by CowSwap.\n\nCheck whether your Wallet app supports off-chain signing (EIP-712 or ETHSIGN).",
|
|
49
|
-
NotFound = 'Token pair selected has insufficient liquidity',
|
|
50
|
-
OrderNotFound = 'The order you are trying to cancel does not exist',
|
|
51
|
-
AlreadyCancelled = 'Order is already cancelled',
|
|
52
|
-
OrderFullyExecuted = 'Order is already filled',
|
|
53
|
-
OrderExpired = 'Order is expired',
|
|
54
|
-
NoLiquidity = 'Token pair selected has insufficient liquidity',
|
|
55
|
-
UnsupportedToken = 'One of the tokens you are trading is unsupported. Please read the FAQ for more info.',
|
|
56
|
-
AmountIsZero = 'Amount is zero',
|
|
57
|
-
SellAmountDoesNotCoverFee = 'Sell amount does not sufficiently cover the current fee',
|
|
58
|
-
TransferEthToContract = 'Sending the native currency to smart contract wallets is not currently supported',
|
|
59
|
-
UNHANDLED_GET_ERROR = 'Order fetch failed. This may be due to a server or network connectivity issue. Please try again later.',
|
|
60
|
-
UNHANDLED_CREATE_ERROR = 'The order was not accepted by the network',
|
|
61
|
-
UNHANDLED_DELETE_ERROR = 'The order cancellation was not accepted by the network',
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function _mapActionToErrorDetail(action?: ApiActionType) {
|
|
65
|
-
switch (action) {
|
|
66
|
-
case 'get':
|
|
67
|
-
return ApiErrorCodeDetails.UNHANDLED_GET_ERROR
|
|
68
|
-
case 'create':
|
|
69
|
-
return ApiErrorCodeDetails.UNHANDLED_CREATE_ERROR
|
|
70
|
-
case 'delete':
|
|
71
|
-
return ApiErrorCodeDetails.UNHANDLED_DELETE_ERROR
|
|
72
|
-
default:
|
|
73
|
-
log.error(
|
|
74
|
-
logPrefix,
|
|
75
|
-
'[OperatorError::_mapActionToErrorDetails] Uncaught error mapping error action type to server error. Please try again later.'
|
|
76
|
-
)
|
|
77
|
-
return 'Something failed. Please try again later.'
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export default class OperatorError extends CowError {
|
|
82
|
-
name = 'OperatorError'
|
|
83
|
-
description: ApiErrorObject['description']
|
|
84
|
-
|
|
85
|
-
// Status 400 errors
|
|
86
|
-
// https://github.com/gnosis/gp-v2-services/blob/9014ae55412a356e46343e051aefeb683cc69c41/orderbook/openapi.yml#L563
|
|
87
|
-
static apiErrorDetails = ApiErrorCodeDetails
|
|
88
|
-
|
|
89
|
-
public static async getErrorMessage(response: Response, action: ApiActionType) {
|
|
90
|
-
try {
|
|
91
|
-
const orderPostError: ApiErrorObject = await response.json()
|
|
92
|
-
|
|
93
|
-
if (orderPostError.errorType) {
|
|
94
|
-
const errorMessage = OperatorError.apiErrorDetails[orderPostError.errorType]
|
|
95
|
-
// shouldn't fall through as this error constructor expects the error code to exist but just in case
|
|
96
|
-
return errorMessage || orderPostError.errorType
|
|
97
|
-
} else {
|
|
98
|
-
log.error(logPrefix, 'Unknown reason for bad order submission', orderPostError)
|
|
99
|
-
return orderPostError.description
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
log.error(logPrefix, 'Error handling a 400 error. Likely a problem deserialising the JSON response')
|
|
103
|
-
return _mapActionToErrorDetail(action)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
static async getErrorFromStatusCode(response: Response, action: 'create' | 'delete') {
|
|
107
|
-
switch (response.status) {
|
|
108
|
-
case 400:
|
|
109
|
-
case 404:
|
|
110
|
-
return this.getErrorMessage(response, action)
|
|
111
|
-
|
|
112
|
-
case 403:
|
|
113
|
-
return `The order cannot be ${action === 'create' ? 'accepted' : 'cancelled'}. Your account is deny-listed.`
|
|
114
|
-
|
|
115
|
-
case 429:
|
|
116
|
-
return `The order cannot be ${
|
|
117
|
-
action === 'create' ? 'accepted. Too many order placements' : 'cancelled. Too many order cancellations'
|
|
118
|
-
}. Please, retry in a minute`
|
|
119
|
-
|
|
120
|
-
case 500:
|
|
121
|
-
default:
|
|
122
|
-
log.error(
|
|
123
|
-
logPrefix,
|
|
124
|
-
`[OperatorError::getErrorFromStatusCode] Error ${
|
|
125
|
-
action === 'create' ? 'creating' : 'cancelling'
|
|
126
|
-
} the order, status code:`,
|
|
127
|
-
response.status || 'unknown'
|
|
128
|
-
)
|
|
129
|
-
return `Error ${action === 'create' ? 'creating' : 'cancelling'} the order`
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
constructor(apiError: ApiErrorObject) {
|
|
133
|
-
super(apiError.description, apiError.errorType)
|
|
134
|
-
|
|
135
|
-
this.description = apiError.description
|
|
136
|
-
this.message = ApiErrorCodeDetails[apiError.errorType]
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function isValidOperatorError(error: any): error is OperatorError {
|
|
141
|
-
return error instanceof OperatorError
|
|
142
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import log from 'loglevel'
|
|
2
|
-
import { CowError, logPrefix } from '../../../utils/common'
|
|
3
|
-
import { ApiErrorCodes, ApiErrorObject } from './OperatorError'
|
|
4
|
-
|
|
5
|
-
export interface GpQuoteErrorObject {
|
|
6
|
-
errorType: GpQuoteErrorCodes
|
|
7
|
-
description: string
|
|
8
|
-
data?: any
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Conforms to backend API
|
|
12
|
-
// https://github.com/gnosis/gp-v2-services/blob/0bd5f7743bebaa5acd3be13e35ede2326a096f14/orderbook/openapi.yml#L562
|
|
13
|
-
export enum GpQuoteErrorCodes {
|
|
14
|
-
UnsupportedToken = 'UnsupportedToken',
|
|
15
|
-
InsufficientLiquidity = 'InsufficientLiquidity',
|
|
16
|
-
FeeExceedsFrom = 'FeeExceedsFrom',
|
|
17
|
-
ZeroPrice = 'ZeroPrice',
|
|
18
|
-
UNHANDLED_ERROR = 'UNHANDLED_ERROR',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export enum GpQuoteErrorDetails {
|
|
22
|
-
UnsupportedToken = 'One of the tokens you are trading is unsupported. Please read the FAQ for more info.',
|
|
23
|
-
InsufficientLiquidity = 'Token pair selected has insufficient liquidity',
|
|
24
|
-
FeeExceedsFrom = 'Current fee exceeds entered "from" amount',
|
|
25
|
-
ZeroPrice = 'Quoted price is zero. This is likely due to a significant price difference between the two tokens. Please try increasing amounts.',
|
|
26
|
-
UNHANDLED_ERROR = 'Quote fetch failed. This may be due to a server or network connectivity issue. Please try again later.',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function mapOperatorErrorToQuoteError(error?: ApiErrorObject): GpQuoteErrorObject {
|
|
30
|
-
switch (error?.errorType) {
|
|
31
|
-
case ApiErrorCodes.NotFound:
|
|
32
|
-
case ApiErrorCodes.NoLiquidity:
|
|
33
|
-
return {
|
|
34
|
-
errorType: GpQuoteErrorCodes.InsufficientLiquidity,
|
|
35
|
-
description: GpQuoteErrorDetails.InsufficientLiquidity,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
case ApiErrorCodes.SellAmountDoesNotCoverFee:
|
|
39
|
-
return {
|
|
40
|
-
errorType: GpQuoteErrorCodes.FeeExceedsFrom,
|
|
41
|
-
description: GpQuoteErrorDetails.FeeExceedsFrom,
|
|
42
|
-
data: error?.data,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
case ApiErrorCodes.UnsupportedToken:
|
|
46
|
-
return {
|
|
47
|
-
errorType: GpQuoteErrorCodes.UnsupportedToken,
|
|
48
|
-
description: error.description,
|
|
49
|
-
}
|
|
50
|
-
case ApiErrorCodes.SellAmountDoesNotCoverFee:
|
|
51
|
-
return {
|
|
52
|
-
errorType: GpQuoteErrorCodes.FeeExceedsFrom,
|
|
53
|
-
description: error.description,
|
|
54
|
-
}
|
|
55
|
-
default:
|
|
56
|
-
return { errorType: GpQuoteErrorCodes.UNHANDLED_ERROR, description: GpQuoteErrorDetails.UNHANDLED_ERROR }
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export default class GpQuoteError extends CowError {
|
|
61
|
-
name = 'QuoteErrorObject'
|
|
62
|
-
description: string
|
|
63
|
-
// any data attached
|
|
64
|
-
data?: any
|
|
65
|
-
|
|
66
|
-
// Status 400 errors
|
|
67
|
-
// https://github.com/gnosis/gp-v2-services/blob/9014ae55412a356e46343e051aefeb683cc69c41/orderbook/openapi.yml#L563
|
|
68
|
-
static quoteErrorDetails = GpQuoteErrorDetails
|
|
69
|
-
|
|
70
|
-
public static async getErrorMessage(response: Response) {
|
|
71
|
-
try {
|
|
72
|
-
const orderPostError: GpQuoteErrorObject = await response.json()
|
|
73
|
-
|
|
74
|
-
if (orderPostError.errorType) {
|
|
75
|
-
const errorMessage = GpQuoteError.quoteErrorDetails[orderPostError.errorType]
|
|
76
|
-
// shouldn't fall through as this error constructor expects the error code to exist but just in case
|
|
77
|
-
return errorMessage || orderPostError.errorType
|
|
78
|
-
} else {
|
|
79
|
-
log.error(logPrefix, 'Unknown reason for bad quote fetch', orderPostError)
|
|
80
|
-
return orderPostError.description
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
log.error(logPrefix, 'Error handling 400/404 error. Likely a problem deserialising the JSON response')
|
|
84
|
-
return GpQuoteError.quoteErrorDetails.UNHANDLED_ERROR
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
static async getErrorFromStatusCode(response: Response) {
|
|
89
|
-
switch (response.status) {
|
|
90
|
-
case 400:
|
|
91
|
-
case 404:
|
|
92
|
-
return this.getErrorMessage(response)
|
|
93
|
-
|
|
94
|
-
case 500:
|
|
95
|
-
default:
|
|
96
|
-
log.error(
|
|
97
|
-
logPrefix,
|
|
98
|
-
'[QuoteError::getErrorFromStatusCode] Error fetching quote, status code:',
|
|
99
|
-
response.status || 'unknown'
|
|
100
|
-
)
|
|
101
|
-
return 'Error fetching quote'
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
constructor(quoteError: GpQuoteErrorObject) {
|
|
105
|
-
super(quoteError.description, quoteError.errorType)
|
|
106
|
-
|
|
107
|
-
this.description = quoteError.description
|
|
108
|
-
this.message = GpQuoteError.quoteErrorDetails[quoteError.errorType]
|
|
109
|
-
this.data = quoteError?.data
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function isValidQuoteError(error: any): error is GpQuoteError {
|
|
114
|
-
return error instanceof GpQuoteError
|
|
115
|
-
}
|
package/src/api/cow/index.ts
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
import log from 'loglevel'
|
|
2
|
-
import fetch from 'cross-fetch'
|
|
3
|
-
import { OrderKind, QuoteQuery } from '@gnosis.pm/gp-v2-contracts'
|
|
4
|
-
import { SupportedChainId as ChainId } from '../../constants/chains'
|
|
5
|
-
import { getSigningSchemeApiValue, OrderCreation } from '../../utils/sign'
|
|
6
|
-
import OperatorError, { ApiErrorCodeDetails, ApiErrorCodes, ApiErrorObject } from './errors/OperatorError'
|
|
7
|
-
import QuoteError, {
|
|
8
|
-
GpQuoteErrorCodes,
|
|
9
|
-
GpQuoteErrorObject,
|
|
10
|
-
mapOperatorErrorToQuoteError,
|
|
11
|
-
GpQuoteErrorDetails,
|
|
12
|
-
} from './errors/QuoteError'
|
|
13
|
-
import { toErc20Address } from '../../utils/tokens'
|
|
14
|
-
import { FeeQuoteParams, PriceInformation, PriceQuoteParams, SimpleGetQuoteResponse } from '../../utils/price'
|
|
15
|
-
|
|
16
|
-
import { ZERO_ADDRESS } from '../../constants'
|
|
17
|
-
import {
|
|
18
|
-
GetOrdersParams,
|
|
19
|
-
GetTradesParams,
|
|
20
|
-
OrderCancellationParams,
|
|
21
|
-
OrderID,
|
|
22
|
-
OrderMetaData,
|
|
23
|
-
ProfileData,
|
|
24
|
-
TradeMetaData,
|
|
25
|
-
} from './types'
|
|
26
|
-
import { CowError, logPrefix, objectToQueryString } from '../../utils/common'
|
|
27
|
-
import { Context } from '../../utils/context'
|
|
28
|
-
|
|
29
|
-
function getGnosisProtocolUrl(isDev: boolean): Partial<Record<ChainId, string>> {
|
|
30
|
-
if (isDev) {
|
|
31
|
-
return {
|
|
32
|
-
[ChainId.MAINNET]: 'https://barn.api.cow.fi/mainnet/api',
|
|
33
|
-
[ChainId.RINKEBY]: 'https://barn.api.cow.fi/rinkeby/api',
|
|
34
|
-
[ChainId.GNOSIS_CHAIN]: 'https://barn.api.cow.fi/xdai/api',
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
[ChainId.MAINNET]: 'https://api.cow.fi/mainnet/api',
|
|
40
|
-
[ChainId.RINKEBY]: 'https://api.cow.fi/rinkeby/api',
|
|
41
|
-
[ChainId.GNOSIS_CHAIN]: 'https://api.cow.fi/xdai/api',
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getProfileUrl(isDev: boolean): Partial<Record<ChainId, string>> {
|
|
46
|
-
if (isDev) {
|
|
47
|
-
return {
|
|
48
|
-
[ChainId.MAINNET]: 'https://barn.api.cow.fi/affiliate/api',
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
[ChainId.MAINNET]: 'https://api.cow.fi/affiliate/api',
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const UNHANDLED_QUOTE_ERROR: GpQuoteErrorObject = {
|
|
58
|
-
errorType: GpQuoteErrorCodes.UNHANDLED_ERROR,
|
|
59
|
-
description: GpQuoteErrorDetails.UNHANDLED_ERROR,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const UNHANDLED_ORDER_ERROR: ApiErrorObject = {
|
|
63
|
-
errorType: ApiErrorCodes.UNHANDLED_CREATE_ERROR,
|
|
64
|
-
description: ApiErrorCodeDetails.UNHANDLED_CREATE_ERROR,
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function _handleQuoteResponse<T = any, P extends QuoteQuery = QuoteQuery>(
|
|
68
|
-
response: Response,
|
|
69
|
-
params?: P
|
|
70
|
-
): Promise<T> {
|
|
71
|
-
if (!response.ok) {
|
|
72
|
-
const errorObj: ApiErrorObject = await response.json()
|
|
73
|
-
|
|
74
|
-
// we need to map the backend error codes to match our own for quotes
|
|
75
|
-
const mappedError = mapOperatorErrorToQuoteError(errorObj)
|
|
76
|
-
const quoteError = new QuoteError(mappedError)
|
|
77
|
-
|
|
78
|
-
if (params) {
|
|
79
|
-
const { sellToken, buyToken } = params
|
|
80
|
-
log.error(logPrefix, `Error querying fee from API - sellToken: ${sellToken}, buyToken: ${buyToken}`)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
throw quoteError
|
|
84
|
-
} else {
|
|
85
|
-
return response.json()
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export class CowApi {
|
|
90
|
-
context: Context
|
|
91
|
-
|
|
92
|
-
API_NAME = 'CoW Protocol'
|
|
93
|
-
|
|
94
|
-
constructor(context: Context) {
|
|
95
|
-
this.context = context
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
get DEFAULT_HEADERS() {
|
|
99
|
-
return { 'Content-Type': 'application/json', 'X-AppId': this.context.appDataHash }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
get API_BASE_URL() {
|
|
103
|
-
return getGnosisProtocolUrl(this.context.isDevEnvironment)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
get PROFILE_API_BASE_URL(): Partial<Record<ChainId, string>> {
|
|
107
|
-
return getProfileUrl(this.context.isDevEnvironment)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async getProfileData(address: string): Promise<ProfileData | null> {
|
|
111
|
-
const chainId = await this.context.chainId
|
|
112
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Get profile data for`, chainId, address)
|
|
113
|
-
if (chainId !== ChainId.MAINNET) {
|
|
114
|
-
log.info(logPrefix, 'Profile data is only available for mainnet')
|
|
115
|
-
return null
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const response = await this.getProfile(`/profile/${address}`)
|
|
119
|
-
|
|
120
|
-
if (!response.ok) {
|
|
121
|
-
const errorResponse = await response.json()
|
|
122
|
-
log.error(logPrefix, errorResponse)
|
|
123
|
-
throw new CowError(errorResponse?.description)
|
|
124
|
-
} else {
|
|
125
|
-
return response.json()
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async getTrades(params: GetTradesParams): Promise<TradeMetaData[]> {
|
|
130
|
-
const { owner, limit, offset } = params
|
|
131
|
-
const qsParams = objectToQueryString({ owner, limit, offset })
|
|
132
|
-
const chainId = await this.context.chainId
|
|
133
|
-
log.debug(logPrefix, '[util:operator] Get trades for', chainId, owner, { limit, offset })
|
|
134
|
-
try {
|
|
135
|
-
const response = await this.get(`/trades${qsParams}`)
|
|
136
|
-
|
|
137
|
-
if (!response.ok) {
|
|
138
|
-
const errorResponse = await response.json()
|
|
139
|
-
throw new CowError(errorResponse)
|
|
140
|
-
} else {
|
|
141
|
-
return response.json()
|
|
142
|
-
}
|
|
143
|
-
} catch (error) {
|
|
144
|
-
log.error(logPrefix, 'Error getting trades:', error)
|
|
145
|
-
throw new CowError('Error getting trades: ' + error)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async getOrders(params: GetOrdersParams): Promise<OrderMetaData[]> {
|
|
150
|
-
const { owner, limit = 1000, offset = 0 } = params
|
|
151
|
-
const queryString = objectToQueryString({ limit, offset })
|
|
152
|
-
const chainId = await this.context.chainId
|
|
153
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Get orders for `, chainId, owner, limit, offset)
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const response = await this.get(`/account/${owner}/orders/${queryString}`)
|
|
157
|
-
|
|
158
|
-
if (!response.ok) {
|
|
159
|
-
const errorResponse: ApiErrorObject = await response.json()
|
|
160
|
-
throw new OperatorError(errorResponse)
|
|
161
|
-
} else {
|
|
162
|
-
return response.json()
|
|
163
|
-
}
|
|
164
|
-
} catch (error) {
|
|
165
|
-
log.error(logPrefix, 'Error getting orders information:', error)
|
|
166
|
-
throw new OperatorError(UNHANDLED_ORDER_ERROR)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async getTxOrders(txHash: string): Promise<OrderMetaData[]> {
|
|
171
|
-
const chainId = await this.context.chainId
|
|
172
|
-
log.debug(`[api:${this.API_NAME}] Get tx orders for `, chainId, txHash)
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const response = await this.get(`/transactions/${txHash}/orders`)
|
|
176
|
-
|
|
177
|
-
if (!response.ok) {
|
|
178
|
-
const errorResponse: ApiErrorObject = await response.json()
|
|
179
|
-
throw new OperatorError(errorResponse)
|
|
180
|
-
} else {
|
|
181
|
-
return response.json()
|
|
182
|
-
}
|
|
183
|
-
} catch (error) {
|
|
184
|
-
log.error('Error getting transaction orders information:', error)
|
|
185
|
-
if (error instanceof OperatorError) throw error
|
|
186
|
-
throw new OperatorError(UNHANDLED_ORDER_ERROR)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async getOrder(orderId: string): Promise<OrderMetaData | null> {
|
|
191
|
-
const chainId = await this.context.chainId
|
|
192
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Get order for `, chainId, orderId)
|
|
193
|
-
try {
|
|
194
|
-
const response = await this.get(`/orders/${orderId}`)
|
|
195
|
-
|
|
196
|
-
if (!response.ok) {
|
|
197
|
-
const errorResponse: ApiErrorObject = await response.json()
|
|
198
|
-
throw new OperatorError(errorResponse)
|
|
199
|
-
} else {
|
|
200
|
-
return response.json()
|
|
201
|
-
}
|
|
202
|
-
} catch (error) {
|
|
203
|
-
log.error(logPrefix, 'Error getting order information:', error)
|
|
204
|
-
throw new OperatorError(UNHANDLED_ORDER_ERROR)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async getPriceQuoteLegacy(params: PriceQuoteParams): Promise<PriceInformation | null> {
|
|
209
|
-
const { baseToken, quoteToken, amount, kind } = params
|
|
210
|
-
const chainId = await this.context.chainId
|
|
211
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Get price from API`, params, 'for', chainId)
|
|
212
|
-
|
|
213
|
-
const response = await this.get(
|
|
214
|
-
`/markets/${toErc20Address(baseToken, chainId)}-${toErc20Address(quoteToken, chainId)}/${kind}/${amount}`
|
|
215
|
-
).catch((error) => {
|
|
216
|
-
log.error(logPrefix, 'Error getting price quote:', error)
|
|
217
|
-
throw new QuoteError(UNHANDLED_QUOTE_ERROR)
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
return _handleQuoteResponse<PriceInformation | null>(response)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async getQuote(params: FeeQuoteParams): Promise<SimpleGetQuoteResponse> {
|
|
224
|
-
const chainId = await this.context.chainId
|
|
225
|
-
const quoteParams = this.mapNewToLegacyParams(params, chainId)
|
|
226
|
-
const response = await this.post('/quote', quoteParams)
|
|
227
|
-
|
|
228
|
-
return _handleQuoteResponse<SimpleGetQuoteResponse>(response)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async sendSignedOrderCancellation(params: OrderCancellationParams): Promise<void> {
|
|
232
|
-
const { cancellation, owner: from } = params
|
|
233
|
-
const chainId = await this.context.chainId
|
|
234
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Delete signed order for network`, chainId, cancellation)
|
|
235
|
-
|
|
236
|
-
const response = await this.delete(`/orders/${cancellation.orderUid}`, {
|
|
237
|
-
signature: cancellation.signature,
|
|
238
|
-
signingScheme: getSigningSchemeApiValue(cancellation.signingScheme),
|
|
239
|
-
from,
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
if (!response.ok) {
|
|
243
|
-
// Raise an exception
|
|
244
|
-
const errorMessage = await OperatorError.getErrorFromStatusCode(response, 'delete')
|
|
245
|
-
throw new CowError(errorMessage)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Cancelled order`, cancellation.orderUid, chainId)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async sendOrder(params: { order: Omit<OrderCreation, 'appData'>; owner: string }): Promise<OrderID> {
|
|
252
|
-
const fullOrder: OrderCreation = { ...params.order, appData: this.context.appDataHash }
|
|
253
|
-
const chainId = await this.context.chainId
|
|
254
|
-
const { owner } = params
|
|
255
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Post signed order for network`, chainId, fullOrder)
|
|
256
|
-
|
|
257
|
-
// Call API
|
|
258
|
-
const response = await this.post(`/orders`, {
|
|
259
|
-
...fullOrder,
|
|
260
|
-
signingScheme: getSigningSchemeApiValue(fullOrder.signingScheme),
|
|
261
|
-
from: owner,
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
// Handle response
|
|
265
|
-
if (!response.ok) {
|
|
266
|
-
// Raise an exception
|
|
267
|
-
const errorMessage = await OperatorError.getErrorFromStatusCode(response, 'create')
|
|
268
|
-
throw new CowError(errorMessage)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const uid = (await response.json()) as string
|
|
272
|
-
log.debug(logPrefix, `[api:${this.API_NAME}] Success posting the signed order`, uid)
|
|
273
|
-
return uid
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
getOrderLink(orderId: OrderID): string {
|
|
277
|
-
const baseUrl = this.getApiBaseUrl()
|
|
278
|
-
|
|
279
|
-
return baseUrl + `/orders/${orderId}`
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
private mapNewToLegacyParams(params: FeeQuoteParams, chainId: ChainId): QuoteQuery {
|
|
283
|
-
const { amount, kind, userAddress, receiver, validTo, sellToken, buyToken } = params
|
|
284
|
-
const fallbackAddress = userAddress || ZERO_ADDRESS
|
|
285
|
-
|
|
286
|
-
const baseParams = {
|
|
287
|
-
sellToken: toErc20Address(sellToken, chainId),
|
|
288
|
-
buyToken: toErc20Address(buyToken, chainId),
|
|
289
|
-
from: fallbackAddress,
|
|
290
|
-
receiver: receiver || fallbackAddress,
|
|
291
|
-
appData: this.context.appDataHash,
|
|
292
|
-
validTo,
|
|
293
|
-
partiallyFillable: false,
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const finalParams: QuoteQuery =
|
|
297
|
-
kind === OrderKind.SELL
|
|
298
|
-
? {
|
|
299
|
-
kind: OrderKind.SELL,
|
|
300
|
-
sellAmountBeforeFee: amount,
|
|
301
|
-
...baseParams,
|
|
302
|
-
}
|
|
303
|
-
: {
|
|
304
|
-
kind: OrderKind.BUY,
|
|
305
|
-
buyAmountAfterFee: amount,
|
|
306
|
-
...baseParams,
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return finalParams
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
private async getApiBaseUrl(): Promise<string> {
|
|
313
|
-
const chainId = await this.context.chainId
|
|
314
|
-
const baseUrl = this.API_BASE_URL[chainId]
|
|
315
|
-
|
|
316
|
-
if (!baseUrl) {
|
|
317
|
-
throw new CowError(`Unsupported Network. The ${this.API_NAME} API is not deployed in the Network ` + chainId)
|
|
318
|
-
} else {
|
|
319
|
-
return baseUrl + '/v1'
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private async getProfileApiBaseUrl(): Promise<string> {
|
|
324
|
-
const chainId = await this.context.chainId
|
|
325
|
-
const baseUrl = this.PROFILE_API_BASE_URL[chainId]
|
|
326
|
-
|
|
327
|
-
if (!baseUrl) {
|
|
328
|
-
throw new CowError(`Unsupported Network. The ${this.API_NAME} API is not deployed in the Network ` + chainId)
|
|
329
|
-
} else {
|
|
330
|
-
return baseUrl + '/v1'
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
private async fetch(url: string, method: 'GET' | 'POST' | 'DELETE', data?: any): Promise<Response> {
|
|
335
|
-
const baseUrl = await this.getApiBaseUrl()
|
|
336
|
-
return fetch(baseUrl + url, {
|
|
337
|
-
headers: this.DEFAULT_HEADERS,
|
|
338
|
-
method,
|
|
339
|
-
body: data !== undefined ? JSON.stringify(data) : data,
|
|
340
|
-
})
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
private async fetchProfile(url: string, method: 'GET' | 'POST' | 'DELETE', data?: any): Promise<Response> {
|
|
344
|
-
const baseUrl = await this.getProfileApiBaseUrl()
|
|
345
|
-
return fetch(baseUrl + url, {
|
|
346
|
-
headers: this.DEFAULT_HEADERS,
|
|
347
|
-
method,
|
|
348
|
-
body: data !== undefined ? JSON.stringify(data) : data,
|
|
349
|
-
})
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
private post(url: string, data: any): Promise<Response> {
|
|
353
|
-
return this.fetch(url, 'POST', data)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
private get(url: string): Promise<Response> {
|
|
357
|
-
return this.fetch(url, 'GET')
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
private getProfile(url: string): Promise<Response> {
|
|
361
|
-
return this.fetchProfile(url, 'GET')
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
private delete(url: string, data: any): Promise<Response> {
|
|
365
|
-
return this.fetch(url, 'DELETE', data)
|
|
366
|
-
}
|
|
367
|
-
}
|