Skip to content

Commit a6ddcae

Browse files
nkoreligiorgi-kiknavelidzeMantisClonecoderabbitai[bot]
authored
feat: Adding private wallet functionality with the integration tests (#1482)
Co-authored-by: giorgi-kiknavelidze <69247736+giorgi-kiknavelidze@users.noreply.github.com> Co-authored-by: MantisClone <david.huntmateo@request.network> Co-authored-by: MantisClone <david.hunt-mateo@mantisdata.solutions> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 4344d28 commit a6ddcae

File tree

7 files changed

+743
-6
lines changed

7 files changed

+743
-6
lines changed

.circleci/config.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,21 @@ jobs:
266266
- store_test_results:
267267
path: packages/integration-test/reports/
268268

269+
test-monthly:
270+
docker:
271+
- *node_image
272+
working_directory: *working_directory
273+
resource_class: large
274+
steps:
275+
- attach_workspace:
276+
at: *working_directory
277+
- run:
278+
name: 'Test payment-processor (hinkal)'
279+
no_output_timeout: 30m
280+
command: 'yarn workspace @requestnetwork/payment-processor run test:hinkal'
281+
- store_test_results:
282+
path: packages/payment-processor/reports/
283+
269284
# Release a next version package everytime we merge to master
270285
next-release:
271286
docker:
@@ -349,3 +364,19 @@ workflows:
349364
- test-nightly:
350365
requires:
351366
- build
367+
368+
monthly:
369+
triggers:
370+
- schedule:
371+
# This is a cron job for "every 1st day of the month at 22 hours"
372+
cron: '0 22 1 * *'
373+
filters:
374+
branches:
375+
only:
376+
- master
377+
378+
jobs:
379+
- build
380+
- test-monthly:
381+
requires:
382+
- build

packages/payment-processor/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535
"lint": "eslint . --fix",
3636
"lint:check": "eslint .",
3737
"prepare": "yarn run build",
38-
"test": "jest --runInBand",
38+
"test": "jest --testPathIgnorePatterns test/payment/erc-20-private-payment-hinkal.test.ts --runInBand",
39+
"test:hinkal": "jest test/payment/erc-20-private-payment-hinkal.test.ts --runInBand",
3940
"test:watch": "yarn test --watch"
4041
},
4142
"dependencies": {
43+
"@hinkal/common": "0.2.7",
4244
"@openzeppelin/contracts": "4.9.6",
4345
"@requestnetwork/currency": "0.23.0",
4446
"@requestnetwork/payment-detection": "0.49.0",

packages/payment-processor/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './payment/btc-address-based';
33
export * from './payment/erc20';
44
export * from './payment/erc20-proxy';
55
export * from './payment/erc20-fee-proxy';
6+
export * from './payment/erc-20-private-payment-hinkal';
67
export * from './payment/erc777-stream';
78
export * from './payment/erc777-utils';
89
export * from './payment/eth-input-data';
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { Contract, ContractTransaction, Signer, BigNumberish, providers, BigNumber } from 'ethers';
2+
import { erc20FeeProxyArtifact } from '@requestnetwork/smart-contracts';
3+
import {
4+
ERC20FeeProxy__factory,
5+
ERC20Proxy__factory,
6+
ERC20__factory,
7+
} from '@requestnetwork/smart-contracts/types';
8+
import { ClientTypes, ExtensionTypes } from '@requestnetwork/types';
9+
import { Erc20PaymentNetwork, getPaymentNetworkExtension } from '@requestnetwork/payment-detection';
10+
import { EvmChains } from '@requestnetwork/currency';
11+
import {
12+
getAmountToPay,
13+
getProvider,
14+
getRequestPaymentValues,
15+
getSigner,
16+
validateRequest,
17+
validateErc20FeeProxyRequest,
18+
getProxyAddress,
19+
} from './utils';
20+
import { IPreparedPrivateTransaction } from './prepared-transaction';
21+
22+
import { emporiumOp, IHinkal, RelayerTransaction } from '@hinkal/common';
23+
import { prepareEthersHinkal } from '@hinkal/common/providers/prepareEthersHinkal';
24+
25+
/**
26+
* This is a globally accessible state variable exported for use in other parts of the application or tests.
27+
*/
28+
export const hinkalStore: Record<string, IHinkal> = {};
29+
30+
/**
31+
* Adds an IHinkal instance to the Hinkal store for a given signer.
32+
*
33+
* This function checks if an IHinkal instance already exists for the provided signer’s address in the `hinkalStore`.
34+
* If it does not exist, it initializes the instance using `prepareEthersHinkal` and stores it. The existing or newly
35+
* created instance is then returned.
36+
*
37+
* @param signer - The signer for which the IHinkal instance should be added or retrieved.
38+
*/
39+
export async function addToHinkalStore(signer: Signer): Promise<IHinkal> {
40+
const address = await signer.getAddress();
41+
if (!hinkalStore[address]) {
42+
hinkalStore[address] = await prepareEthersHinkal(signer);
43+
}
44+
return hinkalStore[address];
45+
}
46+
47+
/**
48+
* Sends ERC20 tokens into a Hinkal shielded address.
49+
*
50+
* @param signerOrProvider the Web3 provider, or signer.
51+
* @param tokenAddress - The address of the ERC20 token being sent.
52+
* @param amount - The amount of tokens to send.
53+
* @param recipientInfo - (Optional) The shielded address of the recipient. If provided, the tokens will be deposited into the recipient's shielded balance. If not provided, the deposit will increase the sender's shielded balance.
54+
*
55+
* @returns A promise that resolves to a `ContractTransaction`.
56+
*/
57+
export async function sendToHinkalShieldedAddressFromPublic(
58+
signerOrProvider: providers.Provider | Signer = getProvider(),
59+
tokenAddress: string,
60+
amount: BigNumberish,
61+
recipientInfo?: string,
62+
): Promise<ContractTransaction> {
63+
const signer = getSigner(signerOrProvider);
64+
const hinkalObject = await addToHinkalStore(signer);
65+
66+
const amountToPay = BigNumber.from(amount).toBigInt();
67+
if (recipientInfo) {
68+
return hinkalObject.depositForOther([tokenAddress], [amountToPay], recipientInfo);
69+
} else {
70+
return hinkalObject.deposit([tokenAddress], [amountToPay]);
71+
}
72+
}
73+
74+
/**
75+
* Processes a transaction to pay privately a request through the ERC20 fee proxy contract.
76+
* @param request request to pay.
77+
* @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
78+
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
79+
*/
80+
export async function payErc20ProxyRequestFromHinkalShieldedAddress(
81+
request: ClientTypes.IRequestData,
82+
signerOrProvider: providers.Provider | Signer = getProvider(),
83+
amount?: BigNumberish,
84+
): Promise<RelayerTransaction> {
85+
const signer = getSigner(signerOrProvider);
86+
const hinkalObject = await addToHinkalStore(signer);
87+
88+
const { amountToPay, tokenAddress, ops } = prepareErc20ProxyPaymentFromHinkalShieldedAddress(
89+
request,
90+
amount,
91+
);
92+
93+
return hinkalObject.actionPrivateWallet(
94+
[tokenAddress],
95+
[-amountToPay],
96+
[false],
97+
ops,
98+
) as Promise<RelayerTransaction>;
99+
}
100+
101+
/**
102+
* Processes a transaction to pay privately a request through the ERC20 fee proxy.
103+
* @param request request to pay.
104+
* @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
105+
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
106+
* @param feeAmount optionally, the fee amount to pay. Defaults to the fee amount of the request.
107+
*/
108+
export async function payErc20FeeProxyRequestFromHinkalShieldedAddress(
109+
request: ClientTypes.IRequestData,
110+
signerOrProvider: providers.Provider | Signer = getProvider(),
111+
amount?: BigNumberish,
112+
feeAmount?: BigNumberish,
113+
): Promise<RelayerTransaction> {
114+
const signer = getSigner(signerOrProvider);
115+
const hinkalObject = await addToHinkalStore(signer);
116+
117+
const { amountToPay, tokenAddress, ops } = prepareErc20FeeProxyPaymentFromHinkalShieldedAddress(
118+
request,
119+
amount,
120+
feeAmount,
121+
);
122+
123+
return hinkalObject.actionPrivateWallet(
124+
[tokenAddress],
125+
[-amountToPay],
126+
[false],
127+
ops,
128+
) as Promise<RelayerTransaction>;
129+
}
130+
131+
/**
132+
* Prepare the transaction to privately pay a request through the ERC20 proxy contract, can be used with a Multisig contract.
133+
* @param request request to pay
134+
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
135+
*/
136+
export function prepareErc20ProxyPaymentFromHinkalShieldedAddress(
137+
request: ClientTypes.IRequestData,
138+
amount?: BigNumberish,
139+
): IPreparedPrivateTransaction {
140+
validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_PROXY_CONTRACT);
141+
142+
const { value: tokenAddress } = request.currencyInfo;
143+
const proxyAddress = getProxyAddress(
144+
request,
145+
Erc20PaymentNetwork.ERC20ProxyPaymentDetector.getDeploymentInformation,
146+
);
147+
148+
const tokenContract = new Contract(tokenAddress, ERC20__factory.createInterface());
149+
const proxyContract = new Contract(proxyAddress, ERC20Proxy__factory.createInterface());
150+
151+
const { paymentReference, paymentAddress } = getRequestPaymentValues(request);
152+
const amountToPay = getAmountToPay(request, amount);
153+
154+
const ops = [
155+
emporiumOp(tokenContract, 'approve', [proxyContract.address, amountToPay]),
156+
emporiumOp(proxyContract, 'transferFromWithReference', [
157+
tokenAddress,
158+
paymentAddress,
159+
amountToPay,
160+
`0x${paymentReference}`,
161+
]),
162+
];
163+
164+
return {
165+
amountToPay: amountToPay.toBigInt(),
166+
tokenAddress,
167+
ops,
168+
};
169+
}
170+
171+
/**
172+
* Prepare the transaction to privately pay a request through the ERC20 fee proxy contract, can be used with a Multisig contract.
173+
* @param request request to pay
174+
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
175+
* @param feeAmountOverride optionally, the fee amount to pay. Defaults to the fee amount of the request.
176+
*/
177+
export function prepareErc20FeeProxyPaymentFromHinkalShieldedAddress(
178+
request: ClientTypes.IRequestData,
179+
amount?: BigNumberish,
180+
feeAmountOverride?: BigNumberish,
181+
): IPreparedPrivateTransaction {
182+
validateErc20FeeProxyRequest(request, amount, feeAmountOverride);
183+
184+
const { value: tokenAddress, network } = request.currencyInfo;
185+
EvmChains.assertChainSupported(network!);
186+
const pn = getPaymentNetworkExtension(request);
187+
const proxyAddress = erc20FeeProxyArtifact.getAddress(network, pn?.version);
188+
189+
const tokenContract = new Contract(tokenAddress, ERC20__factory.createInterface());
190+
const proxyContract = new Contract(proxyAddress, ERC20FeeProxy__factory.createInterface());
191+
192+
const { paymentReference, paymentAddress, feeAddress, feeAmount } =
193+
getRequestPaymentValues(request);
194+
const amountToPay = getAmountToPay(request, amount);
195+
const feeToPay = String(feeAmountOverride || feeAmount || 0);
196+
const totalAmount = amountToPay.add(BigNumber.from(feeToPay));
197+
198+
const ops = [
199+
emporiumOp(tokenContract, 'approve', [proxyContract.address, totalAmount]),
200+
emporiumOp(proxyContract, 'transferFromWithReferenceAndFee', [
201+
tokenAddress,
202+
paymentAddress,
203+
amountToPay,
204+
`0x${paymentReference}`,
205+
feeToPay,
206+
feeAddress,
207+
]),
208+
];
209+
210+
return {
211+
amountToPay: totalAmount.toBigInt(),
212+
tokenAddress,
213+
ops,
214+
};
215+
}

packages/payment-processor/src/payment/prepared-transaction.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,13 @@ export interface IPreparedTransaction {
55
data: string;
66
to: string;
77
}
8+
9+
/** Interface for preparing private transactions using Hinkal middleware */
10+
export interface IPreparedPrivateTransaction {
11+
/** Amount to pay in base units (e.g., wei for ETH, smallest decimal unit for ERC20 tokens based on their decimals()) */
12+
amountToPay: bigint;
13+
/** ERC20 token contract address */
14+
tokenAddress: string;
15+
/** list of operations encoded as HexStrings */
16+
ops: string[];
17+
}

0 commit comments

Comments
 (0)