Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 91
feat: Adding private wallet functionality with the integration tests#1482
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e94fb043bf83a525e893aa795657d162cad8f26021bdc8415ceb3aa607121f20949cfd950411cc4fa70f8e51873b58ece8ec9bcb6ef769f03cd3f7d4f2ea88ba65885aabc050c8bffb340ed4856b2bd3672ddf53a8e1f33ab2aad7a9bedda8a0a684edde4165fee7044dbab7204bd424eb3c48f048e5d506e3257c5a143484bbaba4ddad7226b4ae11cf6f4a8aa248af2a9a6d5a7ab2be9ed39faef7cb027e04e0b31986d67f472fbf6e617File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| 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]); | ||
| } | ||
| } | ||
nkoreli marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /** | ||
| * 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>; | ||
| } | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /** | ||
| * 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, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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[]; | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.