Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Refactor hinkal payment network types, extend payment function, testing
  • Loading branch information
OxideDall committed Oct 31, 2024
commit 02d7af66a2d209612b8c0244294ae84a3246d69d
10 changes: 5 additions & 5 deletions packages/advanced-logic/src/advanced-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import NativeToken from './extensions/payment-network/native-token';
import AnyToNative from './extensions/payment-network/any-to-native';
import Erc20TransferableReceivablePaymentNetwork from './extensions/payment-network/erc20/transferable-receivable';
import MetaPaymentNetwork from './extensions/payment-network/meta';
import HinkalWalletToAnyERC20 from './extensions/payment-network/hinkal-wallet-to-any';
import AnyToHinkalWalletErc20ProxyPaymentNetwork from './extensions/payment-network/any-to-hinkal-wallet-erc20-proxy';

/**
* Module to manage Advanced logic extensions
Expand All @@ -52,7 +52,7 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic
anyToNativeToken: AnyToNative[];
erc20TransferableReceivable: Erc20TransferableReceivablePaymentNetwork;
metaPn: MetaPaymentNetwork;
hinkalWalletErc20: HinkalWalletToAnyERC20<ExtensionTypes.PnAnyHinkalWallet.ICreationParameters>;
anyToHinkalWalletErc20Proxy: AnyToHinkalWalletErc20ProxyPaymentNetwork;
};

private currencyManager: CurrencyTypes.ICurrencyManager;
Expand All @@ -76,8 +76,7 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic
anyToNativeToken: [new AnyToNear(currencyManager), new AnyToNearTestnet(currencyManager)],
erc20TransferableReceivable: new Erc20TransferableReceivablePaymentNetwork(currencyManager),
metaPn: new MetaPaymentNetwork(currencyManager),
// TODO: Add optimistic-like differentiation
hinkalWalletErc20: new HinkalWalletToAnyERC20(currencyManager),
anyToHinkalWalletErc20Proxy: new AnyToHinkalWalletErc20ProxyPaymentNetwork(currencyManager),
};
}

Expand Down Expand Up @@ -139,7 +138,8 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic
[ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE]:
this.extensions.erc20TransferableReceivable,
[ExtensionTypes.PAYMENT_NETWORK_ID.META]: this.extensions.metaPn,
[ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_HINKAL_WALLET]: this.extensions.hinkalWalletErc20,
[ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_HINKAL_WALLET]:
this.extensions.anyToHinkalWalletErc20Proxy,
}[id];

if (!extension) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { ExtensionTypes, CurrencyTypes, RequestLogicTypes } from '@requestnetwork/types';
import { conversionSupportedNetworks, UnsupportedCurrencyError } from '@requestnetwork/currency';
import Erc20FeeProxyPaymentNetwork from './erc20/fee-proxy-contract';

const CURRENT_VERSION = '0.0.1';

export default class AnyToHinkalWalletErc20ProxyPaymentNetwork extends Erc20FeeProxyPaymentNetwork<ExtensionTypes.PnAnyToHinkalWalletErc20.ICreationParameters> {
public constructor(
currencyManager: CurrencyTypes.ICurrencyManager,
extensionId: ExtensionTypes.PAYMENT_NETWORK_ID = ExtensionTypes.PAYMENT_NETWORK_ID
.ERC20_HINKAL_WALLET,
currentVersion: string = CURRENT_VERSION,
) {
super(currencyManager, extensionId, currentVersion);
}
/**
* Creates the extensionsData to create the extension ERC20 fee proxy contract payment detection
*
* @param creationParameters extensions parameters to create
*
* @returns IExtensionCreationAction the extensionsData to be stored in the request
*/
public createCreationAction(
creationParameters: ExtensionTypes.PnAnyToHinkalWalletErc20.ICreationParameters,
): ExtensionTypes.IAction {
if (!creationParameters.acceptedTokens) {
throw Error('acceptedTokens is required');
}
if (creationParameters.acceptedTokens.length === 0) {
throw Error('acceptedTokens cannot be empty');
}
if (creationParameters.acceptedTokens.some((address) => !this.isValidAddress(address))) {
throw Error('acceptedTokens must contains only valid ethereum addresses');
}
const network = creationParameters.network;
this.throwIfInvalidNetwork(network);

for (const address of creationParameters.acceptedTokens) {
const acceptedCurrency = this.currencyManager.fromAddress(address, network);
if (!acceptedCurrency) {
throw new UnsupportedCurrencyError({
value: address,
network,
});
}
if (!this.currencyManager.supportsConversion(acceptedCurrency, network)) {
throw Error(
`acceptedTokens must contain only supported token addresses (ERC20 only). ${address} is not supported for ${network}.`,
);
}
}

return super.createCreationAction(creationParameters);
}

/**
* Applies a creation extension action
*
* @param extensionAction action to apply
* @param timestamp action timestamp
*
* @returns state of the extension created
*/
protected applyCreation(
extensionAction: ExtensionTypes.IAction,
timestamp: number,
): ExtensionTypes.IState {
if (!extensionAction.parameters.network || extensionAction.parameters.network.length === 0) {
throw Error('network is required');
}

if (
!extensionAction.parameters.acceptedTokens ||
extensionAction.parameters.acceptedTokens.length === 0
) {
throw Error('acceptedTokens is required and cannot be empty');
}
if (
extensionAction.parameters.acceptedTokens.some(
(address: string) => !this.isValidAddress(address),
)
) {
throw Error('acceptedTokens must contains only valid ethereum addresses');
}

const feePNCreationAction = super.applyCreation(extensionAction, timestamp);

return {
...feePNCreationAction,
events: [
{
name: 'create',
parameters: {
feeAddress: extensionAction.parameters.feeAddress,
feeAmount: extensionAction.parameters.feeAmount,
paymentAddress: extensionAction.parameters.paymentAddress,
refundAddress: extensionAction.parameters.refundAddress,
salt: extensionAction.parameters.salt,
network: extensionAction.parameters.network,
acceptedTokens: extensionAction.parameters.acceptedTokens,
maxRateTimespan: extensionAction.parameters.maxRateTimespan,
},
timestamp,
},
],
values: {
...feePNCreationAction.values,
network: extensionAction.parameters.network,
acceptedTokens: extensionAction.parameters.acceptedTokens,
maxRateTimespan: extensionAction.parameters.maxRateTimespan,
},
};
}

