From 8843badb24376494be86cfd3a72f691c46fb2c66 Mon Sep 17 00:00:00 2001
From: Mati Dastugue
Date: Tue, 19 Apr 2022 16:42:22 -0300
Subject: [PATCH 1/6] Add metadata api methods to create appDataDoc and upload
it to IPFS
---
src/api/metadata/index.ts | 24 +++++++++++++++++++++++-
src/utils/context.ts | 18 ++++++++++++++----
src/utils/ipfs.ts | 37 +++++++++++++++++++++++++++++++++++++
3 files changed, 74 insertions(+), 5 deletions(-)
create mode 100644 src/utils/ipfs.ts
diff --git a/src/api/metadata/index.ts b/src/api/metadata/index.ts
index b58f0753..9d03c6a7 100644
--- a/src/api/metadata/index.ts
+++ b/src/api/metadata/index.ts
@@ -1,9 +1,12 @@
import log from 'loglevel'
import { Context } from '../../utils/context'
import { getSerializedCID, loadIpfsFromCid } from '../../utils/appData'
-import { AppDataDoc } from './types'
+import { pinJSONToIPFS } from '../../utils/ipfs'
+import { AppDataDoc, MetadataDoc } from './types'
import { CowError } from '../../utils/common'
+const DEFAULT_APP_CODE = 'CowSwap'
+
export class MetadataApi {
context: Context
@@ -11,6 +14,16 @@ export class MetadataApi {
this.context = context
}
+ generateAppDataDoc(metadata: MetadataDoc = {}): AppDataDoc {
+ return {
+ version: '0.1.0',
+ appCode: DEFAULT_APP_CODE,
+ metadata: {
+ ...metadata,
+ },
+ }
+ }
+
async decodeAppData(hash: string): Promise {
try {
const cidV0 = await getSerializedCID(hash)
@@ -34,4 +47,13 @@ export class MetadataApi {
if (!cidV0) throw new CowError('Error getting serialized CID')
return cidV0
}
+
+ async uploadMetadataDocToIpfs(appDataDoc: AppDataDoc): Promise {
+ const { apiKey, apiSecret } = this.context.ipfs
+ if (!apiKey || !apiSecret) {
+ throw new CowError('You need to pass IPFS api credentials.')
+ }
+ const { IpfsHash } = await pinJSONToIPFS(appDataDoc, this.context.ipfs)
+ return this.cidToAppDataHex(IpfsHash)
+ }
}
diff --git a/src/utils/context.ts b/src/utils/context.ts
index dc03bdb7..5712ac20 100644
--- a/src/utils/context.ts
+++ b/src/utils/context.ts
@@ -4,17 +4,27 @@ import { CowError, logPrefix } from './common'
import { SupportedChainId as ChainId } from '../constants/chains'
import { DEFAULT_APP_DATA_HASH, DEFAULT_IPFS_GATEWAY_URI } from '../constants'
+export interface Ipfs {
+ uri: string
+ apiKey?: string
+ apiSecret?: string
+}
+
export interface CowContext {
appDataHash?: string
isDevEnvironment?: boolean
signer?: Signer
- ipfsUri?: string
+ ipfs?: Ipfs
}
export const DefaultCowContext = {
appDataHash: DEFAULT_APP_DATA_HASH,
isDevEnvironment: false,
- ipfsUri: DEFAULT_IPFS_GATEWAY_URI,
+ ipfs: {
+ uri: DEFAULT_IPFS_GATEWAY_URI,
+ apiKey: undefined,
+ apiSecret: undefined,
+ },
}
/**
@@ -83,7 +93,7 @@ export class Context implements Partial {
return this.#context.signer
}
- get ipfsUri(): string {
- return this.#context.ipfsUri ?? DefaultCowContext.ipfsUri
+ get ipfs(): Ipfs {
+ return this.#context.ipfs ?? DefaultCowContext.ipfs
}
}
diff --git a/src/utils/ipfs.ts b/src/utils/ipfs.ts
new file mode 100644
index 00000000..8629b938
--- /dev/null
+++ b/src/utils/ipfs.ts
@@ -0,0 +1,37 @@
+import { Ipfs } from './context'
+
+type PinataPinResponse = {
+ IpfsHash: string
+ PinSize: number
+ Timestamp: string
+}
+
+export async function pinJSONToIPFS(file: any, { uri, apiKey = '', apiSecret = '' }: Ipfs): Promise {
+ const body = JSON.stringify({
+ pinataContent: file,
+ pinataMetadata: { name: 'appData-affiliate' },
+ })
+
+ const pinataUrl = `${uri}/pinning/pinJSONToIPFS`
+
+ const headers = new Headers({
+ 'Content-Type': 'application/json',
+ pinata_api_key: apiKey,
+ pinata_secret_api_key: apiSecret,
+ })
+
+ const request = new Request(pinataUrl, {
+ method: 'POST',
+ headers,
+ body,
+ })
+
+ const response = await fetch(request)
+ const data = await response.json()
+
+ if (response.status !== 200) {
+ throw new Error(data.error.details || data.error)
+ }
+
+ return data
+}
From e70dfa663a70e09c21e4ecb94f681a2d54eff503 Mon Sep 17 00:00:00 2001
From: Mati Dastugue
Date: Thu, 21 Apr 2022 18:47:02 -0300
Subject: [PATCH 2/6] Add unit tests for Metadata API methods
---
src/api/metadata/index.ts | 7 +-
src/api/metadata/metadata.spec.ts | 125 ++++++++++++++++++++++++++++++
src/utils/context.ts | 2 +-
3 files changed, 130 insertions(+), 4 deletions(-)
create mode 100644 src/api/metadata/metadata.spec.ts
diff --git a/src/api/metadata/index.ts b/src/api/metadata/index.ts
index 9d03c6a7..82ae51d9 100644
--- a/src/api/metadata/index.ts
+++ b/src/api/metadata/index.ts
@@ -28,10 +28,11 @@ export class MetadataApi {
try {
const cidV0 = await getSerializedCID(hash)
if (!cidV0) throw new CowError('Error getting serialized CID')
- return await loadIpfsFromCid(cidV0)
- } catch (error) {
+ return loadIpfsFromCid(cidV0)
+ } catch (e) {
+ const error = e as Error
log.error('Error decoding AppData:', error)
- throw new CowError('Error decoding AppData: ' + error)
+ throw new CowError('Error decoding AppData: ' + error.message)
}
}
diff --git a/src/api/metadata/metadata.spec.ts b/src/api/metadata/metadata.spec.ts
new file mode 100644
index 00000000..24235fb2
--- /dev/null
+++ b/src/api/metadata/metadata.spec.ts
@@ -0,0 +1,125 @@
+import fetchMock, { enableFetchMocks } from 'jest-fetch-mock'
+import { CowSdk } from '../../CowSdk'
+import { CowError } from '../../utils/common'
+
+enableFetchMocks()
+
+const chainId = 4 //Rinkeby
+
+const cowSdk = new CowSdk(chainId)
+
+const HTTP_STATUS_OK = 200
+const HTTP_STATUS_INTERNAL_ERROR = 500
+
+const DEFAULT_APP_DATA_DOC = {
+ version: '0.1.0',
+ appCode: 'CowSwap',
+ metadata: {},
+}
+
+const CUSTOM_APP_DATA_DOC = {
+ ...DEFAULT_APP_DATA_DOC,
+ metadata: {
+ referrer: {
+ address: '0x1f5B740436Fc5935622e92aa3b46818906F416E9',
+ version: '0.1.0',
+ },
+ },
+}
+
+const IPFS_HASH = 'QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs'
+
+const APP_DATA_HEX = '0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80'
+
+beforeEach(() => {
+ fetchMock.resetMocks()
+})
+
+afterEach(() => {
+ jest.restoreAllMocks()
+})
+
+test('Valid: Create appDataDoc with empty metadata ', () => {
+ const appDataDoc = cowSdk.metadataApi.generateAppDataDoc({})
+ expect(appDataDoc.version).toEqual(DEFAULT_APP_DATA_DOC.version)
+ expect(appDataDoc.appCode).toEqual(DEFAULT_APP_DATA_DOC.appCode)
+ expect(appDataDoc.metadata).toEqual(DEFAULT_APP_DATA_DOC.metadata)
+})
+
+test('Valid: Create appDataDoc with custom metadata ', () => {
+ const appDataDoc = cowSdk.metadataApi.generateAppDataDoc(CUSTOM_APP_DATA_DOC.metadata)
+ expect(appDataDoc.metadata.referrer?.address).toEqual(CUSTOM_APP_DATA_DOC.metadata.referrer.address)
+ expect(appDataDoc.metadata.referrer?.version).toEqual(CUSTOM_APP_DATA_DOC.metadata.referrer.version)
+})
+
+test('Invalid: Upload to IPFS without passing credentials', async () => {
+ const appDataDoc = cowSdk.metadataApi.generateAppDataDoc(CUSTOM_APP_DATA_DOC.metadata)
+ try {
+ await cowSdk.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
+ } catch (e) {
+ const error = e as CowError
+ expect(error.message).toEqual('You need to pass IPFS api credentials.')
+ }
+})
+
+test('Valid: Upload AppDataDoc to IPFS', async () => {
+ fetchMock.mockResponseOnce(JSON.stringify({ IpfsHash: IPFS_HASH }), { status: HTTP_STATUS_OK })
+ const appDataDoc = cowSdk.metadataApi.generateAppDataDoc(CUSTOM_APP_DATA_DOC.metadata)
+ const cowSdk1 = new CowSdk(chainId, { ipfs: { apiKey: 'validApiKey', apiSecret: 'ValidApiSecret' } })
+ const appDataHex = await cowSdk1.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
+ expect(fetchMock).toHaveBeenCalledTimes(1)
+ expect(appDataHex).toEqual(APP_DATA_HEX)
+})
+
+test('Invalid: Upload AppDataDoc to IPFS with wrong credentials', async () => {
+ fetchMock.mockResponseOnce(JSON.stringify({ error: { details: 'IPFS api keys are invalid' } }), {
+ status: HTTP_STATUS_INTERNAL_ERROR,
+ })
+ const appDataDoc = cowSdk.metadataApi.generateAppDataDoc({})
+ const cowSdk1 = new CowSdk(chainId, { ipfs: { apiKey: 'InvalidApiKey', apiSecret: 'InvValidApiSecret' } })
+ try {
+ await cowSdk1.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
+ } catch (e) {
+ const error = e as Error
+ expect(fetchMock).toHaveBeenCalledTimes(1)
+ expect(error.message).toEqual('IPFS api keys are invalid')
+ }
+})
+
+test('Valid: Decode appData ', async () => {
+ fetchMock.mockResponseOnce(JSON.stringify(CUSTOM_APP_DATA_DOC), { status: HTTP_STATUS_OK })
+ const appDataDoc = await cowSdk.metadataApi.decodeAppData(APP_DATA_HEX)
+
+ expect(fetchMock).toHaveBeenCalledTimes(1)
+ expect(fetchMock).toHaveBeenCalledWith(
+ 'https://gnosis.mypinata.cloud/ipfs/QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs'
+ )
+ expect(appDataDoc?.version).toEqual(CUSTOM_APP_DATA_DOC.version)
+ expect(appDataDoc?.appCode).toEqual(CUSTOM_APP_DATA_DOC.appCode)
+ expect(appDataDoc?.metadata.referrer?.address).toEqual(CUSTOM_APP_DATA_DOC.metadata.referrer.address)
+ expect(appDataDoc?.metadata.referrer?.version).toEqual(CUSTOM_APP_DATA_DOC.metadata.referrer.version)
+})
+
+test('Invalid: Decode appData with wrong hash format', async () => {
+ fetchMock.mockResponseOnce(JSON.stringify({}), { status: HTTP_STATUS_INTERNAL_ERROR })
+ try {
+ await cowSdk.metadataApi.decodeAppData('invalidHash')
+ } catch (e) {
+ const error = e as CowError
+ expect(error.message).toEqual('Error decoding AppData: Incorrect length')
+ }
+})
+
+test('Valid: AppData to CID ', async () => {
+ const decodedAppDataHex = await cowSdk.metadataApi.appDataHexToCid(APP_DATA_HEX)
+ expect(decodedAppDataHex).toEqual(IPFS_HASH)
+})
+
+test('Invalid: AppData to CID with wrong format ', async () => {
+ try {
+ await cowSdk.metadataApi.appDataHexToCid('invalidHash')
+ } catch (e) {
+ const error = e as CowError
+ expect(error.message).toEqual('Incorrect length')
+ }
+})
diff --git a/src/utils/context.ts b/src/utils/context.ts
index 5712ac20..dc51cf11 100644
--- a/src/utils/context.ts
+++ b/src/utils/context.ts
@@ -5,7 +5,7 @@ import { SupportedChainId as ChainId } from '../constants/chains'
import { DEFAULT_APP_DATA_HASH, DEFAULT_IPFS_GATEWAY_URI } from '../constants'
export interface Ipfs {
- uri: string
+ uri?: string
apiKey?: string
apiSecret?: string
}
From 565010f4a630858085728343624360db229452b7 Mon Sep 17 00:00:00 2001
From: Mati Dastugue
Date: Mon, 25 Apr 2022 13:32:36 -0300
Subject: [PATCH 3/6] Update README with new metadata methods
---
README.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/README.md b/README.md
index db2476a6..11f88918 100644
--- a/README.md
+++ b/README.md
@@ -129,6 +129,19 @@ SDK also includes a Metadata API to interact with AppData documents and IPFS CID
// Decode AppData Hex to CID
const decodedAppDataHex = await cowSdk.metadataApi.appDataHexToCid(hash)
console.log(decodedAppDataHex) //QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs
+
+ // Create an AppData Document
+ const appDataDoc = cowSdk.metadataApi.generateAppDataDoc({})
+ /* {
+ version: '0.1.0',
+ appCode: 'CowSwap',
+ metadata: {},
+ } */
+
+ // Upload AppDataDoc to IPFS
+ const cowSdk = new CowSdk(4, { ipfs: { apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' } })
+ await cowSdk.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
+ /* 0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80 */
```
### Install Dependencies
From 198325270691e106ee62618c40e115d48d62b17d Mon Sep 17 00:00:00 2001
From: Mati Dastugue
Date: Wed, 27 Apr 2022 14:28:56 -0300
Subject: [PATCH 4/6] Iprove ifps utils + Refactor ipfs context type
---
README.md | 101 +++++++++++++++++++-----------
src/api/metadata/index.ts | 13 ++--
src/api/metadata/metadata.spec.ts | 7 ++-
src/utils/context.ts | 4 +-
src/utils/ipfs.ts | 27 +++++---
5 files changed, 92 insertions(+), 60 deletions(-)
diff --git a/README.md b/README.md
index 11f88918..9df53815 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,12 @@
# CoW protocol SDK
+
[](https://prettier.io/)
[](https://coveralls.io/github/cowprotocol/cow-sdk?branch=main)
-
> ⚠️⚠️ THE SDK IS IN Beta ⚠️⚠️
-> It is being currently develop and is a work in progress, also it's API is subjected to change.
+> It is being currently develop and is a work in progress, also it's API is subjected to change.
> If you experience any problems, please open an issue in Github trying to describe your problem.
### Getting started
@@ -35,17 +35,18 @@ The SDK will expose the CoW API operations (`cowSdk.cowApi`) and some convenient
const trades = await cowSdk.cowApi.getOrders({
owner: '0x00000000005ef87f8ca7014309ece7260bbcdaeb', // Trader
limit: 5,
- offset: 0
+ offset: 0,
})
console.log(trades)
```
Let's see a full example on how to submit an order to CowSwap.
-> ⚠️ Before starting, the protocol requires you to approve the sell token before the order can be considered.
+> ⚠️ Before starting, the protocol requires you to approve the sell token before the order can be considered.
> For more details see https://docs.cow.fi/tutorials/how-to-submit-orders-via-the-api/1.-set-allowance-for-the-sell-token
In this example, we will:
+
- 1. **Instantiate the SDK and a wallet**: Used for signing orders
- 2. **Get a price/fee quote from the API**: Get current market price and required protocol fee to settle your trade.
- 3. **Sign the order using your wallet**: Only signed orders are considered by the protocol.
@@ -65,7 +66,7 @@ const cowSdk = new CowSdk(4, { signer: wallet })
const quoteResponse = await cowSdk.cowApi.getQuote({
kind: OrderKind.SELL, // Sell order (could also be BUY)
sellToken: '0xc778417e063141139fce010982780140aa0cd5ab', // WETH
- buyToken: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', // USDC
+ buyToken: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', // USDC
amount: '1000000000000000000', // 1 WETH
userAddress: '0x1811be0994930fe9480eaede25165608b093ad7a', // Trader
validTo: 2524608000,
@@ -100,14 +101,14 @@ console.log(`https://explorer.cow.fi/rinkeby/orders/${orderId}`)
SDK also includes a Metadata API to interact with AppData documents and IPFS CIDs
```js
- const chainId = 4 // Rinkeby
- const cowSdk = new CowSdk(chainId)
- let hash = '0xa6c81f4ca727252a05b108f1742a07430f28d474d2a3492d8f325746824d22e5'
-
- // Decode AppData document given a CID hash
- const appDataDoc = await cowSdk.metadataApi.decodeAppData(hash)
- console.log(appDataDoc)
- /* {
+const chainId = 4 // Rinkeby
+const cowSdk = new CowSdk(chainId)
+let hash = '0xa6c81f4ca727252a05b108f1742a07430f28d474d2a3492d8f325746824d22e5'
+
+// Decode AppData document given a CID hash
+const appDataDoc = await cowSdk.metadataApi.decodeAppData(hash)
+console.log(appDataDoc)
+/* {
"appCode": "CowSwap",
"metadata": {
"referrer": {
@@ -118,30 +119,56 @@ SDK also includes a Metadata API to interact with AppData documents and IPFS CID
"version": "0.1.0"
} */
- const cid = 'QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs'
-
- // Decode CID hash to AppData Hex
- const decodedAppDataHex = await cowSdk.metadataApi.cidToAppDataHex(cid)
- console.log(decodedAppDataHex) //0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80
-
- hash = '0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80'
-
- // Decode AppData Hex to CID
- const decodedAppDataHex = await cowSdk.metadataApi.appDataHexToCid(hash)
- console.log(decodedAppDataHex) //QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs
-
- // Create an AppData Document
- const appDataDoc = cowSdk.metadataApi.generateAppDataDoc({})
- /* {
- version: '0.1.0',
- appCode: 'CowSwap',
- metadata: {},
- } */
-
- // Upload AppDataDoc to IPFS
- const cowSdk = new CowSdk(4, { ipfs: { apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' } })
- await cowSdk.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
- /* 0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80 */
+const cid = 'QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs'
+
+// Decode CID hash to AppData Hex
+const decodedAppDataHex = await cowSdk.metadataApi.cidToAppDataHex(cid)
+console.log(decodedAppDataHex) //0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80
+
+hash = '0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80'
+
+// Decode AppData Hex to CID
+const decodedAppDataHex = await cowSdk.metadataApi.appDataHexToCid(hash)
+console.log(decodedAppDataHex) //QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs
+
+// Create an AppData Document with empty metadata and default appCode
+const appDataDoc = cowSdk.metadataApi.generateAppDataDoc({})
+/* {
+ version: '0.1.0',
+ appCode: 'CowSwap',
+ metadata: {},
+ }
+*/
+
+// Create an AppData Document with custom metadata and appCode
+const appDataDoc = cowSdk.metadataApi.generateAppDataDoc(
+ {
+ referrer: {
+ address: '0x1f5B740436Fc5935622e92aa3b46818906F416E9',
+ version: '0.1.0',
+ },
+ },
+ 'CowApp'
+)
+/* {
+ version: '0.1.0',
+ appCode: 'CowApp',
+ metadata: {
+ referrer: {
+ address: '0x1f5B740436Fc5935622e92aa3b46818906F416E9',
+ version: '0.1.0',
+ },
+ },
+ }
+*/
+
+// Upload AppDataDoc to IPFS (Pinata)
+const cowSdk = new CowSdk(4, {
+ ipfs: { pinataApiKey: 'YOUR_PINATA_API_KEY', pinataApiSecret: 'YOUR_PINATA_API_SECRET' },
+})
+
+await cowSdk.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
+/* 0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80 */
```
### Install Dependencies
diff --git a/src/api/metadata/index.ts b/src/api/metadata/index.ts
index 82ae51d9..6a0b39ab 100644
--- a/src/api/metadata/index.ts
+++ b/src/api/metadata/index.ts
@@ -6,6 +6,7 @@ import { AppDataDoc, MetadataDoc } from './types'
import { CowError } from '../../utils/common'
const DEFAULT_APP_CODE = 'CowSwap'
+const DEFAULT_APP_VERSION = '0.1.0'
export class MetadataApi {
context: Context
@@ -14,10 +15,10 @@ export class MetadataApi {
this.context = context
}
- generateAppDataDoc(metadata: MetadataDoc = {}): AppDataDoc {
+ generateAppDataDoc(metadata: MetadataDoc = {}, appCode: string = DEFAULT_APP_CODE): AppDataDoc {
return {
- version: '0.1.0',
- appCode: DEFAULT_APP_CODE,
+ version: DEFAULT_APP_VERSION,
+ appCode,
metadata: {
...metadata,
},
@@ -30,7 +31,7 @@ export class MetadataApi {
if (!cidV0) throw new CowError('Error getting serialized CID')
return loadIpfsFromCid(cidV0)
} catch (e) {
- const error = e as Error
+ const error = e as CowError
log.error('Error decoding AppData:', error)
throw new CowError('Error decoding AppData: ' + error.message)
}
@@ -50,10 +51,6 @@ export class MetadataApi {
}
async uploadMetadataDocToIpfs(appDataDoc: AppDataDoc): Promise {
- const { apiKey, apiSecret } = this.context.ipfs
- if (!apiKey || !apiSecret) {
- throw new CowError('You need to pass IPFS api credentials.')
- }
const { IpfsHash } = await pinJSONToIPFS(appDataDoc, this.context.ipfs)
return this.cidToAppDataHex(IpfsHash)
}
diff --git a/src/api/metadata/metadata.spec.ts b/src/api/metadata/metadata.spec.ts
index 24235fb2..d6a6fbe0 100644
--- a/src/api/metadata/metadata.spec.ts
+++ b/src/api/metadata/metadata.spec.ts
@@ -65,7 +65,7 @@ test('Invalid: Upload to IPFS without passing credentials', async () => {
test('Valid: Upload AppDataDoc to IPFS', async () => {
fetchMock.mockResponseOnce(JSON.stringify({ IpfsHash: IPFS_HASH }), { status: HTTP_STATUS_OK })
const appDataDoc = cowSdk.metadataApi.generateAppDataDoc(CUSTOM_APP_DATA_DOC.metadata)
- const cowSdk1 = new CowSdk(chainId, { ipfs: { apiKey: 'validApiKey', apiSecret: 'ValidApiSecret' } })
+ const cowSdk1 = new CowSdk(chainId, { ipfs: { pinataApiKey: 'validApiKey', pinataApiSecret: 'ValidApiSecret' } })
const appDataHex = await cowSdk1.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
expect(fetchMock).toHaveBeenCalledTimes(1)
expect(appDataHex).toEqual(APP_DATA_HEX)
@@ -76,11 +76,12 @@ test('Invalid: Upload AppDataDoc to IPFS with wrong credentials', async () => {
status: HTTP_STATUS_INTERNAL_ERROR,
})
const appDataDoc = cowSdk.metadataApi.generateAppDataDoc({})
- const cowSdk1 = new CowSdk(chainId, { ipfs: { apiKey: 'InvalidApiKey', apiSecret: 'InvValidApiSecret' } })
+ const cowSdk1 = new CowSdk(chainId, { ipfs: { pinataApiKey: 'InvalidApiKey', pinataApiSecret: 'InvValidApiSecret' } })
try {
await cowSdk1.metadataApi.uploadMetadataDocToIpfs(appDataDoc)
+ await expect(cowSdk1.metadataApi.uploadMetadataDocToIpfs(appDataDoc)).rejects.toThrow('IPFS api keys are invalid')
} catch (e) {
- const error = e as Error
+ const error = e as CowError
expect(fetchMock).toHaveBeenCalledTimes(1)
expect(error.message).toEqual('IPFS api keys are invalid')
}
diff --git a/src/utils/context.ts b/src/utils/context.ts
index dc51cf11..3eadea85 100644
--- a/src/utils/context.ts
+++ b/src/utils/context.ts
@@ -6,8 +6,8 @@ import { DEFAULT_APP_DATA_HASH, DEFAULT_IPFS_GATEWAY_URI } from '../constants'
export interface Ipfs {
uri?: string
- apiKey?: string
- apiSecret?: string
+ pinataApiKey?: string
+ pinataApiSecret?: string
}
export interface CowContext {
diff --git a/src/utils/ipfs.ts b/src/utils/ipfs.ts
index 8629b938..031ab71d 100644
--- a/src/utils/ipfs.ts
+++ b/src/utils/ipfs.ts
@@ -1,3 +1,4 @@
+import { CowError } from './common'
import { Ipfs } from './context'
type PinataPinResponse = {
@@ -6,7 +7,16 @@ type PinataPinResponse = {
Timestamp: string
}
-export async function pinJSONToIPFS(file: any, { uri, apiKey = '', apiSecret = '' }: Ipfs): Promise {
+export async function pinJSONToIPFS(
+ file: any,
+ { uri, pinataApiKey = '', pinataApiSecret = '' }: Ipfs
+): Promise {
+ const { default: fetch } = await import('cross-fetch')
+
+ if (!pinataApiKey || !pinataApiSecret) {
+ throw new CowError('You need to pass IPFS api credentials.')
+ }
+
const body = JSON.stringify({
pinataContent: file,
pinataMetadata: { name: 'appData-affiliate' },
@@ -14,19 +24,16 @@ export async function pinJSONToIPFS(file: any, { uri, apiKey = '', apiSecret = '
const pinataUrl = `${uri}/pinning/pinJSONToIPFS`
- const headers = new Headers({
- 'Content-Type': 'application/json',
- pinata_api_key: apiKey,
- pinata_secret_api_key: apiSecret,
- })
-
- const request = new Request(pinataUrl, {
+ const response = await fetch(pinataUrl, {
method: 'POST',
- headers,
body,
+ headers: {
+ 'Content-Type': 'application/json',
+ pinata_api_key: pinataApiKey,
+ pinata_secret_api_key: pinataApiSecret,
+ },
})
- const response = await fetch(request)
const data = await response.json()
if (response.status !== 200) {
From a8a231034be1fbd7b432fcc480b19e3db6b96d9e Mon Sep 17 00:00:00 2001
From: Mati Dastugue
Date: Wed, 27 Apr 2022 14:52:57 -0300
Subject: [PATCH 5/6] Update readme
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9df53815..fe165613 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,11 @@ hash = '0x5ddb2c8207c10b96fac92cb934ef9ba004bc007a073c9e5b13edc422f209ed80'
const decodedAppDataHex = await cowSdk.metadataApi.appDataHexToCid(hash)
console.log(decodedAppDataHex) //QmUf2TrpSANVXdgcYfAAACe6kg551cY3rAemB7xfEMjYvs
-// Create an AppData Document with empty metadata and default appCode
+/*Create an AppData Document with empty metadata and default appCode
+ generateAppDataDoc receives as parameters:
+ - metadata: MetadataDoc (Default: {})
+ - appCode: string (Default: 'Cowswap')
+*/
const appDataDoc = cowSdk.metadataApi.generateAppDataDoc({})
/* {
version: '0.1.0',
From d81b07f4b2aa0679579dc0342cf09908f3d9947c Mon Sep 17 00:00:00 2001
From: Mati Dastugue
Date: Wed, 27 Apr 2022 16:16:36 -0300
Subject: [PATCH 6/6] set file type as unknown for pinJSONToIPFS method
---
src/utils/ipfs.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/ipfs.ts b/src/utils/ipfs.ts
index 031ab71d..9ebbe663 100644
--- a/src/utils/ipfs.ts
+++ b/src/utils/ipfs.ts
@@ -8,7 +8,7 @@ type PinataPinResponse = {
}
export async function pinJSONToIPFS(
- file: any,
+ file: unknown,
{ uri, pinataApiKey = '', pinataApiSecret = '' }: Ipfs
): Promise {
const { default: fetch } = await import('cross-fetch')