Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adding private wallet functionality with the integration tests #1482

Merged
merged 49 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e94fb04
Adding Private Wallet Functionality with Working Example
nkoreli Nov 3, 2024
3bf83a5
Sorting Hinkal Dependencies
nkoreli Nov 3, 2024
25e893a
refactor: address initial comments
giorgi-kiknavelidze Nov 4, 2024
a795657
refactor: address initial comments
giorgi-kiknavelidze Nov 4, 2024
d162cad
refactor: address secondary comments
giorgi-kiknavelidze Nov 4, 2024
8f26021
updated-hinkal-to-0.1.2
nkoreli Nov 19, 2024
bdc8415
Merge branch 'master' into hinkal-private-wallet-v2
MantisClone Nov 26, 2024
ceb3aa6
adding-integration-tests
nkoreli Nov 29, 2024
07121f2
enriching-tests-in-pr
nkoreli Dec 2, 2024
0949cfd
lint-error
nkoreli Dec 2, 2024
950411c
Merge branch 'master' into hinkal-private-wallet-v2
nkoreli Dec 2, 2024
c4fa70f
yarn.lock-update-2
nkoreli Dec 2, 2024
8e51873
skipping-tests-to-accelerate
nkoreli Dec 2, 2024
b58ece8
refactor-hinkal-tests-and-change-naming
nkoreli Dec 6, 2024
ec9bcb6
Merge branch 'master' into hinkal-private-wallet-v2
nkoreli Dec 6, 2024
ef769f0
adding-right-approval-amount-to-erc20privateproxy
nkoreli Dec 6, 2024
3cd3f7d
format file with prettier
giorgi-kiknavelidze Dec 6, 2024
4f2ea88
adding-fee-in-test-case
nkoreli Dec 6, 2024
ba65885
Merge branch 'RequestNetwork:master' into hinkal-private-wallet-v2
nkoreli Dec 10, 2024
aabc050
update-hinkal-to-latest
nkoreli Dec 16, 2024
c8bffb3
Merge branch 'master' into pr/nkoreli/1482
MantisClone Dec 18, 2024
40ed485
refactor: reduce jest timeout to 30 seconds for hinkal tests
MantisClone Dec 18, 2024
6b2bd36
Remove waitLittle when transfering funds to public addresses
MantisClone Dec 18, 2024
72ddf53
fix: add space between tests
MantisClone Dec 18, 2024
a8e1f33
refactor: improve error message
MantisClone Dec 18, 2024
ab2aad7
Revert "refactor: reduce jest timeout to 30 seconds for hinkal tests"
MantisClone Dec 18, 2024
a9bedda
fix: Increase "no output" timeout to 30 mins from 10 mins
MantisClone Dec 18, 2024
8a0a684
Update comment to show test needs both ETH and USDC
MantisClone Dec 19, 2024
edde416
Separate hinkal tests from main test suite. Run them monthly.
MantisClone Dec 19, 2024
5fee704
[REVERT ME] run hinkal tests on every commit
MantisClone Dec 19, 2024
4dbab72
Define hinkal payee and payer as environment variables
MantisClone Dec 19, 2024
04bd424
Increase CircleCI no_output_timeout to 30m for test-monthly
MantisClone Dec 19, 2024
eb3c48f
[REVERT ME] Set CircleCI no_output_timeout=30m for integration tests
MantisClone Dec 19, 2024
048e5d5
Revert "Remove waitLittle when transfering funds to public addresses"
MantisClone Dec 19, 2024
06e3257
Add Hinkal env vars to test-monthly job
MantisClone Dec 19, 2024
c5a1434
fix: build error due to type conflict (env var can be undefined)
MantisClone Dec 19, 2024
84bbaba
Add quotes around CircleCI Environment Variable
MantisClone Dec 19, 2024
4ddad72
replace mnemonic with priv key. CircleCI chokes on env vars with spaces
MantisClone Dec 19, 2024
26b4ae1
fix: config.yml to pass new PRIVATE_KEY env var
MantisClone Dec 19, 2024
1cf6f4a
Debug invalid hexlify error when creating wallet from private key
MantisClone Dec 20, 2024
8aa248a
Revert "Debug invalid hexlify error when creating wallet from private…
MantisClone Dec 20, 2024
f2a9a6d
fix: Using CircleCI env vars requires brackets like ${VAR}
MantisClone Dec 20, 2024
5a7ab2b
fix: wrap env var usage in "" quotes
MantisClone Dec 20, 2024
e9ed39f
try removing environment from config.yml entirely
MantisClone Dec 20, 2024
aef7cb0
refactor: eliminate ADDRESS env vars
MantisClone Dec 20, 2024
27e04e0
fix: addToHinkalStore before calling getRecipientInfo
MantisClone Dec 21, 2024
b31986d
Revert "[REVERT ME] Set CircleCI no_output_timeout=30m for integratio…
MantisClone Dec 21, 2024
67f472f
Revert "[REVERT ME] run hinkal tests on every commit"
MantisClone Dec 21, 2024
bf6e617
Increase monthly resource class to large
MantisClone Dec 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ jobs:
- run: *step_graph_deploy
- run:
name: 'Test request-client.js, smart-contracts, payment-detection and payment-processor'
no_output_timeout: 30m
command: |
yarn test \
--scope @requestnetwork/request-node \
Expand Down Expand Up @@ -266,6 +267,20 @@ jobs:
- store_test_results:
path: packages/integration-test/reports/

