|
| 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 | +} |
0 commit comments