Skip to content

Commit 66efcd6

Browse files
feat(web3mail): add bulk email processing support
- Add bulk processing capabilities to sendEmail function - Update subgraphQuery to support batch operations - Add new sendEmailBulk test suite - Update fetchMyContacts and fetchUserContacts for bulk support - Extend internal types and types definitions for bulk operations - Update package dependencies
1 parent 12c0ca4 commit 66efcd6

17 files changed

+4685
-1271
lines changed

package-lock.json

Lines changed: 3268 additions & 886 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
@@ -49,15 +49,15 @@
4949
"dependencies": {
5050
"@ethersproject/bytes": "^5.7.0",
5151
"@ethersproject/random": "^5.7.0",
52+
"@iexec/dataprotector": "2.0.0-beta.20-feat-add-bulk-processing-support-094df1e",
5253
"buffer": "^6.0.3",
5354
"ethers": "^6.13.2",
5455
"graphql-request": "^6.1.0",
55-
"iexec": "^8.20.0",
56+
"iexec": "8.20.0-feat-bulk-processing-62e7c5f",
5657
"kubo-rpc-client": "^4.1.1",
5758
"yup": "^1.1.1"
5859
},
5960
"devDependencies": {
60-
"@iexec/dataprotector": "^2.0.0-beta.19",
6161
"@jest/globals": "^29.7.0",
6262
"@swc/core": "^1.3.96",
6363
"@swc/jest": "^0.2.29",

src/utils/subgraphQuery.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,24 @@ export const getValidContact = async (
7171
);
7272

7373
// Convert protectedData[] into Contact[] using the map for constant time lookups
74-
return protectedDataList.map(({ id, name }) => {
75-
const contact = contactsMap.get(id);
76-
if (contact) {
77-
return {
78-
address: id,
79-
name: name,
80-
remainingAccess: contact.remainingAccess,
81-
accessPrice: contact.accessPrice,
82-
owner: contact.owner,
83-
accessGrantTimestamp: contact.accessGrantTimestamp,
84-
isUserStrict: contact.isUserStrict,
85-
};
86-
}
87-
});
74+
return protectedDataList
75+
.map(({ id, name }) => {
76+
const contact = contactsMap.get(id);
77+
if (contact) {
78+
return {
79+
address: id,
80+
name: name,
81+
remainingAccess: contact.remainingAccess,
82+
accessPrice: contact.accessPrice,
83+
owner: contact.owner,
84+
accessGrantTimestamp: contact.accessGrantTimestamp,
85+
isUserStrict: contact.isUserStrict,
86+
grantedAccess: contact.grantedAccess,
87+
};
88+
}
89+
return null;
90+
})
91+
.filter((contact) => contact !== null && contact !== undefined);
8892
} catch (error) {
8993
throw new WorkflowError({
9094
message: 'Failed to fetch subgraph',

src/web3mail/IExecWeb3mail.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { AbstractProvider, AbstractSigner, Eip1193Provider } from 'ethers';
22
import { IExec } from 'iexec';
3+
import {
4+
IExecDataProtectorCore,
5+
ProcessBulkRequestResponse,
6+
} from '@iexec/dataprotector';
37
import { GraphQLClient } from 'graphql-request';
48
import { fetchUserContacts } from './fetchUserContacts.js';
59
import { fetchMyContacts } from './fetchMyContacts.js';
@@ -10,7 +14,7 @@ import {
1014
SendEmailParams,
1115
AddressOrENS,
1216
Web3MailConfigOptions,
13-
SendEmailResponse,
17+
SendEmailSingleResponse,
1418
Web3SignerProvider,
1519
FetchMyContactsParams,
1620
} from './types.js';
@@ -34,6 +38,7 @@ interface Web3mailResolvedConfig {
3438
ipfsGateway: string;
3539
defaultWorkerpool: string;
3640
iexec: IExec;
41+
dataProtector: IExecDataProtectorCore;
3742
}
3843

3944
export class IExecWeb3mail {
@@ -51,6 +56,8 @@ export class IExecWeb3mail {
5156

5257
private iexec!: IExec;
5358

59+
private dataProtector!: IExecDataProtectorCore;
60+
5461
private initPromise: Promise<void> | null = null;
5562

5663
private ethProvider: EthersCompatibleProvider;
@@ -75,6 +82,7 @@ export class IExecWeb3mail {
7582
this.ipfsGateway = config.ipfsGateway;
7683
this.defaultWorkerpool = config.defaultWorkerpool;
7784
this.iexec = config.iexec;
85+
this.dataProtector = config.dataProtector;
7886
});
7987
}
8088
return this.initPromise;
@@ -105,14 +113,17 @@ export class IExecWeb3mail {
105113
});
106114
}
107115

108-
async sendEmail(args: SendEmailParams): Promise<SendEmailResponse> {
116+
async sendEmail(
117+
args: SendEmailParams
118+
): Promise<ProcessBulkRequestResponse | SendEmailSingleResponse> {
109119
await this.init();
110120
await isValidProvider(this.iexec);
111121
return sendEmail({
112122
...args,
113123
workerpoolAddressOrEns:
114124
args.workerpoolAddressOrEns || this.defaultWorkerpool,
115125
iexec: this.iexec,
126+
dataProtector: this.dataProtector,
116127
ipfsNode: this.ipfsNode,
117128
ipfsGateway: this.ipfsGateway,
118129
dappAddressOrENS: this.dappAddressOrENS,
@@ -184,6 +195,17 @@ export class IExecWeb3mail {
184195
throw new Error(`Failed to create GraphQLClient: ${error.message}`);
185196
}
186197

198+
const dataProtector = new IExecDataProtectorCore(this.ethProvider, {
199+
iexecOptions: {
200+
ipfsGatewayURL: ipfsGateway,
201+
...this.options?.iexecOptions,
202+
allowExperimentalNetworks: this.options.allowExperimentalNetworks,
203+
},
204+
ipfsGateway,
205+
ipfsNode,
206+
subgraphUrl,
207+
});
208+
187209
return {
188210
dappAddressOrENS,
189211
dappWhitelistAddress: dappWhitelistAddress.toLowerCase(),
@@ -192,6 +214,7 @@ export class IExecWeb3mail {
192214
ipfsNode,
193215
ipfsGateway,
194216
iexec,
217+
dataProtector,
195218
};
196219
}
197220
}

src/web3mail/fetchMyContacts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const fetchMyContacts = async ({
1616
dappAddressOrENS = throwIfMissing(),
1717
dappWhitelistAddress = throwIfMissing(),
1818
isUserStrict = false,
19+
bulkOnly = false,
1920
}: IExecConsumer &
2021
SubgraphConsumer &
2122
DappAddressConsumer &
@@ -33,5 +34,6 @@ export const fetchMyContacts = async ({
3334
dappWhitelistAddress,
3435
userAddress,
3536
isUserStrict: vIsUserStrict,
37+
bulkOnly,
3638
});
3739
};

src/web3mail/fetchUserContacts.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
isEnsTest,
1313
throwIfMissing,
1414
} from '../utils/validators.js';
15-
import { Contact, FetchUserContactsParams } from './types.js';
15+
import { Contact, FetchUserContactsParams, GrantedAccess } from './types.js';
1616
import {
1717
DappAddressConsumer,
1818
DappWhitelistAddressConsumer,
@@ -27,6 +27,7 @@ export const fetchUserContacts = async ({
2727
dappWhitelistAddress = throwIfMissing(),
2828
userAddress,
2929
isUserStrict = false,
30+
bulkOnly = false,
3031
}: IExecConsumer &
3132
SubgraphConsumer &
3233
DappAddressConsumer &
@@ -47,6 +48,7 @@ export const fetchUserContacts = async ({
4748
const vIsUserStrict = booleanSchema()
4849
.label('isUserStrict')
4950
.validateSync(isUserStrict);
51+
const vBulkOnly = booleanSchema().label('bulkOnly').validateSync(bulkOnly);
5052

5153
try {
5254
const [dappOrders, whitelistOrders] = await Promise.all([
@@ -55,15 +57,16 @@ export const fetchUserContacts = async ({
5557
userAddress: vUserAddress,
5658
appAddress: vDappAddressOrENS,
5759
isUserStrict: vIsUserStrict,
60+
bulkOnly: vBulkOnly,
5861
}),
5962
fetchAllOrdersByApp({
6063
iexec,
6164
userAddress: vUserAddress,
6265
appAddress: vDappWhitelistAddress,
6366
isUserStrict: vIsUserStrict,
67+
bulkOnly: vBulkOnly,
6468
}),
6569
]);
66-
6770
const orders = dappOrders.concat(whitelistOrders);
6871
const myContacts: Omit<Contact, 'name'>[] = [];
6972
let web3DappResolvedAddress = vDappAddressOrENS;
@@ -77,13 +80,25 @@ export const fetchUserContacts = async ({
7780
order.order.apprestrict.toLowerCase() ===
7881
vDappWhitelistAddress.toLowerCase()
7982
) {
80-
const contact = {
83+
const contact: Contact = {
8184
address: order.order.dataset.toLowerCase(),
8285
owner: order.signer.toLowerCase(),
8386
remainingAccess: order.remaining,
8487
accessPrice: order.order.datasetprice,
8588
accessGrantTimestamp: order.publicationTimestamp,
8689
isUserStrict: order.order.requesterrestrict !== ZeroAddress,
90+
grantedAccess: {
91+
dataset: order.order.dataset,
92+
datasetprice: order.order.datasetprice.toString(),
93+
volume: order.order.volume.toString(),
94+
tag: order.order.tag,
95+
apprestrict: order.order.apprestrict,
96+
workerpoolrestrict: order.order.workerpoolrestrict,
97+
requesterrestrict: order.order.requesterrestrict,
98+
salt: order.order.salt,
99+
sign: order.order.sign,
100+
remainingAccess: order.remaining,
101+
} as GrantedAccess,
87102
};
88103
myContacts.push(contact);
89104
}
@@ -94,7 +109,6 @@ export const fetchUserContacts = async ({
94109
return await getValidContact(graphQLClient, myContacts);
95110
} catch (error) {
96111
handleIfProtocolError(error);
97-
98112
throw new WorkflowError({
99113
message: 'Failed to fetch user contacts',
100114
errorCause: error,
@@ -107,23 +121,24 @@ async function fetchAllOrdersByApp({
107121
userAddress,
108122
appAddress,
109123
isUserStrict,
124+
bulkOnly,
110125
}: {
111126
iexec: IExec;
112127
userAddress: string;
113128
appAddress: string;
114129
isUserStrict: boolean;
130+
bulkOnly?: boolean;
115131
}): Promise<PublishedDatasetorder[]> {
116-
const ordersFirstPage = iexec.orderbook.fetchDatasetOrderbook(
117-
ANY_DATASET_ADDRESS,
118-
{
119-
app: appAddress,
120-
requester: userAddress,
121-
isAppStrict: true,
122-
isRequesterStrict: isUserStrict,
123-
// Use maxPageSize here to avoid too many round-trips (we want everything anyway)
124-
pageSize: 1000,
125-
}
126-
);
132+
const ordersFirstPage = iexec.orderbook.fetchDatasetOrderbook({
133+
dataset: ANY_DATASET_ADDRESS,
134+
app: appAddress,
135+
requester: userAddress,
136+
isAppStrict: true,
137+
isRequesterStrict: isUserStrict,
138+
bulkOnly,
139+
// Use maxPageSize here to avoid too many round-trips (we want everything anyway)
140+
pageSize: 1000,
141+
});
127142
const { orders: allOrders } = await autoPaginateRequest({
128143
request: ordersFirstPage,
129144
});

src/web3mail/internalTypes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IExec } from 'iexec';
22
import { AddressOrENS } from './types.js';
33
import { GraphQLClient } from 'graphql-request';
4+
import { IExecDataProtectorCore } from '@iexec/dataprotector';
45

56
export type ProtectedDataQuery = {
67
id: string;
@@ -31,6 +32,8 @@ export type IExecConsumer = {
3132
iexec: IExec;
3233
};
3334

35+
export type DataProtectorConsumer = { dataProtector: IExecDataProtectorCore };
36+
3437
export type SubgraphConsumer = {
3538
graphQLClient: GraphQLClient;
3639
};

0 commit comments

Comments
 (0)