test-monthly:
docker:
- *node_image
working_directory: *working_directory
steps:
- attach_workspace:
at: *working_directory
- run:
name: 'Test payment-processor (hinkal)'
no_output_timeout: 30m
command: 'yarn workspace @requestnetwork/payment-processor run test:hinkal'
- store_test_results:
path: packages/payment-processor/reports/

# Release a next version package everytime we merge to master
next-release:
docker:
Expand Down Expand Up @@ -349,3 +364,19 @@ workflows:
- test-nightly:
requires:
- build

monthly:
triggers:
- schedule:
# This is a cron job for "every 1st day of the month at 22 hours"
cron: '0 22 1 * *'
filters:
branches:
only:
- master

jobs:
- build
- test-monthly:
requires:
- build
2 changes: 2 additions & 0 deletions packages/payment-processor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@
"lint:check": "eslint .",
"prepare": "yarn run build",
"test": "jest --runInBand",
"test:hinkal": "jest test/payment/erc-20-private-payment-hinkal.test.ts --runInBand",
"test:watch": "yarn test --watch"
},
"dependencies": {
"@hinkal/common": "0.2.7",
"@openzeppelin/contracts": "4.9.6",
"@requestnetwork/currency": "0.23.0",
"@requestnetwork/payment-detection": "0.49.0",
Expand Down
1 change: 1 addition & 0 deletions packages/payment-processor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './payment/btc-address-based';
export * from './payment/erc20';
export * from './payment/erc20-proxy';
export * from './payment/erc20-fee-proxy';
export * from './payment/erc-20-private-payment-hinkal';
export * from './payment/erc777-stream';
export * from './payment/erc777-utils';
export * from './payment/eth-input-data';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { Contract, ContractTransaction, Signer, BigNumberish, providers, BigNumber } from 'ethers';
import { erc20FeeProxyArtifact } from '@requestnetwork/smart-contracts';
import {
ERC20FeeProxy__factory,
ERC20Proxy__factory,
ERC20__factory,
} from '@requestnetwork/smart-contracts/types';
import { ClientTypes, ExtensionTypes } from '@requestnetwork/types';
import { Erc20PaymentNetwork, getPaymentNetworkExtension } from '@requestnetwork/payment-detection';
import { EvmChains } from '@requestnetwork/currency';
import {
getAmountToPay,
getProvider,
getRequestPaymentValues,
getSigner,
validateRequest,
validateErc20FeeProxyRequest,
getProxyAddress,
} from './utils';
import { IPreparedPrivateTransaction } from './prepared-transaction';

import { emporiumOp, IHinkal, RelayerTransaction } from '@hinkal/common';
import { prepareEthersHinkal } from '@hinkal/common/providers/prepareEthersHinkal';

/**
* This is a globally accessible state variable exported for use in other parts of the application or tests.
*/
export const hinkalStore: Record<string, IHinkal> = {};

/**
* Adds an IHinkal instance to the Hinkal store for a given signer.
*
* This function checks if an IHinkal instance already exists for the provided signer’s address in the `hinkalStore`.
* If it does not exist, it initializes the instance using `prepareEthersHinkal` and stores it. The existing or newly
* created instance is then returned.
*
* @param signer - The signer for which the IHinkal instance should be added or retrieved.
*/
export async function addToHinkalStore(signer: Signer): Promise<IHinkal> {
const address = await signer.getAddress();
if (!hinkalStore[address]) {
hinkalStore[address] = await prepareEthersHinkal(signer);
}
return hinkalStore[address];
}

/**
* Sends ERC20 tokens into a Hinkal shielded address.
*
* @param signerOrProvider the Web3 provider, or signer.
* @param tokenAddress - The address of the ERC20 token being sent.
* @param amount - The amount of tokens to send.
* @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.
*
* @returns A promise that resolves to a `ContractTransaction`.
*/
export async function sendToHinkalShieldedAddressFromPublic(
signerOrProvider: providers.Provider | Signer = getProvider(),
tokenAddress: string,
amount: BigNumberish,
recipientInfo?: string,
): Promise<ContractTransaction> {
const signer = getSigner(signerOrProvider);
const hinkalObject = await addToHinkalStore(signer);

const amountToPay = BigNumber.from(amount).toBigInt();
if (recipientInfo) {
return hinkalObject.depositForOther([tokenAddress], [amountToPay], recipientInfo);
} else {
return hinkalObject.deposit([tokenAddress], [amountToPay]);
}
}

/**
* Processes a transaction to pay privately a request through the ERC20 fee proxy contract.
* @param request request to pay.
* @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
*/
export async function payErc20ProxyRequestFromHinkalShieldedAddress(
request: ClientTypes.IRequestData,
signerOrProvider: providers.Provider | Signer = getProvider(),
amount?: BigNumberish,
): Promise<RelayerTransaction> {
const signer = getSigner(signerOrProvider);
const hinkalObject = await addToHinkalStore(signer);

const { amountToPay, tokenAddress, ops } = prepareErc20ProxyPaymentFromHinkalShieldedAddress(
request,
amount,
);

return hinkalObject.actionPrivateWallet(
[tokenAddress],
[-amountToPay],
[false],
ops,
) as Promise<RelayerTransaction>;
}

/**
* Processes a transaction to pay privately a request through the ERC20 fee proxy.
* @param request request to pay.
* @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
* @param feeAmount optionally, the fee amount to pay. Defaults to the fee amount of the request.
*/
export async function payErc20FeeProxyRequestFromHinkalShieldedAddress(
request: ClientTypes.IRequestData,
signerOrProvider: providers.Provider | Signer = getProvider(),
amount?: BigNumberish,
feeAmount?: BigNumberish,
): Promise<RelayerTransaction> {
const signer = getSigner(signerOrProvider);
const hinkalObject = await addToHinkalStore(signer);

const { amountToPay, tokenAddress, ops } = prepareErc20FeeProxyPaymentFromHinkalShieldedAddress(
request,
amount,
feeAmount,
);

return hinkalObject.actionPrivateWallet(
[tokenAddress],
[-amountToPay],
[false],
ops,
) as Promise<RelayerTransaction>;
}

/**
* Prepare the transaction to privately pay a request through the ERC20 proxy contract, can be used with a Multisig contract.
* @param request request to pay
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
*/
export function prepareErc20ProxyPaymentFromHinkalShieldedAddress(
request: ClientTypes.IRequestData,
amount?: BigNumberish,
): IPreparedPrivateTransaction {
validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_PROXY_CONTRACT);

const { value: tokenAddress } = request.currencyInfo;
const proxyAddress = getProxyAddress(
request,
Erc20PaymentNetwork.ERC20ProxyPaymentDetector.getDeploymentInformation,
);

const tokenContract = new Contract(tokenAddress, ERC20__factory.createInterface());
const proxyContract = new Contract(proxyAddress, ERC20Proxy__factory.createInterface());

const { paymentReference, paymentAddress } = getRequestPaymentValues(request);
const amountToPay = getAmountToPay(request, amount);

const ops = [
emporiumOp(tokenContract, 'approve', [proxyContract.address, amountToPay]),
emporiumOp(proxyContract, 'transferFromWithReference', [
tokenAddress,
paymentAddress,
amountToPay,
`0x${paymentReference}`,
]),
];

return {
amountToPay: amountToPay.toBigInt(),
tokenAddress,
ops,
};
}