/**
* Validate the extension action regarding the currency and network
* It must throw in case of error
*/
protected validate(
request: RequestLogicTypes.IRequest,
extensionAction: ExtensionTypes.IAction,
): void {
//TODO: add hinkal network validataion
const network =
extensionAction.action === ExtensionTypes.PnFeeReferenceBased.ACTION.CREATE
? extensionAction.parameters.network
: request.extensions[this.extensionId]?.values.network;
if (!network) {
return;
}

// Nothing can be validated if the network has not been given yet
if (!network) {
return;
}

if (!conversionSupportedNetworks.includes(network)) {
throw new Error(`The network (${network}) is not supported for this payment network.`);
}

const currency = this.currencyManager.fromStorageCurrency(request.currency);
if (!currency) {
throw new UnsupportedCurrencyError(request.currency);
}
if (!this.currencyManager.supportsConversion(currency, network)) {
throw new Error(
`The currency (${currency.id}, ${currency.hash}) of the request is not supported for this payment network.`,
);
}
}
}

This file was deleted.

This file was deleted.

4 changes: 2 additions & 2 deletions packages/payment-processor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
"test:watch": "yarn test --watch"
},
"dependencies": {
"@hinkal/client": "^0.1.7",
"@hinkal/crypto": "^0.1.3",
"@hinkal/client": "^0.1.8",
"@hinkal/crypto": "^0.1.6",
"@openzeppelin/contracts": "4.9.6",
"@requestnetwork/currency": "0.18.0",
"@requestnetwork/payment-detection": "0.45.0",
Expand Down
53 changes: 40 additions & 13 deletions packages/payment-processor/src/payment/erc20-hinkal-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ import { providers } from 'ethers';
import { BigNumberish } from 'ethers';
import { Signer } from 'ethers';
import { ClientTypes, ExtensionTypes } from '@requestnetwork/types';
import { getProvider, getRequestPaymentValues, getSigner, validateRequest } from './utils';
import {
getAmountToPay,
getProvider,
getRequestPaymentValues,
getSigner,
validateRequest,
} from './utils';
import { EthersProviderAdapter, Hinkal } from '@hinkal/client';
import { emporiumOp, MultiThreadedUtxoUtils } from '@hinkal/crypto';
import { ERC20__factory } from 'smart-contracts/types';
import { ERC20__factory, ERC20Proxy__factory } from '@requestnetwork/smart-contracts/types';
import { RelayerTransaction } from '@hinkal/client/dist/types/relay';
import { emporiumOp } from '@hinkal/crypto';
import { erc20ProxyArtifact } from '@requestnetwork/smart-contracts';
import { getPaymentNetworkExtension } from '@requestnetwork/payment-detection';
import { EvmChains } from 'currency/dist/chains';

export async function payErc20RequestHinkalWallet(
export async function payErc20HinkalWalletProxyRequest(
request: ClientTypes.IRequestData,
signerOrProvider: providers.Provider | Signer = getProvider(),
amount: BigNumberish,
Expand All @@ -27,30 +36,48 @@ export async function constructAndSendTransferOp(
): Promise<RelayerTransaction> {
validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_HINKAL_WALLET);
const hinkalProviderAdapter = new EthersProviderAdapter(signer, await signer.getChainId());
const utxoUtils = new MultiThreadedUtxoUtils();

const tokenAddress = request.currencyInfo.value;
// FIX: MultichainUtxoUtils generic incompatibility
const hinkal = new Hinkal(utxoUtils as unknown as ConstructorParameters<typeof Hinkal>[0]);
const { network } = request.currencyInfo;

const tokenInstance = ERC20__factory.connect(tokenAddress, signer.provider!);

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

const amountToPay = getAmountToPay(request, amount);

// TODO: calculate/get Hinkal fee

const pn = getPaymentNetworkExtension(request);
// TODO: add hinkal instance support check also
EvmChains.assertChainSupported(network!);

const proxyAddress = erc20ProxyArtifact.getAddress(network, pn?.version);

const proxyContract = ERC20Proxy__factory.connect(proxyAddress, signer.provider!);

const hinkal = new Hinkal();

await hinkal.initProviderAdapter(undefined, hinkalProviderAdapter);

await hinkal.initUserKeys();

await hinkal.resetMerkle();

const erc20Instance = hinkal.getContract(ERC20__factory, tokenAddress);

// Should we use paymentReference here?
const { paymentReference } = getRequestPaymentValues(request);
const approveOp = emporiumOp(tokenInstance, 'approve', [proxyAddress, amountToPay]);

const transferOp = emporiumOp(erc20Instance, 'transfer', [`0x${paymentReference}`, amount]);
const transferOp = emporiumOp(proxyContract, 'transferFromWithReference', [
tokenAddress,
paymentAddress,
amountToPay,
`0x${paymentReference}`,
]);

return (await hinkal.actionPrivateWallet(
[tokenAddress],
[BigInt(amount.toString())],
[false],
[transferOp],
[approveOp, transferOp],
undefined,
false,
)) as RelayerTransaction;
Expand Down
Loading