@cowprotocol/cow-sdk 0.0.6 → 0.0.8-RC.0
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/.babelrc +4 -0
- package/.github/workflows/build.yml +50 -0
- package/.github/workflows/lint.yml +19 -0
- package/.github/workflows/publish.yml +5 -8
- package/.github/workflows/test.yml +47 -0
- package/README.md +34 -1
- package/babel.config.js +2 -5
- package/dist/CowSdk.d.ts +22 -0
- package/dist/{src/src/api → api}/cow/errors/OperatorError.d.ts +1 -1
- package/dist/{src/src/api → api}/cow/errors/QuoteError.d.ts +1 -1
- package/dist/{src/src/api → api}/cow/index.d.ts +8 -8
- package/dist/{src/src/api → api}/cow/types.d.ts +2 -2
- package/dist/api/index.d.ts +2 -0
- package/dist/api/metadata/index.d.ts +9 -0
- package/dist/api/metadata/types.d.ts +15 -0
- package/dist/{src/appData.schema-d44994e0.js → appData.schema-d44994e0.js} +0 -0
- package/dist/{src/appData.schema-d44994e0.js.map → appData.schema-d44994e0.js.map} +0 -0
- package/dist/{src/appData.schema-fb2df827.js → appData.schema-fb2df827.js} +0 -0
- package/dist/{src/appData.schema-fb2df827.js.map → appData.schema-fb2df827.js.map} +0 -0
- package/dist/{src/src/constants → constants}/chains.d.ts +0 -0
- package/dist/{src/src/constants → constants}/index.d.ts +1 -0
- package/dist/{src/src/constants → constants}/tokens.d.ts +2 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.modern.js +2 -0
- package/dist/index.modern.js.map +1 -0
- package/dist/index.module.js +2 -0
- package/dist/index.module.js.map +1 -0
- package/dist/{src/src/types → types}/index.d.ts +2 -1
- package/dist/utils/appData.d.ts +10 -0
- package/dist/{src/src/utils → utils}/common.d.ts +2 -0
- package/dist/utils/context.d.ts +30 -0
- package/dist/{src/src/utils → utils}/sign.d.ts +1 -1
- package/dist/{src/src/utils → utils}/tokens.d.ts +1 -1
- package/package.json +22 -21
- package/src/CowSdk.ts +39 -17
- package/src/api/cow/errors/OperatorError.ts +5 -3
- package/src/api/cow/errors/QuoteError.ts +4 -3
- package/src/api/cow/index.ts +69 -45
- package/src/api/cow/types.ts +2 -2
- package/src/api/index.ts +1 -0
- package/src/api/metadata/index.ts +37 -0
- package/src/api/metadata/types.ts +17 -0
- package/src/constants/index.ts +2 -0
- package/src/constants/tokens.ts +2 -2
- package/src/index.ts +4 -4
- package/src/types/index.ts +2 -1
- package/src/utils/appData.spec.ts +44 -1
- package/src/utils/appData.ts +28 -2
- package/src/utils/common.ts +8 -0
- package/src/utils/context.ts +61 -14
- package/src/utils/price.ts +1 -1
- package/src/utils/sign.ts +5 -5
- package/src/utils/tokens.ts +2 -2
- package/src/workflows/publish.sh +4 -32
- package/tsconfig.json +5 -6
- package/dist/src/cow-sdk.esm.js +0 -2
- package/dist/src/cow-sdk.esm.js.map +0 -1
- package/dist/src/cow-sdk.js +0 -2
- package/dist/src/cow-sdk.js.map +0 -1
- package/dist/src/cow-sdk.modern.js +0 -2
- package/dist/src/cow-sdk.modern.js.map +0 -1
- package/dist/src/src/CowSdk.d.ts +0 -16
- package/dist/src/src/api/index.d.ts +0 -1
- package/dist/src/src/index.d.ts +0 -4
- package/dist/src/src/utils/appData.d.ts +0 -7
- package/dist/src/src/utils/context.d.ts +0 -24
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import log from 'loglevel'
|
|
2
|
-
import { CowError } from '
|
|
2
|
+
import { CowError, logPrefix } from '../../../utils/common'
|
|
3
3
|
import { ApiErrorCodes, ApiErrorObject } from './OperatorError'
|
|
4
4
|
|
|
5
5
|
export interface GpQuoteErrorObject {
|
|
@@ -76,11 +76,11 @@ export default class GpQuoteError extends CowError {
|
|
|
76
76
|
// shouldn't fall through as this error constructor expects the error code to exist but just in case
|
|
77
77
|
return errorMessage || orderPostError.errorType
|
|
78
78
|
} else {
|
|
79
|
-
log.error('Unknown reason for bad quote fetch', orderPostError)
|
|
79
|
+
log.error(logPrefix, 'Unknown reason for bad quote fetch', orderPostError)
|
|
80
80
|
return orderPostError.description
|
|
81
81
|
}
|
|
82
82
|
} catch (error) {
|
|
83
|
-
log.error('Error handling 400/404 error. Likely a problem deserialising the JSON response')
|
|
83
|
+
log.error(logPrefix, 'Error handling 400/404 error. Likely a problem deserialising the JSON response')
|
|
84
84
|
return GpQuoteError.quoteErrorDetails.UNHANDLED_ERROR
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -94,6 +94,7 @@ export default class GpQuoteError extends CowError {
|
|
|
94
94
|
case 500:
|
|
95
95
|
default:
|
|
96
96
|
log.error(
|
|
97
|
+
logPrefix,
|
|
97
98
|
'[QuoteError::getErrorFromStatusCode] Error fetching quote, status code:',
|
|
98
99
|
response.status || 'unknown'
|
|
99
100
|
)
|
package/src/api/cow/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import log from 'loglevel'
|
|
2
2
|
import fetch from 'cross-fetch'
|
|
3
3
|
import { OrderKind, QuoteQuery } from '@gnosis.pm/gp-v2-contracts'
|
|
4
|
-
import { SupportedChainId as ChainId } from '
|
|
5
|
-
import { getSigningSchemeApiValue, OrderCreation } from '
|
|
4
|
+
import { SupportedChainId as ChainId } from '../../constants/chains'
|
|
5
|
+
import { getSigningSchemeApiValue, OrderCreation } from '../../utils/sign'
|
|
6
6
|
import OperatorError, { ApiErrorCodeDetails, ApiErrorCodes, ApiErrorObject } from './errors/OperatorError'
|
|
7
7
|
import QuoteError, {
|
|
8
8
|
GpQuoteErrorCodes,
|
|
@@ -10,10 +10,10 @@ import QuoteError, {
|
|
|
10
10
|
mapOperatorErrorToQuoteError,
|
|
11
11
|
GpQuoteErrorDetails,
|
|
12
12
|
} from './errors/QuoteError'
|
|
13
|
-
import { toErc20Address } from '
|
|
14
|
-
import { FeeQuoteParams, PriceInformation, PriceQuoteParams, SimpleGetQuoteResponse } from '
|
|
13
|
+
import { toErc20Address } from '../../utils/tokens'
|
|
14
|
+
import { FeeQuoteParams, PriceInformation, PriceQuoteParams, SimpleGetQuoteResponse } from '../../utils/price'
|
|
15
15
|
|
|
16
|
-
import { ZERO_ADDRESS } from '
|
|
16
|
+
import { ZERO_ADDRESS } from '../../constants'
|
|
17
17
|
import {
|
|
18
18
|
GetOrdersParams,
|
|
19
19
|
GetTradesParams,
|
|
@@ -22,9 +22,9 @@ import {
|
|
|
22
22
|
OrderMetaData,
|
|
23
23
|
ProfileData,
|
|
24
24
|
TradeMetaData,
|
|
25
|
-
} from '
|
|
26
|
-
import { CowError, objectToQueryString } from '
|
|
27
|
-
import { Context } from '
|
|
25
|
+
} from './types'
|
|
26
|
+
import { CowError, logPrefix, objectToQueryString } from '../../utils/common'
|
|
27
|
+
import { Context } from '../../utils/context'
|
|
28
28
|
|
|
29
29
|
function getGnosisProtocolUrl(isDev: boolean): Partial<Record<ChainId, string>> {
|
|
30
30
|
if (isDev) {
|
|
@@ -77,7 +77,7 @@ async function _handleQuoteResponse<T = any, P extends QuoteQuery = QuoteQuery>(
|
|
|
77
77
|
|
|
78
78
|
if (params) {
|
|
79
79
|
const { sellToken, buyToken } = params
|
|
80
|
-
log.error(`Error querying fee from API - sellToken: ${sellToken}, buyToken: ${buyToken}`)
|
|
80
|
+
log.error(logPrefix, `Error querying fee from API - sellToken: ${sellToken}, buyToken: ${buyToken}`)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
throw quoteError
|
|
@@ -86,14 +86,12 @@ async function _handleQuoteResponse<T = any, P extends QuoteQuery = QuoteQuery>(
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export class CowApi
|
|
90
|
-
chainId: T
|
|
89
|
+
export class CowApi {
|
|
91
90
|
context: Context
|
|
92
91
|
|
|
93
92
|
API_NAME = 'CoW Protocol'
|
|
94
93
|
|
|
95
|
-
constructor(
|
|
96
|
-
this.chainId = chainId
|
|
94
|
+
constructor(context: Context) {
|
|
97
95
|
this.context = context
|
|
98
96
|
}
|
|
99
97
|
|
|
@@ -110,9 +108,10 @@ export class CowApi<T extends ChainId> {
|
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
async getProfileData(address: string): Promise<ProfileData | null> {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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')
|
|
116
115
|
return null
|
|
117
116
|
}
|
|
118
117
|
|
|
@@ -120,7 +119,7 @@ export class CowApi<T extends ChainId> {
|
|
|
120
119
|
|
|
121
120
|
if (!response.ok) {
|
|
122
121
|
const errorResponse = await response.json()
|
|
123
|
-
log.error(errorResponse)
|
|
122
|
+
log.error(logPrefix, errorResponse)
|
|
124
123
|
throw new CowError(errorResponse?.description)
|
|
125
124
|
} else {
|
|
126
125
|
return response.json()
|
|
@@ -130,7 +129,8 @@ export class CowApi<T extends ChainId> {
|
|
|
130
129
|
async getTrades(params: GetTradesParams): Promise<TradeMetaData[]> {
|
|
131
130
|
const { owner, limit, offset } = params
|
|
132
131
|
const qsParams = objectToQueryString({ owner, limit, offset })
|
|
133
|
-
|
|
132
|
+
const chainId = await this.context.chainId
|
|
133
|
+
log.debug(logPrefix, '[util:operator] Get trades for', chainId, owner, { limit, offset })
|
|
134
134
|
try {
|
|
135
135
|
const response = await this.get(`/trades${qsParams}`)
|
|
136
136
|
|
|
@@ -141,7 +141,7 @@ export class CowApi<T extends ChainId> {
|
|
|
141
141
|
return response.json()
|
|
142
142
|
}
|
|
143
143
|
} catch (error) {
|
|
144
|
-
log.error('Error getting trades:', error)
|
|
144
|
+
log.error(logPrefix, 'Error getting trades:', error)
|
|
145
145
|
throw new CowError('Error getting trades: ' + error)
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -149,7 +149,8 @@ export class CowApi<T extends ChainId> {
|
|
|
149
149
|
async getOrders(params: GetOrdersParams): Promise<OrderMetaData[]> {
|
|
150
150
|
const { owner, limit = 1000, offset = 0 } = params
|
|
151
151
|
const queryString = objectToQueryString({ limit, offset })
|
|
152
|
-
|
|
152
|
+
const chainId = await this.context.chainId
|
|
153
|
+
log.debug(logPrefix, `[api:${this.API_NAME}] Get orders for `, chainId, owner, limit, offset)
|
|
153
154
|
|
|
154
155
|
try {
|
|
155
156
|
const response = await this.get(`/account/${owner}/orders/${queryString}`)
|
|
@@ -161,13 +162,34 @@ export class CowApi<T extends ChainId> {
|
|
|
161
162
|
return response.json()
|
|
162
163
|
}
|
|
163
164
|
} catch (error) {
|
|
164
|
-
log.error('Error getting orders information:', 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
|
|
165
186
|
throw new OperatorError(UNHANDLED_ORDER_ERROR)
|
|
166
187
|
}
|
|
167
188
|
}
|
|
168
189
|
|
|
169
190
|
async getOrder(orderId: string): Promise<OrderMetaData | null> {
|
|
170
|
-
|
|
191
|
+
const chainId = await this.context.chainId
|
|
192
|
+
log.debug(logPrefix, `[api:${this.API_NAME}] Get order for `, chainId, orderId)
|
|
171
193
|
try {
|
|
172
194
|
const response = await this.get(`/orders/${orderId}`)
|
|
173
195
|
|
|
@@ -178,22 +200,20 @@ export class CowApi<T extends ChainId> {
|
|
|
178
200
|
return response.json()
|
|
179
201
|
}
|
|
180
202
|
} catch (error) {
|
|
181
|
-
log.error('Error getting order information:', error)
|
|
203
|
+
log.error(logPrefix, 'Error getting order information:', error)
|
|
182
204
|
throw new OperatorError(UNHANDLED_ORDER_ERROR)
|
|
183
205
|
}
|
|
184
206
|
}
|
|
185
207
|
|
|
186
208
|
async getPriceQuoteLegacy(params: PriceQuoteParams): Promise<PriceInformation | null> {
|
|
187
209
|
const { baseToken, quoteToken, amount, kind } = params
|
|
188
|
-
|
|
210
|
+
const chainId = await this.context.chainId
|
|
211
|
+
log.debug(logPrefix, `[api:${this.API_NAME}] Get price from API`, params, 'for', chainId)
|
|
189
212
|
|
|
190
213
|
const response = await this.get(
|
|
191
|
-
`/markets/${toErc20Address(baseToken,
|
|
192
|
-
quoteToken,
|
|
193
|
-
this.chainId
|
|
194
|
-
)}/${kind}/${amount}`
|
|
214
|
+
`/markets/${toErc20Address(baseToken, chainId)}-${toErc20Address(quoteToken, chainId)}/${kind}/${amount}`
|
|
195
215
|
).catch((error) => {
|
|
196
|
-
log.error('Error getting price quote:', error)
|
|
216
|
+
log.error(logPrefix, 'Error getting price quote:', error)
|
|
197
217
|
throw new QuoteError(UNHANDLED_QUOTE_ERROR)
|
|
198
218
|
})
|
|
199
219
|
|
|
@@ -201,7 +221,8 @@ export class CowApi<T extends ChainId> {
|
|
|
201
221
|
}
|
|
202
222
|
|
|
203
223
|
async getQuote(params: FeeQuoteParams): Promise<SimpleGetQuoteResponse> {
|
|
204
|
-
const
|
|
224
|
+
const chainId = await this.context.chainId
|
|
225
|
+
const quoteParams = this.mapNewToLegacyParams(params, chainId)
|
|
205
226
|
const response = await this.post('/quote', quoteParams)
|
|
206
227
|
|
|
207
228
|
return _handleQuoteResponse<SimpleGetQuoteResponse>(response)
|
|
@@ -209,8 +230,8 @@ export class CowApi<T extends ChainId> {
|
|
|
209
230
|
|
|
210
231
|
async sendSignedOrderCancellation(params: OrderCancellationParams): Promise<void> {
|
|
211
232
|
const { cancellation, owner: from } = params
|
|
212
|
-
|
|
213
|
-
log.debug(`[api:${this.API_NAME}] Delete signed order for network`,
|
|
233
|
+
const chainId = await this.context.chainId
|
|
234
|
+
log.debug(logPrefix, `[api:${this.API_NAME}] Delete signed order for network`, chainId, cancellation)
|
|
214
235
|
|
|
215
236
|
const response = await this.delete(`/orders/${cancellation.orderUid}`, {
|
|
216
237
|
signature: cancellation.signature,
|
|
@@ -224,13 +245,14 @@ export class CowApi<T extends ChainId> {
|
|
|
224
245
|
throw new CowError(errorMessage)
|
|
225
246
|
}
|
|
226
247
|
|
|
227
|
-
log.debug(`[api:${this.API_NAME}] Cancelled order`, cancellation.orderUid,
|
|
248
|
+
log.debug(logPrefix, `[api:${this.API_NAME}] Cancelled order`, cancellation.orderUid, chainId)
|
|
228
249
|
}
|
|
229
250
|
|
|
230
251
|
async sendOrder(params: { order: Omit<OrderCreation, 'appData'>; owner: string }): Promise<OrderID> {
|
|
231
252
|
const fullOrder: OrderCreation = { ...params.order, appData: this.context.appDataHash }
|
|
253
|
+
const chainId = await this.context.chainId
|
|
232
254
|
const { owner } = params
|
|
233
|
-
log.debug(`[api:${this.API_NAME}] Post signed order for network`,
|
|
255
|
+
log.debug(logPrefix, `[api:${this.API_NAME}] Post signed order for network`, chainId, fullOrder)
|
|
234
256
|
|
|
235
257
|
// Call API
|
|
236
258
|
const response = await this.post(`/orders`, {
|
|
@@ -247,7 +269,7 @@ export class CowApi<T extends ChainId> {
|
|
|
247
269
|
}
|
|
248
270
|
|
|
249
271
|
const uid = (await response.json()) as string
|
|
250
|
-
log.debug(`[api:${this.API_NAME}] Success posting the signed order`, uid)
|
|
272
|
+
log.debug(logPrefix, `[api:${this.API_NAME}] Success posting the signed order`, uid)
|
|
251
273
|
return uid
|
|
252
274
|
}
|
|
253
275
|
|
|
@@ -287,28 +309,30 @@ export class CowApi<T extends ChainId> {
|
|
|
287
309
|
return finalParams
|
|
288
310
|
}
|
|
289
311
|
|
|
290
|
-
private getApiBaseUrl(): string {
|
|
291
|
-
const
|
|
312
|
+
private async getApiBaseUrl(): Promise<string> {
|
|
313
|
+
const chainId = await this.context.chainId
|
|
314
|
+
const baseUrl = this.API_BASE_URL[chainId]
|
|
292
315
|
|
|
293
316
|
if (!baseUrl) {
|
|
294
|
-
throw new CowError(`Unsupported Network. The ${this.API_NAME} API is not deployed in the Network ` +
|
|
317
|
+
throw new CowError(`Unsupported Network. The ${this.API_NAME} API is not deployed in the Network ` + chainId)
|
|
295
318
|
} else {
|
|
296
319
|
return baseUrl + '/v1'
|
|
297
320
|
}
|
|
298
321
|
}
|
|
299
322
|
|
|
300
|
-
private getProfileApiBaseUrl(): string {
|
|
301
|
-
const
|
|
323
|
+
private async getProfileApiBaseUrl(): Promise<string> {
|
|
324
|
+
const chainId = await this.context.chainId
|
|
325
|
+
const baseUrl = this.PROFILE_API_BASE_URL[chainId]
|
|
302
326
|
|
|
303
327
|
if (!baseUrl) {
|
|
304
|
-
throw new CowError(`Unsupported Network. The ${this.API_NAME} API is not deployed in the Network ` +
|
|
328
|
+
throw new CowError(`Unsupported Network. The ${this.API_NAME} API is not deployed in the Network ` + chainId)
|
|
305
329
|
} else {
|
|
306
330
|
return baseUrl + '/v1'
|
|
307
331
|
}
|
|
308
332
|
}
|
|
309
333
|
|
|
310
|
-
private fetch(url: string, method: 'GET' | 'POST' | 'DELETE', data?: any): Promise<Response> {
|
|
311
|
-
const baseUrl = this.getApiBaseUrl()
|
|
334
|
+
private async fetch(url: string, method: 'GET' | 'POST' | 'DELETE', data?: any): Promise<Response> {
|
|
335
|
+
const baseUrl = await this.getApiBaseUrl()
|
|
312
336
|
return fetch(baseUrl + url, {
|
|
313
337
|
headers: this.DEFAULT_HEADERS,
|
|
314
338
|
method,
|
|
@@ -316,8 +340,8 @@ export class CowApi<T extends ChainId> {
|
|
|
316
340
|
})
|
|
317
341
|
}
|
|
318
342
|
|
|
319
|
-
private fetchProfile(url: string, method: 'GET' | 'POST' | 'DELETE', data?: any): Promise<Response> {
|
|
320
|
-
const baseUrl = this.getProfileApiBaseUrl()
|
|
343
|
+
private async fetchProfile(url: string, method: 'GET' | 'POST' | 'DELETE', data?: any): Promise<Response> {
|
|
344
|
+
const baseUrl = await this.getProfileApiBaseUrl()
|
|
321
345
|
return fetch(baseUrl + url, {
|
|
322
346
|
headers: this.DEFAULT_HEADERS,
|
|
323
347
|
method,
|
package/src/api/cow/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OrderKind } from '@gnosis.pm/gp-v2-contracts'
|
|
2
|
-
import { SupportedChainId as ChainId } from '
|
|
3
|
-
import { OrderCancellation, SigningSchemeValue } from '
|
|
2
|
+
import { SupportedChainId as ChainId } from '../../constants/chains'
|
|
3
|
+
import { OrderCancellation, SigningSchemeValue } from '../../utils/sign'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Unique identifier for the order, calculated by keccak256(orderDigest, ownerAddress, validTo),
|
package/src/api/index.ts
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import log from 'loglevel'
|
|
2
|
+
import { Context } from '../../utils/context'
|
|
3
|
+
import { getSerializedCID, loadIpfsFromCid } from '../../utils/appData'
|
|
4
|
+
import { AppDataDoc } from './types'
|
|
5
|
+
import { CowError } from '../../utils/common'
|
|
6
|
+
|
|
7
|
+
export class MetadataApi {
|
|
8
|
+
context: Context
|
|
9
|
+
|
|
10
|
+
constructor(context: Context) {
|
|
11
|
+
this.context = context
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async decodeAppData(hash: string): Promise<void | AppDataDoc> {
|
|
15
|
+
try {
|
|
16
|
+
const cidV0 = await getSerializedCID(hash)
|
|
17
|
+
if (!cidV0) throw new CowError('Error getting serialized CID')
|
|
18
|
+
return await loadIpfsFromCid(cidV0)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
log.error('Error decoding AppData:', error)
|
|
21
|
+
throw new CowError('Error decoding AppData: ' + error)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async cidToAppDataHex(ipfsHash: string): Promise<string | void> {
|
|
26
|
+
const { CID } = await import('multiformats/cid')
|
|
27
|
+
|
|
28
|
+
const { digest } = CID.parse(ipfsHash).multihash
|
|
29
|
+
return `0x${Buffer.from(digest).toString('hex')}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async appDataHexToCid(hash: string): Promise<string | void> {
|
|
33
|
+
const cidV0 = await getSerializedCID(hash)
|
|
34
|
+
if (!cidV0) throw new CowError('Error getting serialized CID')
|
|
35
|
+
return cidV0
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Metadata {
|
|
2
|
+
version: string
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface ReferralMetadata extends Metadata {
|
|
6
|
+
address: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type MetadataDoc = {
|
|
10
|
+
referrer?: ReferralMetadata
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AppDataDoc = {
|
|
14
|
+
version: string
|
|
15
|
+
appCode?: string
|
|
16
|
+
metadata: MetadataDoc
|
|
17
|
+
}
|
package/src/constants/index.ts
CHANGED
|
@@ -12,3 +12,5 @@ export const GP_SETTLEMENT_CONTRACT_ADDRESS: Partial<Record<number, string>> = {
|
|
|
12
12
|
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
|
13
13
|
|
|
14
14
|
export const DEFAULT_APP_DATA_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_IPFS_GATEWAY_URI = 'https://gnosis.mypinata.cloud/ipfs'
|
package/src/constants/tokens.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { CowError } from '
|
|
2
|
-
export { ALL_SUPPORTED_CHAIN_IDS } from '
|
|
3
|
-
export * from '
|
|
4
|
-
export
|
|
1
|
+
export { CowError } from './utils/common'
|
|
2
|
+
export { ALL_SUPPORTED_CHAIN_IDS } from './constants/chains'
|
|
3
|
+
export * from './types'
|
|
4
|
+
export { CowSdk } from './CowSdk'
|
package/src/types/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { validateAppDataDocument } from './appData'
|
|
1
|
+
import { validateAppDataDocument, getSerializedCID, loadIpfsFromCid } from './appData'
|
|
2
2
|
|
|
3
3
|
const VALID_RESULT = {
|
|
4
4
|
result: true,
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
const INVALID_CID_LENGTH = 'Incorrect length'
|
|
8
|
+
|
|
7
9
|
test('Valid minimal document', async () => {
|
|
8
10
|
const validation = await validateAppDataDocument({
|
|
9
11
|
version: '0.1.0',
|
|
@@ -64,3 +66,44 @@ test('Invalid: No metadata', async () => {
|
|
|
64
66
|
})
|
|
65
67
|
expect(validation.result).toBeFalsy()
|
|
66
68
|
})
|
|
69
|
+
|
|
70
|
+
test('Invalid: No metadata', async () => {
|
|
71
|
+
const validation = await validateAppDataDocument({
|
|
72
|
+
version: '0.1.0',
|
|
73
|
+
appCode: 'MyApp',
|
|
74
|
+
})
|
|
75
|
+
expect(validation.result).toBeFalsy()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('Invalid: No metadata', async () => {
|
|
79
|
+
const validation = await validateAppDataDocument({
|
|
80
|
+
version: '0.1.0',
|
|
81
|
+
appCode: 'MyApp',
|
|
82
|
+
})
|
|
83
|
+
expect(validation.result).toBeFalsy()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('Valid serialized appData CID', async () => {
|
|
87
|
+
const hash = '0xa6c81f4ca727252a05b108f1742a07430f28d474d2a3492d8f325746824d22e5'
|
|
88
|
+
const serializedCidV0 = 'QmZZhNnqMF1gRywNKnTPuZksX7rVjQgTT3TJAZ7R6VE3b2'
|
|
89
|
+
const cidV0 = await getSerializedCID(hash)
|
|
90
|
+
|
|
91
|
+
expect(cidV0).toEqual(serializedCidV0)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('Invalid: serialized appData CID format ', async () => {
|
|
95
|
+
const invalidHash = '0xa6c81f4ca727252a05b108f1742'
|
|
96
|
+
try {
|
|
97
|
+
await getSerializedCID(invalidHash)
|
|
98
|
+
} catch (e: any) {
|
|
99
|
+
expect(e.message).toEqual(INVALID_CID_LENGTH)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('Valid IPFS appData from CID', async () => {
|
|
104
|
+
const validSerializedCidV0 = 'QmZZhNnqMF1gRywNKnTPuZksX7rVjQgTT3TJAZ7R6VE3b2'
|
|
105
|
+
const appDataDocument = await loadIpfsFromCid(validSerializedCidV0)
|
|
106
|
+
const validation = await validateAppDataDocument(appDataDocument)
|
|
107
|
+
|
|
108
|
+
expect(validation).toEqual(VALID_RESULT)
|
|
109
|
+
})
|
package/src/utils/appData.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import Ajv, { ErrorObject, ValidateFunction } from 'ajv'
|
|
2
|
+
import { fromHexString } from './common'
|
|
3
|
+
import { DEFAULT_IPFS_GATEWAY_URI } from '../constants'
|
|
4
|
+
import { AppDataDoc } from '../types'
|
|
2
5
|
|
|
3
6
|
let validate: ValidateFunction | undefined
|
|
4
7
|
let ajv: Ajv
|
|
@@ -21,9 +24,32 @@ async function getValidator(): Promise<{ ajv: Ajv; validate: ValidateFunction }>
|
|
|
21
24
|
return { ajv, validate }
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
export async function
|
|
27
|
+
export async function getSerializedCID(hash: string): Promise<void | string> {
|
|
28
|
+
const cidVersion = 0x1 //cidv1
|
|
29
|
+
const codec = 0x70 //dag-pb
|
|
30
|
+
const type = 0x12 //sha2-256
|
|
31
|
+
const length = 32 //256 bits
|
|
32
|
+
const _hash = hash.replace(/(^0x)/, '')
|
|
33
|
+
|
|
34
|
+
const hexHash = fromHexString(_hash)
|
|
35
|
+
|
|
36
|
+
if (!hexHash) return
|
|
37
|
+
|
|
38
|
+
const uint8array = Uint8Array.from([cidVersion, codec, type, length, ...hexHash])
|
|
39
|
+
const { CID } = await import('multiformats/cid')
|
|
40
|
+
return CID.decode(uint8array).toV0().toString()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function loadIpfsFromCid(cid: string, ipfsUri = DEFAULT_IPFS_GATEWAY_URI): Promise<AppDataDoc> {
|
|
44
|
+
const { default: fetch } = await import('cross-fetch')
|
|
45
|
+
const response = await fetch(`${ipfsUri}/${cid}`)
|
|
46
|
+
|
|
47
|
+
return await response.json()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function validateAppDataDocument(appDataDocument: unknown): Promise<ValidationResult> {
|
|
25
51
|
const { ajv, validate } = await getValidator()
|
|
26
|
-
const result = !!
|
|
52
|
+
const result = !!validate(appDataDocument)
|
|
27
53
|
|
|
28
54
|
return {
|
|
29
55
|
result,
|
package/src/utils/common.ts
CHANGED
|
@@ -25,3 +25,11 @@ export function objectToQueryString(o: any): string {
|
|
|
25
25
|
|
|
26
26
|
return qsResult ? `?${qsResult}` : ''
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
export const logPrefix = 'cow-sdk:'
|
|
30
|
+
|
|
31
|
+
export function fromHexString(hexString: string) {
|
|
32
|
+
const stringMatch = hexString.match(/.{1,2}/g)
|
|
33
|
+
if (!stringMatch) return
|
|
34
|
+
return new Uint8Array(stringMatch.map((byte) => parseInt(byte, 16)))
|
|
35
|
+
}
|
package/src/utils/context.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { Signer } from 'ethers'
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import log from 'loglevel'
|
|
3
|
+
import { CowError, logPrefix } from './common'
|
|
4
|
+
import { SupportedChainId as ChainId } from '../constants/chains'
|
|
5
|
+
import { DEFAULT_APP_DATA_HASH, DEFAULT_IPFS_GATEWAY_URI } from '../constants'
|
|
4
6
|
|
|
5
7
|
export interface CowContext {
|
|
6
8
|
appDataHash?: string
|
|
7
9
|
isDevEnvironment?: boolean
|
|
8
10
|
signer?: Signer
|
|
11
|
+
ipfsUri?: string
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
export const DefaultCowContext = {
|
|
14
|
+
export const DefaultCowContext = {
|
|
15
|
+
appDataHash: DEFAULT_APP_DATA_HASH,
|
|
16
|
+
isDevEnvironment: false,
|
|
17
|
+
ipfsUri: DEFAULT_IPFS_GATEWAY_URI,
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
/**
|
|
14
21
|
*
|
|
@@ -17,26 +24,66 @@ export const DefaultCowContext = { appDataHash: DEFAULT_APP_DATA_HASH, isDevEnvi
|
|
|
17
24
|
* @class Context
|
|
18
25
|
* @implements {Required<CowContext>}
|
|
19
26
|
*/
|
|
20
|
-
export class Context implements
|
|
21
|
-
|
|
27
|
+
export class Context implements Partial<CowContext> {
|
|
28
|
+
#context: CowContext
|
|
29
|
+
#chainId: ChainId
|
|
30
|
+
|
|
31
|
+
constructor(chainId: ChainId, context: CowContext) {
|
|
32
|
+
this.#chainId = this.updateChainId(chainId)
|
|
33
|
+
this.#context = { ...DefaultCowContext, ...context }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
updateChainId(chainId: ChainId) {
|
|
37
|
+
if (!ChainId[chainId]) {
|
|
38
|
+
throw new CowError(`Invalid chainId: ${chainId}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
log.debug(logPrefix, `Updating chainId to: ${chainId}`)
|
|
42
|
+
|
|
43
|
+
this.#chainId = chainId
|
|
44
|
+
return chainId
|
|
45
|
+
}
|
|
22
46
|
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
get chainId(): Promise<ChainId> {
|
|
48
|
+
const provider = this.#context.signer?.provider
|
|
49
|
+
if (!provider) {
|
|
50
|
+
return Promise.resolve(this.#chainId)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
log.debug(logPrefix, 'Getting chainId from provider')
|
|
54
|
+
|
|
55
|
+
const getAndReconciliateNetwork = async () => {
|
|
56
|
+
const network = await provider.getNetwork()
|
|
57
|
+
const chainId = network.chainId
|
|
58
|
+
|
|
59
|
+
if (chainId !== this.#chainId) {
|
|
60
|
+
log.debug(
|
|
61
|
+
logPrefix,
|
|
62
|
+
`ChainId mismatch: Provider's chainId: ${chainId} vs Context's chainId: ${
|
|
63
|
+
this.#chainId
|
|
64
|
+
}. Updating Context's chainId`
|
|
65
|
+
)
|
|
66
|
+
this.updateChainId(chainId)
|
|
67
|
+
}
|
|
68
|
+
return chainId
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return getAndReconciliateNetwork()
|
|
25
72
|
}
|
|
26
73
|
|
|
27
74
|
get appDataHash(): string {
|
|
28
|
-
return this
|
|
75
|
+
return this.#context.appDataHash ?? DefaultCowContext.appDataHash
|
|
29
76
|
}
|
|
30
77
|
|
|
31
78
|
get isDevEnvironment(): boolean {
|
|
32
|
-
return this
|
|
79
|
+
return this.#context.isDevEnvironment ?? DefaultCowContext.isDevEnvironment
|
|
33
80
|
}
|
|
34
81
|
|
|
35
|
-
get signer(): Signer {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
82
|
+
get signer(): Signer | undefined {
|
|
83
|
+
return this.#context.signer
|
|
84
|
+
}
|
|
39
85
|
|
|
40
|
-
|
|
86
|
+
get ipfsUri(): string {
|
|
87
|
+
return this.#context.ipfsUri ?? DefaultCowContext.ipfsUri
|
|
41
88
|
}
|
|
42
89
|
}
|
package/src/utils/price.ts
CHANGED
package/src/utils/sign.ts
CHANGED
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
} from '@gnosis.pm/gp-v2-contracts'
|
|
13
13
|
import log from 'loglevel'
|
|
14
14
|
|
|
15
|
-
import { SupportedChainId as ChainId } from '
|
|
16
|
-
import { GP_SETTLEMENT_CONTRACT_ADDRESS } from '
|
|
15
|
+
import { SupportedChainId as ChainId } from '../constants/chains'
|
|
16
|
+
import { GP_SETTLEMENT_CONTRACT_ADDRESS } from '../constants'
|
|
17
17
|
import { TypedDataDomain, Signer } from '@ethersproject/abstract-signer'
|
|
18
|
-
import { CowError } from './common'
|
|
18
|
+
import { CowError, logPrefix } from './common'
|
|
19
19
|
|
|
20
20
|
// For error codes, see:
|
|
21
21
|
// - https://eth.wiki/json-rpc/json-rpc-error-codes-improvement-proposal
|
|
@@ -151,7 +151,7 @@ async function _signPayload(
|
|
|
151
151
|
_signer = signer
|
|
152
152
|
}
|
|
153
153
|
} catch (e) {
|
|
154
|
-
log.error('Wallet not supported:', e)
|
|
154
|
+
log.error(logPrefix, 'Wallet not supported:', e)
|
|
155
155
|
throw new CowError('Wallet not supported')
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -160,7 +160,7 @@ async function _signPayload(
|
|
|
160
160
|
} catch (e) {
|
|
161
161
|
if (!isProviderRpcError(e)) {
|
|
162
162
|
// Some other error signing. Let it bubble up.
|
|
163
|
-
log.error(e)
|
|
163
|
+
log.error(logPrefix, e)
|
|
164
164
|
throw e
|
|
165
165
|
}
|
|
166
166
|
|