Skip to content

Commit 5b450df

Browse files
Merge pull request #191 from iExecBlockchainComputing/feature/add-multi-chain-config
Feature/add multi chain config
2 parents 110d927 + 9aff493 commit 5b450df

File tree

13 files changed

+862
-620
lines changed

13 files changed

+862
-620
lines changed

package-lock.json

Lines changed: 600 additions & 497 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"buffer": "^6.0.3",
5353
"ethers": "^6.13.2",
5454
"graphql-request": "^6.1.0",
55-
"iexec": "^8.13.1",
55+
"iexec": "^8.16.0",
5656
"kubo-rpc-client": "^4.1.1",
5757
"yup": "^1.1.1"
5858
},
@@ -75,4 +75,4 @@
7575
"typescript": "^5.5.2",
7676
"whitelist-smart-contract": "github:iExecBlockchainComputing/whitelist-smart-contract#0.2.0"
7777
}
78-
}
78+
}

src/config/config.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,44 @@
1-
export const WEB3_MAIL_DAPP_ADDRESS = 'web3mail.apps.iexec.eth';
2-
export const PROD_WORKERPOOL_ADDRESS = 'prod-v8-bellecour.main.pools.iexec.eth';
3-
export const DATAPROTECTOR_SUBGRAPH_ENDPOINT =
4-
'https://thegraph.iex.ec/subgraphs/name/bellecour/dataprotector-v2';
51
export const MAX_DESIRED_DATA_ORDER_PRICE = 0;
62
export const MAX_DESIRED_APP_ORDER_PRICE = 0;
73
export const MAX_DESIRED_WORKERPOOL_ORDER_PRICE = 0;
84
export const DEFAULT_CONTENT_TYPE = 'text/plain';
9-
export const IPFS_UPLOAD_URL = '/dns4/ipfs-upload.v8-bellecour.iex.ec/https';
10-
export const DEFAULT_IPFS_GATEWAY = 'https://ipfs-gateway.v8-bellecour.iex.ec';
11-
export const WHITELIST_SMART_CONTRACT_ADDRESS =
12-
'0x781482C39CcE25546583EaC4957Fb7Bf04C277D2';
135
export const ANY_DATASET_ADDRESS = 'any';
6+
7+
export const CHAIN_IDS = {
8+
BELLECOUR: 134,
9+
AVALANCHE_FUJI: 43113,
10+
ARBITRUM_SEPOLIA: 421614,
11+
} as const;
12+
13+
export const DEFAULT_CHAIN_ID = CHAIN_IDS.BELLECOUR;
14+
15+
interface ChainConfig {
16+
name: string;
17+
dappAddress: string;
18+
prodWorkerpoolAddress: string;
19+
dataProtectorSubgraph: string;
20+
ipfsUploadUrl: string;
21+
ipfsGateway: string;
22+
whitelistSmartContract: string;
23+
}
24+
25+
export const CHAIN_CONFIG: Record<number, ChainConfig> = {
26+
[CHAIN_IDS.BELLECOUR]: {
27+
name: 'bellecour',
28+
dappAddress: 'web3mail.apps.iexec.eth',
29+
prodWorkerpoolAddress: 'prod-v8-bellecour.main.pools.iexec.eth',
30+
dataProtectorSubgraph:
31+
'https://thegraph.iex.ec/subgraphs/name/bellecour/dataprotector-v2',
32+
ipfsUploadUrl: '/dns4/ipfs-upload.v8-bellecour.iex.ec/https',
33+
ipfsGateway: 'https://ipfs-gateway.v8-bellecour.iex.ec',
34+
whitelistSmartContract: '0x781482C39CcE25546583EaC4957Fb7Bf04C277D2',
35+
},
36+
};
37+
38+
export function getChainConfig(chainId: number): ChainConfig {
39+
const config = CHAIN_CONFIG[chainId];
40+
if (!config) {
41+
throw new Error(`Unsupported chain ID: ${chainId}`);
42+
}
43+
return config;
44+
}