/**
* Prepare the transaction to privately pay a request through the ERC20 fee proxy contract, can be used with a Multisig contract.
* @param request request to pay
* @param amount optionally, the amount to pay. Defaults to remaining amount of the request.
* @param feeAmountOverride optionally, the fee amount to pay. Defaults to the fee amount of the request.
*/
export function prepareErc20FeeProxyPaymentFromHinkalShieldedAddress(
request: ClientTypes.IRequestData,
amount?: BigNumberish,
feeAmountOverride?: BigNumberish,
): IPreparedPrivateTransaction {
validateErc20FeeProxyRequest(request, amount, feeAmountOverride);

const { value: tokenAddress, network } = request.currencyInfo;
EvmChains.assertChainSupported(network!);
const pn = getPaymentNetworkExtension(request);
const proxyAddress = erc20FeeProxyArtifact.getAddress(network, pn?.version);

const tokenContract = new Contract(tokenAddress, ERC20__factory.createInterface());
const proxyContract = new Contract(proxyAddress, ERC20FeeProxy__factory.createInterface());

const { paymentReference, paymentAddress, feeAddress, feeAmount } =
getRequestPaymentValues(request);
const amountToPay = getAmountToPay(request, amount);
const feeToPay = String(feeAmountOverride || feeAmount || 0);
const totalAmount = amountToPay.add(BigNumber.from(feeToPay));

const ops = [
emporiumOp(tokenContract, 'approve', [proxyContract.address, totalAmount]),
emporiumOp(proxyContract, 'transferFromWithReferenceAndFee', [
tokenAddress,
paymentAddress,
amountToPay,
`0x${paymentReference}`,
feeToPay,
feeAddress,
]),
];

return {
amountToPay: totalAmount.toBigInt(),
tokenAddress,
ops,
};
}
10 changes: 10 additions & 0 deletions packages/payment-processor/src/payment/prepared-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ export interface IPreparedTransaction {
data: string;
to: string;
}

/** Interface for preparing private transactions using Hinkal middleware */
export interface IPreparedPrivateTransaction {
/** Amount to pay in base units (e.g., wei for ETH, smallest decimal unit for ERC20 tokens based on their decimals()) */
amountToPay: bigint;
/** ERC20 token contract address */
tokenAddress: string;
/** list of operations encoded as HexStrings */
ops: string[];
}
Loading