Skip to content

Commit 2ae31e3

Browse files
feat(sendEmail): add allowDeposit parameter to enable automatic deposit
- Add allowDeposit optional parameter to SendEmailParams type - Add allowDeposit parameter to sendEmail function with default value false - Pass allowDeposit to matchOrders call - Update e2e tests for allowDeposit feature This feature allows users to automatically deposit funds when their balance is insufficient to cover the task cost.
1 parent 2b6914f commit 2ae31e3

File tree

3 files changed

+98
-15
lines changed

3 files changed

+98
-15
lines changed

src/web3mail/sendEmail.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ import {
2222
senderNameSchema,
2323
throwIfMissing,
2424
} from '../utils/validators.js';
25-
import {
26-
checkUserVoucher,
27-
filterWorkerpoolOrders,
28-
} from './sendEmail.models.js';
29-
import { SendEmailParams, SendEmailResponse } from './types.js';
3025
import {
3126
DappAddressConsumer,
3227
DappWhitelistAddressConsumer,
@@ -35,6 +30,11 @@ import {
3530
IpfsNodeConfigConsumer,
3631
SubgraphConsumer,
3732
} from './internalTypes.js';
33+
import {
34+
checkUserVoucher,
35+
filterWorkerpoolOrders,
36+
} from './sendEmail.models.js';
37+
import { SendEmailParams, SendEmailResponse } from './types.js';
3838

3939
export type SendEmail = typeof sendEmail;
4040

@@ -56,6 +56,7 @@ export const sendEmail = async ({
5656
senderName,
5757
protectedData,
5858
useVoucher = false,
59+
allowDeposit = false,
5960
}: IExecConsumer &
6061
SubgraphConsumer &
6162
DappAddressConsumer &
@@ -120,6 +121,10 @@ export const sendEmail = async ({
120121
.label('useVoucher')
121122
.validateSync(useVoucher);
122123

124+
const vAllowDeposit = booleanSchema()
125+
.label('allowDeposit')
126+
.validateSync(allowDeposit);
127+
123128
// Check protected data schema through subgraph
124129
const isValidProtectedData = await checkProtectedDataValidity(
125130
graphQLClient,
@@ -316,14 +321,17 @@ export const sendEmail = async ({
316321
const requestorder = await iexec.order.signRequestorder(requestorderToSign);
317322

318323
// Match orders and compute task ID
324+
// TODO: Remove @ts-ignore once iexec SDK is updated to a version that includes allowDeposit in matchOrders types
325+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
326+
// @ts-ignore - allowDeposit is supported at runtime but not yet in TypeScript types
319327
const { dealid: dealId } = await iexec.order.matchOrders(
320328
{
321329
apporder: apporder,
322330
datasetorder: datasetorder,
323331
workerpoolorder: workerpoolorder,
324332
requestorder: requestorder,
325333
},
326-
{ useVoucher: vUseVoucher }
334+
{ useVoucher: vUseVoucher, allowDeposit: vAllowDeposit }
327335
);
328336

329337
const taskId = await iexec.deal.computeTaskId(dealId, 0);

src/web3mail/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import type { BulkRequest } from '@iexec/dataprotector';
12
import { EnhancedWallet } from 'iexec';
23
import { IExecConfigOptions } from 'iexec/IExecConfig';
3-
import type { BulkRequest } from '@iexec/dataprotector';
44

55
export type Web3SignerProvider = EnhancedWallet;
66

@@ -64,6 +64,7 @@ export type SendEmailParams = {
6464
appMaxPrice?: number;
6565
workerpoolMaxPrice?: number;
6666
useVoucher?: boolean;
67+
allowDeposit?: boolean;
6768
};
6869

6970
export type FetchMyContactsParams = {

tests/e2e/sendEmail.test.ts

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import {
44
} from '@iexec/dataprotector';
55
import { beforeAll, describe, expect, it } from '@jest/globals';
66
import { HDNodeWallet } from 'ethers';
7+
import { IExec } from 'iexec';
8+
import { NULL_ADDRESS } from 'iexec/utils';
9+
import {
10+
DEFAULT_CHAIN_ID,
11+
getChainDefaultConfig,
12+
} from '../../src/config/config.js';
713
import { IExecWeb3mail, WorkflowError } from '../../src/index.js';
814
import {
915
MAX_EXPECTED_BLOCKTIME,
@@ -22,12 +28,6 @@ import {
2228
getTestWeb3SignerProvider,
2329
waitSubgraphIndexing,
2430
} from '../test-utils.js';
25-
import { IExec } from 'iexec';
26-
import { NULL_ADDRESS } from 'iexec/utils';
27-
import {
28-
DEFAULT_CHAIN_ID,
29-
getChainDefaultConfig,
30-
} from '../../src/config/config.js';
3131

3232
describe('web3mail.sendEmail()', () => {
3333
let consumerWallet: HDNodeWallet;
@@ -41,14 +41,14 @@ describe('web3mail.sendEmail()', () => {
4141
let learnProdWorkerpoolAddress: string;
4242
const iexecOptions = getTestIExecOption();
4343
const prodWorkerpoolPublicPrice = 1000;
44-
44+
const workerpoolprice = 1_000;
4545
beforeAll(async () => {
4646
// (default) prod workerpool (not free) always available
4747
await createAndPublishWorkerpoolOrder(
4848
TEST_CHAIN.prodWorkerpool,
4949
TEST_CHAIN.prodWorkerpoolOwnerWallet,
5050
NULL_ADDRESS,
51-
1_000,
51+
workerpoolprice,
5252
prodWorkerpoolPublicPrice
5353
);
5454
// learn prod pool (free) assumed always available
@@ -657,4 +657,78 @@ describe('web3mail.sendEmail()', () => {
657657
});
658658
});
659659
});
660+
661+
describe('allowDeposit', () => {
662+
let protectData: ProtectedDataWithSecretProps;
663+
consumerWallet = getRandomWallet();
664+
const dataPricePerAccess = 1000;
665+
let web3mailConsumerInstance: IExecWeb3mail;
666+
beforeAll(async () => {
667+
protectData = await dataProtector.protectData({
668+
data: { email: '[email protected]' },
669+
name: 'test do not use',
670+
});
671+
await dataProtector.grantAccess({
672+
authorizedApp: getChainDefaultConfig(DEFAULT_CHAIN_ID).dappAddress,
673+
protectedData: protectData.address,
674+
authorizedUser: consumerWallet.address, // consumer wallet
675+
numberOfAccess: 1000,
676+
pricePerAccess: dataPricePerAccess,
677+
});
678+
await waitSubgraphIndexing();
679+
web3mailConsumerInstance = new IExecWeb3mail(
680+
...getTestConfig(consumerWallet.privateKey)
681+
);
682+
}, 2 * MAX_EXPECTED_BLOCKTIME);
683+
it(
684+
'should throw error if insufficient total balance to cover task cost and allowDeposit is false',
685+
async () => {
686+
let error;
687+
try {
688+
await web3mailConsumerInstance.sendEmail({
689+
emailSubject: 'e2e mail object for test',
690+
emailContent: 'e2e mail content for test',
691+
protectedData: protectData.address,
692+
dataMaxPrice: dataPricePerAccess,
693+
workerpoolMaxPrice: workerpoolprice,
694+
allowDeposit: false,
695+
});
696+
} catch (err) {
697+
error = err;
698+
}
699+
expect(error).toBeInstanceOf(WorkflowError);
700+
expect(error).toBeInstanceOf(Error);
701+
expect(error.message).toBe('Failed to sendEmail');
702+
const causeMsg =
703+
error.errorCause?.message ||
704+
error.cause?.message ||
705+
error.cause ||
706+
error.errorCause;
707+
expect(causeMsg).toBe(
708+
`Cost per task (${
709+
dataPricePerAccess + workerpoolprice
710+
}) is greater than requester account stake (0). Orders can't be matched. If you are the requester, you should deposit to top up your account`
711+
);
712+
},
713+
3 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
714+
);
715+
716+
it(
717+
'should send email after depositing sufficient funds to cover task cost when allowDeposit is true',
718+
async () => {
719+
const result = await web3mailConsumerInstance.sendEmail({
720+
emailSubject: 'e2e mail object for test',
721+
emailContent: 'e2e mail content for test',
722+
protectedData: protectData.address,
723+
dataMaxPrice: dataPricePerAccess,
724+
workerpoolMaxPrice: workerpoolprice,
725+
allowDeposit: true,
726+
});
727+
expect(result).toBeDefined();
728+
expect(result).toHaveProperty('taskId');
729+
expect(result).toHaveProperty('dealId');
730+
},
731+
3 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
732+
);
733+
});
660734
});

0 commit comments

Comments
 (0)