src/utils/getChainId.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
JsonRpcProvider,
3+
BrowserProvider,
4+
AbstractProvider,
5+
AbstractSigner,
6+
Eip1193Provider,
7+
} from 'ethers';
8+
import { DEFAULT_CHAIN_ID } from '../config/config.js';
9+
10+
type EthersCompatibleProvider =
11+
| string
12+
| AbstractProvider
13+
| AbstractSigner
14+
| Eip1193Provider;
15+
16+
export async function getChainIdFromProvider(
17+
ethProvider: EthersCompatibleProvider
18+
): Promise<number> {
19+
try {
20+
if (typeof ethProvider === 'string') {
21+
const provider = new JsonRpcProvider(ethProvider);
22+
const network = await provider.getNetwork();
23+
return Number(network.chainId);
24+
} else if (ethProvider instanceof AbstractProvider) {
25+
const network = await ethProvider.getNetwork();
26+
return Number(network.chainId);
27+
} else if (ethProvider instanceof AbstractSigner) {
28+
const { provider } = ethProvider;
29+
if (!provider) {
30+
throw Error('Signer is not connected to a provider');
31+
}
32+
const network = await provider.getNetwork();
33+
return Number(network.chainId);
34+
} else if ('request' in ethProvider) {
35+
const provider = new BrowserProvider(ethProvider as Eip1193Provider);
36+
const network = await provider.getNetwork();
37+
return Number(network.chainId);
38+
}
39+
} catch (e) {
40+
console.warn('Failed to detect chainId:', e);
41+
}
42+
return DEFAULT_CHAIN_ID;
43+
}

src/utils/ipfs-service.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { create } from 'kubo-rpc-client';
2-
import { IPFS_UPLOAD_URL, DEFAULT_IPFS_GATEWAY } from '../config/config.js';
32

