diff --git a/packages/client/Readme.md b/packages/client/Readme.md index 1e2ad0c..04e3c89 100644 --- a/packages/client/Readme.md +++ b/packages/client/Readme.md @@ -12,9 +12,8 @@ To get started with the TalentLayer SDK in a browser environment, you'll first n 1. Ensure you have the necessary environment variables set up. These include: -`INFURA_PROJECT_ID -INFURA_SECRET -INFURA_IPFS_BASE_URL +`IPFS_SECRET +IPFS_WRITE_URL TALENT_LAYER_PLATFORM_ID PUBLIC_SIGNATURE_API_URL` @@ -32,9 +31,8 @@ import { TalentLayerClient } from '@talentLayer/client'; // Adjust the import ba const client = new TalentLayerClient({ chainId: YOUR_CHAIN_ID, // Replace with your chain ID ipfsConfig: { - clientId: process.env.INFURA_PROJECT_ID, - clientSecret: process.env.INFURA_SECRET, - baseUrl: process.env.INFURA_IPFS_BASE_URL, + clientSecret: process.env.IPFS_SECRET, + baseUrl: process.env.IPFS_WRITE_URL, }, platformId: parseInt(process.env.TALENT_LAYER_PLATFORM_ID), signatureApiUrl: process.env.PUBLIC_SIGNATURE_API_URL, @@ -45,7 +43,7 @@ const client = new TalentLayerClient({ > `PUBLIC_SIGNATURE_API_URL` is an optional property. It can be omitted. For understanding how it works, refer to its usage in the starter-kit: https://github.com/TalentLayer-Labs/starter-kit -> You can get your infura client id, secret and base url from infura's official website by setting up a new project +> For ipfs, you can get your ecret and base url from quicknode's official website by setting up a new project ### NodeJs (backend) @@ -55,9 +53,8 @@ For backend applications, the TalentLayer SDK can be initialized similarly to th As with the browser setup, ensure you have the necessary environment variables: -`INFURA_PROJECT_ID -INFURA_SECRET -INFURA_IPFS_BASE_URL +`IPFS_SECRET +IPFS_WRITE_URL TALENT_LAYER_PLATFORM_ID PUBLIC_SIGNATURE_API_URL` @@ -71,9 +68,8 @@ import { TalentLayerClient } from '@talentLayer/client'; // Adjust the import ba const client = new TalentLayerClient({ chainId: YOUR_CHAIN_ID, // Replace with your chain ID ipfsConfig: { - clientId: process.env.INFURA_PROJECT_ID, - clientSecret: process.env.INFURA_SECRET, - baseUrl: process.env.INFURA_IPFS_BASE_URL, + clientSecret: process.env.IPFS_SECRET, + baseUrl: process.env.IPFS_WRITE_URL, }, platformId: parseInt(process.env.TALENT_LAYER_PLATFORM_ID), signatureApiUrl: process.env.PUBLIC_SIGNATURE_API_URL, diff --git a/packages/client/src/__tests__/talentLayerClient.spec.ts b/packages/client/src/__tests__/talentLayerClient.spec.ts index bf7ac53..3fbc7b4 100644 --- a/packages/client/src/__tests__/talentLayerClient.spec.ts +++ b/packages/client/src/__tests__/talentLayerClient.spec.ts @@ -1,4 +1,3 @@ - import { ERC20 } from '../blockchain-bindings/erc20'; import { Disputes } from '../disputes'; import { Escrow } from '../escrow'; @@ -18,205 +17,191 @@ import TalentLayerPlatformID from '../contracts/ABI/TalentLayerPlatformID.json'; import TalentLayerArbitrator from '../contracts/ABI/TalentLayerArbitrator.json'; import { getChainConfig } from '../config'; - - -jest.mock('axios') +jest.mock('axios'); jest.mock('ipfs-http-client', () => ({ - create: jest.fn().mockImplementation(() => ({ - add: jest.fn().mockResolvedValue({ path: '0xracoon' }), // Mocking the add method of IPFS client - pin: { add: jest.fn().mockResolvedValue({ path: '0xRacoon' }) } - })), + create: jest.fn().mockImplementation(() => ({ + add: jest.fn().mockResolvedValue({ path: '0xracoon' }), // Mocking the add method of IPFS client + pin: { add: jest.fn().mockResolvedValue({ path: '0xRacoon' }) }, + })), })); describe('TalentLayerClient', () => { - let client: any; - - beforeEach(() => { - Object.defineProperty(globalThis, 'window', { - }); - const chainId = 137; - const ipfsConfig = { - clientId: 'abcd', - clientSecret: 'abcde', - baseUrl: 'www.example.com' - } - const viemConfig = {}; - const platformID = testPlatformId; - const signatureApiUrl = 'www.example.com'; - client = new TalentLayerClient({ - chainId: chainId, - ipfsConfig: ipfsConfig, - platformId: testPlatformId, - signatureApiUrl: signatureApiUrl, - debug: false - }) - - - }) - - describe('constructor', () => { - it('should be initialised successfully', async () => { - expect(client).toBeDefined() - - }) - }) - - describe('getters', () => { - it('should return all domain specific getters', async () => { - expect(client.platform).toBeInstanceOf(Platform) - expect(client.erc20).toBeInstanceOf(ERC20) - expect(client.proposal).toBeInstanceOf(Proposal) - expect(client.disputes).toBeInstanceOf(Disputes) - expect(client.service).toBeInstanceOf(Service) - expect(client.profile).toBeInstanceOf(Profile) - expect(client.escrow).toBeInstanceOf(Escrow) - expect(client.review).toBeInstanceOf(Review) - }) - }) - - describe('getChainConfig', () => { - it('should return the chain config based on the network id passed', () => { - // Arrange - const networkId = NetworkEnum.MUMBAI - - // Act - const config = client.getChainConfig(networkId); - - // Assert - expect(config).toEqual(getChainConfig(networkId)); - }) - }) + let client: any; + + beforeEach(() => { + Object.defineProperty(globalThis, 'window', {}); + const chainId = 137; + const ipfsConfig = { + clientSecret: 'abcde', + baseUrl: 'www.example.com', + }; + const viemConfig = {}; + const platformID = testPlatformId; + const signatureApiUrl = 'www.example.com'; + client = new TalentLayerClient({ + chainId: chainId, + ipfsConfig: ipfsConfig, + platformId: testPlatformId, + signatureApiUrl: signatureApiUrl, + debug: false, + }); + }); + + describe('constructor', () => { + it('should be initialised successfully', async () => { + expect(client).toBeDefined(); + }); + }); + + describe('getters', () => { + it('should return all domain specific getters', async () => { + expect(client.platform).toBeInstanceOf(Platform); + expect(client.erc20).toBeInstanceOf(ERC20); + expect(client.proposal).toBeInstanceOf(Proposal); + expect(client.disputes).toBeInstanceOf(Disputes); + expect(client.service).toBeInstanceOf(Service); + expect(client.profile).toBeInstanceOf(Profile); + expect(client.escrow).toBeInstanceOf(Escrow); + expect(client.review).toBeInstanceOf(Review); + }); + }); + + describe('getChainConfig', () => { + it('should return the chain config based on the network id passed', () => { + // Arrange + const networkId = NetworkEnum.MUMBAI; + + // Act + const config = client.getChainConfig(networkId); + + // Assert + expect(config).toEqual(getChainConfig(networkId)); + }); + }); }); - describe('TalentLayerClient:dev', () => { - let client: any; - - beforeEach(() => { - Object.defineProperty(globalThis, 'window', { - }); - const chainId = 137; - const ipfsConfig = { - clientId: 'abcd', - clientSecret: 'abcde', - baseUrl: 'www.example.com' - } - const viemConfig = {}; - const platformID = testPlatformId; - const signatureApiUrl = 'www.example.com'; - const localNetworkId = NetworkEnum.LOCAL; - const customConfig: CustomConfig = { - chainConfig: { - id: localNetworkId, - name: 'local', - network: 'local', - nativeCurrency: { - name: 'racoon', - symbol: 'RCN', - decimals: 18 - }, - rpcUrls: { - default: { - http: ['http:localhost:1337'] - }, - public: { - http: ['http:localhost:1337'] - } - } - - - - }, - contractConfig: { - networkId: localNetworkId, - subgraphUrl: 'www.example.com', - escrowConfig: { - adminFee: '0', - adminWallet: '0xC01FcDfDE3B2ABA1eab76731493C617FfAED2F10', - timeoutPayment: 3600 * 24 * 7, - }, - tokens: { - '0x0000000000000000000000000000000000000000': { - address: '0x0000000000000000000000000000000000000000', - symbol: 'RLC', - name: 'iExec RLC', - decimals: 18, - }, - '0xe62C28709E4F19Bae592a716b891A9B76bf897E4': { - address: '0xe62C28709E4F19Bae592a716b891A9B76bf897E4', - symbol: 'SERC20', - name: 'SimpleERC20', - decimals: 18, - }, - }, - contracts: { - talentLayerId: { - address: '0xC51537E03f56650C63A9Feca4cCb5a039c77c822', - abi: TalentLayerID.abi, - }, - talentLayerService: { - address: '0x45E8F869Fd316741A9316f39bF09AD03Df88496f', - abi: TalerLayerService.abi, - }, - talentLayerReview: { - address: '0x6A5BF452108DA389B7B38284E871f538671Ad375', - abi: TalentLayerReview.abi, - }, - talentLayerEscrow: { - address: '0x7A534501a6e63448EBC691f27B27B76d4F9b7E17', - abi: TalentLayerEscrow.abi, - }, - talentLayerPlatformId: { - address: '0x05D8A2E01EB06c284ECBae607A2d0c2BE946Bf49', - abi: TalentLayerPlatformID.abi, - }, - talentLayerArbitrator: { - address: '0x24cEd045b50cF811862B1c33dC6B1fbC8358F521', - abi: TalentLayerArbitrator.abi, - }, - } - } - } - - client = new TalentLayerClient({ - chainId: chainId, - ipfsConfig: ipfsConfig, - platformId: testPlatformId, - signatureApiUrl: signatureApiUrl, - customConfig: customConfig - }) - }) - - describe('constructor', () => { - it('should be initialised successfully', async () => { - expect(client).toBeDefined() - - }) - }) - - describe('getters', () => { - it('should return all domain specific getters', async () => { - expect(client.platform).toBeInstanceOf(Platform) - expect(client.erc20).toBeInstanceOf(ERC20) - expect(client.proposal).toBeInstanceOf(Proposal) - expect(client.disputes).toBeInstanceOf(Disputes) - expect(client.service).toBeInstanceOf(Service) - expect(client.profile).toBeInstanceOf(Profile) - expect(client.escrow).toBeInstanceOf(Escrow) - expect(client.review).toBeInstanceOf(Review) - }) - }) - - describe('getChainConfig', () => { - it('should return the chain config irrespective of the network id passed', () => { - // Arrange - const networkId = NetworkEnum.MUMBAI - - // Act - const config = client.getChainConfig(networkId); - - // Assert - expect(config).toEqual(client.customConfig.contractConfig); - }) - }) -}) + let client: any; + + beforeEach(() => { + Object.defineProperty(globalThis, 'window', {}); + const chainId = 137; + const ipfsConfig = { + clientSecret: 'abcde', + baseUrl: 'www.example.com', + }; + const viemConfig = {}; + const platformID = testPlatformId; + const signatureApiUrl = 'www.example.com'; + const localNetworkId = NetworkEnum.LOCAL; + const customConfig: CustomConfig = { + chainConfig: { + id: localNetworkId, + name: 'local', + network: 'local', + nativeCurrency: { + name: 'racoon', + symbol: 'RCN', + decimals: 18, + }, + rpcUrls: { + default: { + http: ['http:localhost:1337'], + }, + public: { + http: ['http:localhost:1337'], + }, + }, + }, + contractConfig: { + networkId: localNetworkId, + subgraphUrl: 'www.example.com', + escrowConfig: { + adminFee: '0', + adminWallet: '0xC01FcDfDE3B2ABA1eab76731493C617FfAED2F10', + timeoutPayment: 3600 * 24 * 7, + }, + tokens: { + '0x0000000000000000000000000000000000000000': { + address: '0x0000000000000000000000000000000000000000', + symbol: 'RLC', + name: 'iExec RLC', + decimals: 18, + }, + '0xe62C28709E4F19Bae592a716b891A9B76bf897E4': { + address: '0xe62C28709E4F19Bae592a716b891A9B76bf897E4', + symbol: 'SERC20', + name: 'SimpleERC20', + decimals: 18, + }, + }, + contracts: { + talentLayerId: { + address: '0xC51537E03f56650C63A9Feca4cCb5a039c77c822', + abi: TalentLayerID.abi, + }, + talentLayerService: { + address: '0x45E8F869Fd316741A9316f39bF09AD03Df88496f', + abi: TalerLayerService.abi, + }, + talentLayerReview: { + address: '0x6A5BF452108DA389B7B38284E871f538671Ad375', + abi: TalentLayerReview.abi, + }, + talentLayerEscrow: { + address: '0x7A534501a6e63448EBC691f27B27B76d4F9b7E17', + abi: TalentLayerEscrow.abi, + }, + talentLayerPlatformId: { + address: '0x05D8A2E01EB06c284ECBae607A2d0c2BE946Bf49', + abi: TalentLayerPlatformID.abi, + }, + talentLayerArbitrator: { + address: '0x24cEd045b50cF811862B1c33dC6B1fbC8358F521', + abi: TalentLayerArbitrator.abi, + }, + }, + }, + }; + + client = new TalentLayerClient({ + chainId: chainId, + ipfsConfig: ipfsConfig, + platformId: testPlatformId, + signatureApiUrl: signatureApiUrl, + customConfig: customConfig, + }); + }); + + describe('constructor', () => { + it('should be initialised successfully', async () => { + expect(client).toBeDefined(); + }); + }); + + describe('getters', () => { + it('should return all domain specific getters', async () => { + expect(client.platform).toBeInstanceOf(Platform); + expect(client.erc20).toBeInstanceOf(ERC20); + expect(client.proposal).toBeInstanceOf(Proposal); + expect(client.disputes).toBeInstanceOf(Disputes); + expect(client.service).toBeInstanceOf(Service); + expect(client.profile).toBeInstanceOf(Profile); + expect(client.escrow).toBeInstanceOf(Escrow); + expect(client.review).toBeInstanceOf(Review); + }); + }); + + describe('getChainConfig', () => { + it('should return the chain config irrespective of the network id passed', () => { + // Arrange + const networkId = NetworkEnum.MUMBAI; + + // Act + const config = client.getChainConfig(networkId); + + // Assert + expect(config).toEqual(client.customConfig.contractConfig); + }); + }); +}); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3e1466e..a4a4568 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -45,7 +45,6 @@ export class TalentLayerClient { this.graphQlClient = new GraphQLClient(getGraphQLConfig(config.chainId)); this.ipfsClient = new IPFSClient({ baseUrl: config.ipfsConfig.baseUrl, - clientId: config.ipfsConfig.clientId, clientSecret: config.ipfsConfig.clientSecret, }); this.viemClient = new ViemClient(config.chainId, config.walletConfig || {}, this.logger); @@ -55,9 +54,14 @@ export class TalentLayerClient { // DEV mode overrides if (config.customConfig) { this.customConfig = config?.customConfig; - this.chainId = config.customConfig.chainConfig.id + this.chainId = config.customConfig.chainConfig.id; this.graphQlClient = new GraphQLClient(getGraphQLConfig(config.customConfig.chainConfig.id)); - this.viemClient = new ViemClient(config.customConfig.chainConfig.id, config.walletConfig || {}, this.logger, this.customConfig); + this.viemClient = new ViemClient( + config.customConfig.chainConfig.id, + config.walletConfig || {}, + this.logger, + this.customConfig, + ); } } @@ -77,7 +81,14 @@ export class TalentLayerClient { */ // @ts-ignore get erc20(): IERC20 { - return new ERC20(this.ipfsClient, this.viemClient, this.platformID, this.chainId, this.logger, this.customConfig); + return new ERC20( + this.ipfsClient, + this.viemClient, + this.platformID, + this.chainId, + this.logger, + this.customConfig, + ); } /** @@ -103,7 +114,13 @@ export class TalentLayerClient { */ // @ts-ignore get platform(): IPlatform { - return new Platform(this.graphQlClient, this.viemClient, this.platformID, this.ipfsClient, this.logger); + return new Platform( + this.graphQlClient, + this.viemClient, + this.platformID, + this.ipfsClient, + this.logger, + ); } /** @@ -112,7 +129,13 @@ export class TalentLayerClient { */ // @ts-ignore get disputes(): IDispute { - return new Disputes(this.viemClient, this.platformID, this.graphQlClient, this.chainId, this.logger); + return new Disputes( + this.viemClient, + this.platformID, + this.graphQlClient, + this.chainId, + this.logger, + ); } /** @@ -138,7 +161,13 @@ export class TalentLayerClient { */ // @ts-ignore get profile(): IProfile { - return new Profile(this.graphQlClient, this.ipfsClient, this.viemClient, this.platformID, this.logger); + return new Profile( + this.graphQlClient, + this.ipfsClient, + this.viemClient, + this.platformID, + this.logger, + ); } /** @@ -147,7 +176,13 @@ export class TalentLayerClient { */ // @ts-ignore get review(): IReview { - return new Review(this.graphQlClient, this.ipfsClient, this.viemClient, this.platformID, this.logger); + return new Review( + this.graphQlClient, + this.ipfsClient, + this.viemClient, + this.platformID, + this.logger, + ); } /** @@ -162,7 +197,7 @@ export class TalentLayerClient { this.viemClient, this.platformID, this.chainId, - this.logger + this.logger, ); } } diff --git a/packages/client/src/ipfs/__tests__/ipfs.spec.ts b/packages/client/src/ipfs/__tests__/ipfs.spec.ts deleted file mode 100644 index 38dead0..0000000 --- a/packages/client/src/ipfs/__tests__/ipfs.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import IPFSClient from ".."; -import { IPFSClientConfig } from "../../types"; - -jest.mock('axios') -jest.mock('ipfs-http-client', () => ({ - create: jest.fn().mockImplementation(() => ({ - add: jest.fn().mockResolvedValue({ path: '0xracoon' }), // Mocking the add method of IPFS client - pin: { add: jest.fn().mockResolvedValue({ path: '0xRacoon' }) } - })), -})); - -describe('IPFSClient', () => { - let ipfsClientConfig: IPFSClientConfig; - let ipfsClient: IPFSClient; - - beforeEach(() => { - ipfsClientConfig = { clientId: 'clientId', clientSecret: 'clientSecret', baseUrl: 'baseUrl' }; - ipfsClient = new IPFSClient(ipfsClientConfig); - }); - - describe('post', () => { - test('should post data successfully', async () => { - // Arrange - const data = 'someData'; - - // Act - console.log(ipfsClient) - const result = await ipfsClient.post(data); - - // Assert - expect(result).toEqual('0xracoon'); - }); - - test('should throw an error if the IPFS client is not initialized', async () => { - // Arrange - const data = 'someData'; - ipfsClient.ipfs = null; // Simulating an uninitialized IPFS client - - // Act & Assert - await expect(ipfsClient.post(data)).rejects.toThrow('IPFS client not intiialised properly'); - }); - }); - - // Additional tests as needed... -}); \ No newline at end of file diff --git a/packages/client/src/ipfs/index.ts b/packages/client/src/ipfs/index.ts index 08a2f99..3fefe43 100644 --- a/packages/client/src/ipfs/index.ts +++ b/packages/client/src/ipfs/index.ts @@ -2,35 +2,43 @@ import { IPFSClientConfig } from '../types'; export default class IPFSClient { static readonly IPFS_CLIENT_ERROR = 'IPFS client not initialised'; - ipfs: any; - authorization: any; + url: string; + secret: string; constructor(ipfsClientConfig: IPFSClientConfig) { - const authorization = - 'Basic ' + btoa(ipfsClientConfig.clientId + ':' + ipfsClientConfig.clientSecret); - this.authorization = authorization; - // ipfs-http-client is being mocked by src/__mocks__/ipfs-http-client - // due to the way its being imported in ipfs/index.ts, we have to add a special mock for it - import('ipfs-http-client').then(({ create }) => { - this.ipfs = create({ - url: ipfsClientConfig.baseUrl, - headers: { - authorization, - }, - }); - }); + this.url = ipfsClientConfig.baseUrl; + this.secret = ipfsClientConfig.clientSecret; } public async post(data: string): Promise { - const formData = new FormData(); - formData.append('file', new Blob([data], { type: 'application/json' })); + try { + const myHeaders = new Headers(); + myHeaders.append('x-api-key', this.secret as unknown as string); - if (this.ipfs) { - const result = await this.ipfs.add(data); - await this.ipfs.pin.add(result.path); - return result.path; - } + const formdata = new FormData(); + formdata.append('Body', new Blob([data], { type: 'application/json' })); + formdata.append('Key', 'metadata'); + formdata.append('ContentType', 'application/json'); + + const requestOptions = { + method: 'POST', + headers: myHeaders, + body: formdata, + redirect: 'follow', + }; + + const response = await fetch( + this.url, + // @ts-ignore + requestOptions, + ); + const result: { pin?: { cid?: string } } = await response.json(); - throw Error('IPFS client not intiialised properly'); + if (!result.pin?.cid) throw new Error('Error while uploading to IPFS - no cid returned'); + + return result.pin.cid; + } catch (error: any) { + throw new Error('Error while uploading to IPFS - post failed'); + } } } diff --git a/packages/client/src/types/index.ts b/packages/client/src/types/index.ts index f3dbffb..2a1e301 100644 --- a/packages/client/src/types/index.ts +++ b/packages/client/src/types/index.ts @@ -12,7 +12,7 @@ export enum NetworkEnum { MUMBAI = 80001, IEXEC = 134, POLYGON = 137, - LOCAL = 1 + LOCAL = 1, } export enum RateToken { @@ -35,7 +35,6 @@ export type GraphQLConfig = { export type GraphQLQuery = string; export type IPFSClientConfig = { - clientId: string; clientSecret: string; baseUrl: string; }; @@ -52,23 +51,22 @@ type Currency = { decimals: number; name: string; symbol: string; -} +}; export type chainConfig = Chain; /** * Represents a custom config that can be passed to the sdk during initialisation of the client * Typically, it should bne used in order to use the sdk with a custom chain and contracts - * + * * @property {chainConfig} chainConfig - The config of the custom chain that the sdk is being used for * @property {Config} contractConfig - The custom contracts to be used with the sdk */ export type CustomConfig = { - chainConfig: chainConfig, - contractConfig: Config - -} + chainConfig: chainConfig; + contractConfig: Config; +}; export type TalentLayerClientConfig = { chainId: NetworkEnum; @@ -77,13 +75,13 @@ export type TalentLayerClientConfig = { platformId: number; signatureApiUrl?: string; customConfig?: CustomConfig; - debug?: Boolean + debug?: Boolean; }; /** * Represents the response of a transaction made through the client. * This type is typically used to encapsulate details of blockchain transactions initiated by the client. - * + * * @property {Hash} tx - The transaction hash that uniquely identifies the transaction on the blockchain. * @property {string} cid - The Content Identifier (CID) referring to the location of the transaction-related data stored on IPFS. */