diff --git a/package-lock.json b/package-lock.json index 11205fb..d77ede0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "buffer": "^6.0.3", "ethers": "^6.8.1", "graphql-request": "^6.1.0", - "iexec": "^8.16.1", + "iexec": "^8.17.0", "kubo-rpc-client": "^4.1.3", "yup": "^1.1.1" }, @@ -5322,9 +5322,9 @@ ] }, "node_modules/iexec": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.16.1.tgz", - "integrity": "sha512-0Kxt5z2gpjcwRkGmfr3/ckOk1G3p6G4s4CQzjtgKo8v64giLneO/PcbQAkRLW7imCNegvQL6Bpw1oQCwELYHqw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.17.0.tgz", + "integrity": "sha512-wOXtQenIpXa94DA12CqvFXsRlje5U1eG4NQJwwEm5BxEMG5mZqEI8etHcUx8/qy1WQwwbuv1CcPP07gT8GZI3Q==", "license": "Apache-2.0", "dependencies": { "@ensdomains/ens-contracts": "^1.2.5", diff --git a/package.json b/package.json index fe71aa0..d292cf0 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "buffer": "^6.0.3", "ethers": "^6.8.1", "graphql-request": "^6.1.0", - "iexec": "^8.16.1", + "iexec": "^8.17.0", "kubo-rpc-client": "^4.1.3", "yup": "^1.1.1" }, diff --git a/src/config/config.ts b/src/config/config.ts index 17e39db..b45a4df 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -7,7 +7,7 @@ export const DEFAULT_CHAIN_ID = 134; interface ChainConfig { name: string; - dappAddress: string; + dappAddress?: string; prodWorkerpoolAddress: string; dataProtectorSubgraph: string; ipfsUploadUrl: string; @@ -29,7 +29,7 @@ export const CHAIN_CONFIG: Record = { }, 421614: { name: 'arbitrum-sepolia-testnet', - dappAddress: 'web3telegram.apps.iexec.eth', + dappAddress: undefined, // ENS not supported on this network, address will be resolved from Compass prodWorkerpoolAddress: '0x39c3cdd91a7f1c4ed59108a9da4e79de9a1c1b59', dataProtectorSubgraph: 'https://thegraph.arbitrum-sepolia-testnet.iex.ec/api/subgraphs/id/5YjRPLtjS6GH6bB4yY55Qg4HzwtRGQ8TaHtGf9UBWWd', diff --git a/src/utils/resolveDappAddressFromCompass.ts b/src/utils/resolveDappAddressFromCompass.ts new file mode 100644 index 0000000..351946f --- /dev/null +++ b/src/utils/resolveDappAddressFromCompass.ts @@ -0,0 +1,55 @@ +import { CompassCallError } from 'iexec/errors'; +import { AddressOrENS } from '../web3telegram/types.js'; + +export async function resolveDappAddressFromCompass( + compassUrl: string, + chainId: number +): Promise { + if (!compassUrl) { + return undefined; + } + + return ( + fetch(`${compassUrl}/${chainId}/iapps/web3telegram`) + // Handle network errors + .catch((error) => { + throw new CompassCallError( + `Connection to ${compassUrl} failed with a network error`, + error + ); + }) + // Handle server errors + .then((response) => { + if (response.status >= 500 && response.status <= 599) { + throw new CompassCallError( + `Server at ${compassUrl} encountered an internal error`, + Error( + `Server internal error: ${response.status} ${response.statusText}` + ) + ); + } + return response; + }) + // Handle unexpected response formats + .then((response) => { + if (response.status !== 200) { + throw new Error( + `Failed to fetch dapp address from compass: ${response.statusText}` + ); + } + const contentType = response.headers.get('Content-Type'); + if (!contentType || contentType.indexOf('application/json') === -1) { + throw new Error( + 'Failed to fetch dapp address from compass: response is not JSON' + ); + } + return response.json(); + }) + .then((data) => { + if (!data || !data.address) { + throw new Error(`No dapp address found in compass response`); + } + return data.address; + }) + ); +} diff --git a/src/web3telegram/IExecWeb3telegram.ts b/src/web3telegram/IExecWeb3telegram.ts index 2f65415..fc3ef0c 100644 --- a/src/web3telegram/IExecWeb3telegram.ts +++ b/src/web3telegram/IExecWeb3telegram.ts @@ -17,6 +17,7 @@ import { import { getChainDefaultConfig } from '../config/config.js'; import { isValidProvider } from '../utils/validators.js'; import { getChainIdFromProvider } from '../utils/getChainId.js'; +import { resolveDappAddressFromCompass } from '../utils/resolveDappAddressFromCompass.js'; type EthersCompatibleProvider = | AbstractProvider @@ -126,16 +127,38 @@ export class IExecWeb3telegram { allowExperimentalNetworks: this.options.allowExperimentalNetworks, }); + const ipfsGateway = + this.options?.ipfsGateway || chainDefaultConfig?.ipfsGateway; + + let iexec: IExec, graphQLClient: GraphQLClient; + + try { + iexec = new IExec( + { ethProvider: this.ethProvider }, + { + ipfsGatewayURL: ipfsGateway, + ...this.options?.iexecOptions, + allowExperimentalNetworks: this.options.allowExperimentalNetworks, + } + ); + } catch (e: any) { + throw new Error(`Unsupported ethProvider: ${e.message}`); + } + const subgraphUrl = this.options?.dataProtectorSubgraph || chainDefaultConfig?.dataProtectorSubgraph; const dappAddressOrENS = - this.options?.dappAddressOrENS || chainDefaultConfig?.dappAddress; + this.options?.dappAddressOrENS || + chainDefaultConfig?.dappAddress || + (await resolveDappAddressFromCompass( + await iexec.config.resolveCompassURL(), + chainId + )); const dappWhitelistAddress = this.options?.dappWhitelistAddress || chainDefaultConfig?.whitelistSmartContract; - const ipfsGateway = - this.options?.ipfsGateway || chainDefaultConfig?.ipfsGateway; + const defaultWorkerpool = chainDefaultConfig?.prodWorkerpoolAddress; const ipfsNode = this.options?.ipfsNode || chainDefaultConfig?.ipfsUploadUrl; @@ -156,21 +179,6 @@ export class IExecWeb3telegram { ); } - let iexec: IExec, graphQLClient: GraphQLClient; - - try { - iexec = new IExec( - { ethProvider: this.ethProvider }, - { - ipfsGatewayURL: ipfsGateway, - ...this.options?.iexecOptions, - allowExperimentalNetworks: this.options.allowExperimentalNetworks, - } - ); - } catch (e: any) { - throw new Error(`Unsupported ethProvider: ${e.message}`); - } - try { graphQLClient = new GraphQLClient(subgraphUrl); } catch (error: any) { diff --git a/tests/e2e/constructor.test.ts b/tests/e2e/constructor.test.ts index 1c15b61..9818f6c 100644 --- a/tests/e2e/constructor.test.ts +++ b/tests/e2e/constructor.test.ts @@ -116,6 +116,21 @@ describe('IExecWeb3telegram()', () => { expect(await iexec.config.resolveIexecGatewayURL()).toBe(iexecGatewayURL); }); + it( + 'When calling a read method should work as expected', + async () => { + // --- GIVEN + const web3telegram = new IExecWeb3telegram(); + const wallet = Wallet.createRandom(); + + // --- WHEN/THEN + await expect( + web3telegram.fetchUserContacts({ userAddress: wallet.address }) + ).resolves.not.toThrow(); + }, + MAX_EXPECTED_WEB2_SERVICES_TIME + ); + describe('When instantiating SDK with an experimental network', () => { const experimentalNetworkSigner = getWeb3Provider( Wallet.createRandom().privateKey, @@ -157,10 +172,10 @@ describe('IExecWeb3telegram()', () => { expect(web3telegram['ipfsGateway']).toBe( arbitrumSepoliaConfig!.ipfsGateway ); - expect(web3telegram['ipfsNode']).toBe(arbitrumSepoliaConfig!.ipfsUploadUrl); - expect(web3telegram['dappAddressOrENS']).toBe( - arbitrumSepoliaConfig!.dappAddress + expect(web3telegram['ipfsNode']).toBe( + arbitrumSepoliaConfig!.ipfsUploadUrl ); + expect(web3telegram['dappAddressOrENS']).toMatch(/^0x[a-fA-F0-9]{40}$/); // resolved from Compass expect(web3telegram['dappWhitelistAddress']).toBe( arbitrumSepoliaConfig!.whitelistSmartContract.toLowerCase() ); @@ -190,7 +205,9 @@ describe('IExecWeb3telegram()', () => { allowExperimentalNetworks: true, }); expect(arbitrumSepoliaConfig).not.toBeNull(); - expect(web3telegram['ipfsNode']).toBe(arbitrumSepoliaConfig!.ipfsUploadUrl); + expect(web3telegram['ipfsNode']).toBe( + arbitrumSepoliaConfig!.ipfsUploadUrl + ); expect(web3telegram['dappWhitelistAddress']).toBe( arbitrumSepoliaConfig!.whitelistSmartContract.toLowerCase() ); @@ -204,18 +221,26 @@ describe('IExecWeb3telegram()', () => { }); }); - it( - 'When calling a read method should work as expected', - async () => { - // --- GIVEN - const web3telegram = new IExecWeb3telegram(); - const wallet = Wallet.createRandom(); + describe('When instantiating SDK with on a network backed by Compass', () => { + it('should resolve dapp address from Compass', async () => { + const chainId = 421614; // Arbitrum Sepolia Testnet ENS not supported + const chainConfig = getChainDefaultConfig(chainId, { + allowExperimentalNetworks: true, + }); + expect(chainConfig.dappAddress).toBeUndefined(); // ENS not supported on this network - // --- WHEN/THEN - await expect( - web3telegram.fetchUserContacts({ userAddress: wallet.address }) - ).resolves.not.toThrow(); - }, - MAX_EXPECTED_WEB2_SERVICES_TIME - ); + const web3telegram = new IExecWeb3telegram( + getWeb3Provider(Wallet.createRandom().privateKey, { + host: chainId, + allowExperimentalNetworks: true, + }), + { allowExperimentalNetworks: true } + ); + await web3telegram.init(); + + const dappAddressOrENS = web3telegram['dappAddressOrENS']; + expect(typeof dappAddressOrENS).toBe('string'); + expect(dappAddressOrENS).toMatch(/^0x[a-fA-F0-9]{40}$/); + }); + }); }); diff --git a/tests/unit/utils/resolveDappAddressFromCompass.test.ts b/tests/unit/utils/resolveDappAddressFromCompass.test.ts new file mode 100644 index 0000000..3248728 --- /dev/null +++ b/tests/unit/utils/resolveDappAddressFromCompass.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from '@jest/globals'; +import { resolveDappAddressFromCompass } from '../../../src/utils/resolveDappAddressFromCompass.js'; + +describe('resolveDappAddressFromCompass', () => { + it('should return undefined if compassUrl is not provided', async () => { + const result = await resolveDappAddressFromCompass('', 1); + expect(result).toBeUndefined(); + }); + + it('should resolve dapp address from a valid compass instance', async () => { + const compassUrl = 'https://compass.arbitrum-sepolia-testnet.iex.ec'; + const chainId = 421614; + const address = await resolveDappAddressFromCompass(compassUrl, chainId); + expect(address).toBeDefined(); + }); + + it('should throw CompassCallError on network error', async () => { + const compassUrl = 'https://invalid-url.iex.ec'; + await expect(resolveDappAddressFromCompass(compassUrl, 1)).rejects.toThrow( + 'Compass API error: Connection to https://invalid-url.iex.ec failed with a network error' + ); + }); +});