43
interface GetOptions {
54
ipfsGateway?: string;
@@ -8,10 +7,7 @@ interface AddOptions extends GetOptions {
87
ipfsNode?: string;
98
}
109

11-
const get = async (
12-
cid,
13-
{ ipfsGateway = DEFAULT_IPFS_GATEWAY }: GetOptions = {}
14-
) => {
10+
const get = async (cid, { ipfsGateway }: GetOptions = {}) => {
1511
const multiaddr = `/ipfs/${cid.toString()}`;
1612
const publicUrl = `${ipfsGateway}${multiaddr}`;
1713
const res = await fetch(publicUrl);
@@ -22,13 +18,7 @@ const get = async (
2218
return new Uint8Array(arrayBuffer);
2319
};
2420

25-
const add = async (
26-
content,
27-
{
28-
ipfsNode = IPFS_UPLOAD_URL,
29-
ipfsGateway = DEFAULT_IPFS_GATEWAY,
30-
}: AddOptions = {}
31-
) => {
21+
const add = async (content, { ipfsNode, ipfsGateway }: AddOptions = {}) => {
3222
const ipfsClient = create(ipfsNode);
3323
const { cid } = await ipfsClient.add(content);
3424
await get(cid.toString(), { ipfsGateway });

src/web3mail/IExecWeb3mail.ts

Lines changed: 122 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AbstractProvider, AbstractSigner, Eip1193Provider } from 'ethers';
22
import { IExec } from 'iexec';
3+
import { GraphQLClient } from 'graphql-request';
34
import { fetchUserContacts } from './fetchUserContacts.js';
45
import { fetchMyContacts } from './fetchMyContacts.js';
56
import { sendEmail } from './sendEmail.js';
@@ -13,66 +14,75 @@ import {
1314
Web3SignerProvider,
1415
FetchMyContactsParams,
1516
} from './types.js';
16-
import { GraphQLClient } from 'graphql-request';
17-
import {
18-
WEB3_MAIL_DAPP_ADDRESS,
19-
IPFS_UPLOAD_URL,
20-
DEFAULT_IPFS_GATEWAY,
21-
DATAPROTECTOR_SUBGRAPH_ENDPOINT,
22-
WHITELIST_SMART_CONTRACT_ADDRESS,
23-
} from '../config/config.js';
17+
import { CHAIN_CONFIG } from '../config/config.js';
2418
import { isValidProvider } from '../utils/validators.js';
19+
import { getChainIdFromProvider } from '../utils/getChainId.js';
20+
21+
type EthersCompatibleProvider =
22+
| AbstractProvider
23+
| AbstractSigner
24+
| Eip1193Provider
25+
| Web3SignerProvider
26+
| string;
27+
28+
interface Web3mailResolvedConfig {
29+
dappAddressOrENS: AddressOrENS;
30+
dappWhitelistAddress: AddressOrENS;
31+
graphQLClient: GraphQLClient;
32+
ipfsNode: string;
33+
ipfsGateway: string;
34+
defaultWorkerpool: string;
35+
iexec: IExec;
36+
}
2537

2638
export class IExecWeb3mail {
27-
private iexec: IExec;
39+
private dappAddressOrENS!: AddressOrENS;
40+
41+
private dappWhitelistAddress!: AddressOrENS;
42+
43+
private graphQLClient!: GraphQLClient;
44+
45+
private ipfsNode!: string;
2846

29-
private ipfsNode: string;
47+
private ipfsGateway!: string;
3048

31-
private ipfsGateway: string;
49+
private defaultWorkerpool!: string;
3250

33-
private dataProtectorSubgraph: string;
51+
private iexec!: IExec;
3452

35-
private dappAddressOrENS: AddressOrENS;
53+
private initPromise: Promise<void> | null = null;
3654

37-
private dappWhitelistAddress: AddressOrENS;
55+
private ethProvider: EthersCompatibleProvider;
3856

39-
private graphQLClient: GraphQLClient;
57+
private options: Web3MailConfigOptions;
4058

4159
constructor(
42-
ethProvider?:
43-
| Eip1193Provider
44-
| AbstractProvider
45-
| AbstractSigner
46-
| Web3SignerProvider
47-
| string,
60+
ethProvider?: EthersCompatibleProvider,
4861
options?: Web3MailConfigOptions
4962
) {
50-
try {
51-
this.iexec = new IExec(
52-
{ ethProvider: ethProvider || 'bellecour' },
53-
options?.iexecOptions
54-
);
55-
} catch (e) {
56-
throw Error('Unsupported ethProvider');
57-
}
63+
this.ethProvider = ethProvider || 'bellecour';
64+
this.options = options || {};
65+
}
5866

59-
try {
60-
this.dataProtectorSubgraph =
61-
options?.dataProtectorSubgraph || DATAPROTECTOR_SUBGRAPH_ENDPOINT;
62-
this.graphQLClient = new GraphQLClient(this.dataProtectorSubgraph);
63-
} catch (e) {
64-
throw Error('Impossible to create GraphQLClient');
67+
async init(): Promise<void> {
68+
if (!this.initPromise) {
69+
this.initPromise = this.resolveConfig().then((config) => {
70+
this.dappAddressOrENS = config.dappAddressOrENS;
71+
this.dappWhitelistAddress = config.dappWhitelistAddress;
72+
this.graphQLClient = config.graphQLClient;
73+
this.ipfsNode = config.ipfsNode;
74+
this.ipfsGateway = config.ipfsGateway;
75+
this.defaultWorkerpool = config.defaultWorkerpool;
76+
this.iexec = config.iexec;
77+
});
6578
}
66-
67-
this.dappAddressOrENS = options?.dappAddressOrENS || WEB3_MAIL_DAPP_ADDRESS;
68-
this.ipfsNode = options?.ipfsNode || IPFS_UPLOAD_URL;
69-
this.ipfsGateway = options?.ipfsGateway || DEFAULT_IPFS_GATEWAY;
70-
this.dappWhitelistAddress =
71-
options?.dappWhitelistAddress || WHITELIST_SMART_CONTRACT_ADDRESS;
79+
return this.initPromise;
7280
}
7381

7482
async fetchMyContacts(args?: FetchMyContactsParams): Promise<Contact[]> {
83+
await this.init();
7584
await isValidProvider(this.iexec);
85+
7686
return fetchMyContacts({
7787
...args,
7888
iexec: this.iexec,
@@ -82,7 +92,9 @@ export class IExecWeb3mail {
8292
});
8393
}
8494

85-
fetchUserContacts(args: FetchUserContactsParams): Promise<Contact[]> {
95+
async fetchUserContacts(args: FetchUserContactsParams): Promise<Contact[]> {
96+
await this.init();
97+
8698
return fetchUserContacts({
8799
...args,
88100
iexec: this.iexec,
@@ -93,9 +105,12 @@ export class IExecWeb3mail {
93105
}
94106

95107
async sendEmail(args: SendEmailParams): Promise<SendEmailResponse> {
108+
await this.init();
96109
await isValidProvider(this.iexec);
97110
return sendEmail({
98111
...args,
112+
workerpoolAddressOrEns:
113+
args.workerpoolAddressOrEns || this.defaultWorkerpool,
99114
iexec: this.iexec,
100115
ipfsNode: this.ipfsNode,
101116
ipfsGateway: this.ipfsGateway,
@@ -105,4 +120,69 @@ export class IExecWeb3mail {
105120
useVoucher: args?.useVoucher,
106121
});
107122
}
123+
124+
private async resolveConfig(): Promise<Web3mailResolvedConfig> {
125+
const chainId = await getChainIdFromProvider(this.ethProvider);
126+
const chainDefaultConfig = CHAIN_CONFIG[chainId];
127+
128+
const subgraphUrl =
129+
this.options?.dataProtectorSubgraph ||
130+
chainDefaultConfig?.dataProtectorSubgraph;
131+
const dappAddressOrENS =
132+
this.options?.dappAddressOrENS || chainDefaultConfig?.dappAddress;
133+
const dappWhitelistAddress =
134+
this.options?.dappWhitelistAddress ||
135+
chainDefaultConfig?.whitelistSmartContract;
136+
const ipfsGateway =
137+
this.options?.ipfsGateway || chainDefaultConfig?.ipfsGateway;
138+
const defaultWorkerpool = chainDefaultConfig?.prodWorkerpoolAddress;
139+
const ipfsNode =
140+
this.options?.ipfsNode || chainDefaultConfig?.ipfsUploadUrl;
141+
142+
const missing = [];
143+
if (!subgraphUrl) missing.push('dataProtectorSubgraph');
144+
if (!dappAddressOrENS) missing.push('dappAddress');
145+
if (!dappWhitelistAddress) missing.push('whitelistSmartContract');
146+
if (!ipfsGateway) missing.push('ipfsGateway');
147+
if (!defaultWorkerpool) missing.push('prodWorkerpoolAddress');
148+
if (!ipfsNode) missing.push('ipfsUploadUrl');
149+
150+
if (missing.length > 0) {
151+
throw new Error(
152+
`Missing required configuration for chainId ${chainId}: ${missing.join(
153+
', '
154+
)}`
155+
);
156+
}
157+
158+
let iexec: IExec, graphQLClient: GraphQLClient;
159+
160+
try {
161+
iexec = new IExec(
162+
{ ethProvider: this.ethProvider },
163+
{
164+
ipfsGatewayURL: ipfsGateway,
165+
...this.options?.iexecOptions,
166+
}
167+
);
168+
} catch (e: any) {
169+
throw new Error(`Unsupported ethProvider: ${e.message}`);
170+
}
171+
172+
try {
173+
graphQLClient = new GraphQLClient(subgraphUrl);
174+
} catch (error: any) {
175+
throw new Error(`Failed to create GraphQLClient: ${error.message}`);
176+
}
177+
178+
return {
179+
dappAddressOrENS,
180+
dappWhitelistAddress: dappWhitelistAddress.toLowerCase(),
181+
defaultWorkerpool,
182+
graphQLClient,
183+
ipfsNode,
184+
ipfsGateway,
185+
iexec,
186+
};
187+
}
108188
}

src/web3mail/sendEmail.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
MAX_DESIRED_APP_ORDER_PRICE,
55
MAX_DESIRED_DATA_ORDER_PRICE,
66
MAX_DESIRED_WORKERPOOL_ORDER_PRICE,
7-
PROD_WORKERPOOL_ADDRESS,
87
} from '../config/config.js';
98
import { handleIfProtocolError, WorkflowError } from '../utils/errors.js';
109
import { generateSecureUniqueId } from '../utils/generateUniqueId.js';
@@ -42,7 +41,7 @@ export type SendEmail = typeof sendEmail;
4241
export const sendEmail = async ({
4342
graphQLClient = throwIfMissing(),
4443
iexec = throwIfMissing(),
45-
workerpoolAddressOrEns = PROD_WORKERPOOL_ADDRESS,
44+
workerpoolAddressOrEns,
4645
dappAddressOrENS,
4746
dappWhitelistAddress,
4847
ipfsNode,

0 commit comments

Comments
 (0)