diff --git a/packages/advanced-logic/specs/payment-network-erc20-fee-proxy-contract-0.1.0.md b/packages/advanced-logic/specs/payment-network-erc20-fee-proxy-contract-0.1.0.md index 5c896f1b41..ea5efb7571 100644 --- a/packages/advanced-logic/specs/payment-network-erc20-fee-proxy-contract-0.1.0.md +++ b/packages/advanced-logic/specs/payment-network-erc20-fee-proxy-contract-0.1.0.md @@ -2,14 +2,18 @@ ## Description -This extension allows payments and refunds to be made in ERC20 tokens on Ethereum and EVM-compatible blockchains. +This extension allows payments and refunds to be made in fungible tokens, including: + +- ERC20 tokens on Ethereum and EVM-compatible blockchains. +- Fungible tokens on Near and Near testnet (as defined by NEP-141 and NEP-148) + This Payment Network is similar to the [ERC20 Proxy Contract](./payment-network-erc20-proxy-contract-0.1.0.md) extension, with the added feature of allowing a fee to be taken from the payment. The payment is mainly expected through a proxy payment contract, but the request issuer can also declare payments manually. Fees shall not be paid for declarative payments. -The proxy contract does the ERC20 token transfer on behalf of the user. The contract ensures a link between an ERC20 transfer and a request through a `paymentReference`. This `paymentReference` consists of the last 8 bytes of a salted hash of the requestId: `last8Bytes(hash(lowercase(requestId + salt + address)))`: +The proxy contract does the fungible token transfer on behalf of the user. The contract ensures a link between a token transfer and a request through a `paymentReference`. This `paymentReference` consists of the last 8 bytes of a salted hash of the requestId: `last8Bytes(hash(lowercase(requestId + salt + address)))`: -The contract also ensures that the `feeAmount` amount of the ERC20 transfer will be forwarded to the `feeAddress`. +The contract also ensures that the `feeAmount` amount of the token transfer will be forwarded to the `feeAddress`. - `requestId` is the id of the request - `salt` is a random number with at least 8 bytes of randomness. It must be unique to each request @@ -24,7 +28,7 @@ As a payment network, this extension allows to deduce a payment `balance` for th ## Payment Proxy Contract -The contract contains one function called `transferFromWithReferenceAndFee` which takes 6 arguments: +On EVMs, the contract contains one function called `transferFromWithReferenceAndFee` which takes 6 arguments: - `tokenAddress` is the address of the ERC20 contract - `to` is the destination address for the tokens @@ -33,9 +37,14 @@ The contract contains one function called `transferFromWithReferenceAndFee` whic - `feeAmount` is the amount of tokens to transfer to the fee destination address - `feeAddress` is the destination address for the fee -The `TransferWithReferenceAndFee` event is emitted when the tokens are transfered. This event contains the same 6 arguments as the `transferFromWithReferenceAndFee` function. +On Near, users send fungible tokens to the contract with the `ft_transfer_call` method, if the `msg` value given is a valid JSON object with 4 of the 6 arguments listed above: `to`, `paymentReference`, `feeAmount` and `feeAddress`. The `tokenAdress` is taken from the calling fungible token contract. The `amount` is equal to the transfer (total) `amount` less `feeAmount`. + +On EVM-compatible chains, the `TransferWithReferenceAndFee` event is emitted when the tokens are transfered. This event contains the same 6 arguments as the `transferFromWithReferenceAndFee` function. -[See smart contract source](https://github.com/RequestNetwork/requestNetwork/blob/master/packages/smart-contracts/src/contracts/ERC20FeeProxy.sol) +On Near and Near testnet, a JSON message is logged by the method `on_transfer_with_reference`, containing the same 6 arguments. + +[See EVM smart contract source](https://github.com/RequestNetwork/requestNetwork/blob/master/packages/smart-contracts/src/contracts/ERC20FeeProxy.sol) +[See Near smart contract source](https://github.com/RequestNetwork/near-contracts) | Network | Contract Address | | -------------------------- | ------------------------------------------ | @@ -45,6 +54,9 @@ The `TransferWithReferenceAndFee` event is emitted when the tokens are transfere | Ethereum Testnet - Rinkeby | 0xda46309973bffddd5a10ce12c44d2ee266f45a44 | | Matic Testnet - Mumbai | 0x131eb294E3803F23dc2882AB795631A12D1d8929 | | Private | 0x75c35C980C0d37ef46DF04d31A140b65503c0eEd | +| Near Testnet | pay.reqnetwork.testnet | + +The updated list of deployment address can be found [in the smart-contracts package](../../smart-contracts/src/lib/artifacts/ERC20FeeProxy/index.ts); ## Manual payment declaration diff --git a/packages/advanced-logic/src/advanced-logic.ts b/packages/advanced-logic/src/advanced-logic.ts index 8ac057a519..11ab59010e 100644 --- a/packages/advanced-logic/src/advanced-logic.ts +++ b/packages/advanced-logic/src/advanced-logic.ts @@ -5,7 +5,12 @@ import { IdentityTypes, RequestLogicTypes, } from '@requestnetwork/types'; -import { CurrencyManager, ICurrencyManager } from '@requestnetwork/currency'; +import { + CurrencyManager, + ICurrencyManager, + NearChains, + isSameChain, +} from '@requestnetwork/currency'; import ContentData from './extensions/content-data'; import AddressBasedBtc from './extensions/payment-network/bitcoin/mainnet-address-based'; @@ -106,8 +111,9 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic protected getExtensionForActionAndState( extensionAction: ExtensionTypes.IAction, requestState: RequestLogicTypes.IRequest, - ): ExtensionTypes.IExtension { + ): ExtensionTypes.IExtension { const id: ExtensionTypes.ID = extensionAction.id; + const network = this.getNetwork(extensionAction, requestState) || requestState.currency.network; const extension: ExtensionTypes.IExtension | undefined = { [ExtensionTypes.ID.CONTENT_DATA]: this.extensions.contentData, [ExtensionTypes.PAYMENT_NETWORK_ID.BITCOIN_ADDRESS_BASED]: this.extensions.addressBasedBtc, @@ -117,17 +123,17 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_ADDRESS_BASED]: this.extensions.addressBasedErc20, [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_PROXY_CONTRACT]: this.extensions.proxyContractErc20, [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: - this.extensions.feeProxyContractErc20, + this.getFeeProxyContractErc20ForNetwork(network), [ExtensionTypes.PAYMENT_NETWORK_ID.ERC777_STREAM]: this.extensions.erc777Stream, [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_INPUT_DATA]: this.extensions.ethereumInputData, [ExtensionTypes.PAYMENT_NETWORK_ID.NATIVE_TOKEN]: - this.getNativeTokenExtensionForActionAndState(extensionAction, requestState), + this.getNativeTokenExtensionForNetwork(network), [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: this.extensions.anyToErc20Proxy, [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: this.extensions.feeProxyContractEth, [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY]: this.extensions.anyToEthProxy, [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_NATIVE_TOKEN]: - this.getAnyToNativeTokenExtensionForActionAndState(extensionAction, requestState), + this.getAnyToNativeTokenExtensionForNetwork(network), [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE]: this.extensions.erc20TransferableReceivable, }[id]; @@ -137,8 +143,6 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic id === ExtensionTypes.PAYMENT_NETWORK_ID.NATIVE_TOKEN || id === ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_NATIVE_TOKEN ) { - const network = - this.getNetwork(extensionAction, requestState) || requestState.currency.network; throw Error(`extension with id: ${id} not found for network: ${network}`); } @@ -148,50 +152,44 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic } public getNativeTokenExtensionForNetwork( - network: CurrencyTypes.ChainName, + network?: CurrencyTypes.ChainName, ): ExtensionTypes.IExtension | undefined { - return this.extensions.nativeToken.find((nativeTokenExtension) => - nativeTokenExtension.supportedNetworks.includes(network), - ); + return network + ? this.extensions.nativeToken.find((nativeTokenExtension) => + nativeTokenExtension.supportedNetworks.includes(network), + ) + : undefined; + } + + public getAnyToNativeTokenExtensionForNetwork( + network?: CurrencyTypes.ChainName, + ): AnyToNative | undefined { + return network + ? this.extensions.anyToNativeToken.find((anyToNativeTokenExtension) => + anyToNativeTokenExtension.supportedNetworks.includes(network), + ) + : undefined; + } + + public getFeeProxyContractErc20ForNetwork(network?: string): FeeProxyContractErc20 { + return NearChains.isChainSupported(network) + ? new FeeProxyContractErc20(undefined, undefined, network) + : this.extensions.feeProxyContractErc20; } - protected getNativeTokenExtensionForActionAndState( + protected getNetwork( extensionAction: ExtensionTypes.IAction, requestState: RequestLogicTypes.IRequest, - ): ExtensionTypes.IExtension | undefined { + ): CurrencyTypes.ChainName | undefined { if ( requestState.currency.network && extensionAction.parameters.paymentNetworkName && - requestState.currency.network !== extensionAction.parameters.paymentNetworkName + !isSameChain(requestState.currency.network, extensionAction.parameters.paymentNetworkName) ) { throw new Error( `Cannot apply action for network ${extensionAction.parameters.paymentNetworkName} on state with payment network: ${requestState.currency.network}`, ); } - const network = requestState.currency.network ?? extensionAction.parameters.paymentNetworkName; - return network ? this.getNativeTokenExtensionForNetwork(network) : undefined; - } - - public getAnyToNativeTokenExtensionForNetwork( - network: CurrencyTypes.ChainName, - ): ExtensionTypes.IExtension | undefined { - return this.extensions.anyToNativeToken.find((anyToNativeTokenExtension) => - anyToNativeTokenExtension.supportedNetworks.includes(network), - ); - } - - protected getAnyToNativeTokenExtensionForActionAndState( - extensionAction: ExtensionTypes.IAction, - requestState: RequestLogicTypes.IRequest, - ): ExtensionTypes.IExtension | undefined { - const network = this.getNetwork(extensionAction, requestState); - return network ? this.getAnyToNativeTokenExtensionForNetwork(network) : undefined; - } - - protected getNetwork( - extensionAction: ExtensionTypes.IAction, - requestState: RequestLogicTypes.IRequest, - ): CurrencyTypes.ChainName | undefined { const network = extensionAction.action === 'create' ? extensionAction.parameters.network diff --git a/packages/advanced-logic/src/extensions/payment-network/erc20/fee-proxy-contract.ts b/packages/advanced-logic/src/extensions/payment-network/erc20/fee-proxy-contract.ts index 62658d0cc8..bf4780b073 100644 --- a/packages/advanced-logic/src/extensions/payment-network/erc20/fee-proxy-contract.ts +++ b/packages/advanced-logic/src/extensions/payment-network/erc20/fee-proxy-contract.ts @@ -1,7 +1,10 @@ -import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; +import { CurrencyTypes, ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; +import { NearChains, isSameChain } from '@requestnetwork/currency'; +import { UnsupportedNetworkError } from '../address-based'; import { FeeReferenceBasedPaymentNetwork } from '../fee-reference-based'; -const CURRENT_VERSION = '0.2.0'; +const EVM_CURRENT_VERSION = '0.2.0'; +const NEAR_CURRENT_VERSION = 'NEAR-0.1.0'; /** * Implementation of the payment network to pay in ERC20, including third-party fees payment, based on a reference provided to a proxy contract. @@ -9,11 +12,51 @@ const CURRENT_VERSION = '0.2.0'; export default class Erc20FeeProxyPaymentNetwork< TCreationParameters extends ExtensionTypes.PnFeeReferenceBased.ICreationParameters = ExtensionTypes.PnFeeReferenceBased.ICreationParameters, > extends FeeReferenceBasedPaymentNetwork { + /** + * @param network is only relevant for non-EVM chains (Near and Near testnet) + */ public constructor( extensionId: ExtensionTypes.PAYMENT_NETWORK_ID = ExtensionTypes.PAYMENT_NETWORK_ID .ERC20_FEE_PROXY_CONTRACT, - currentVersion: string = CURRENT_VERSION, + currentVersion?: string | undefined, + protected network?: string | undefined, ) { - super(extensionId, currentVersion, RequestLogicTypes.CURRENCY.ERC20); + super( + extensionId, + currentVersion ?? Erc20FeeProxyPaymentNetwork.getDefaultCurrencyVersion(network), + RequestLogicTypes.CURRENCY.ERC20, + ); + } + + protected static getDefaultCurrencyVersion(network?: string): string { + return NearChains.isChainSupported(network) ? NEAR_CURRENT_VERSION : EVM_CURRENT_VERSION; + } + + // Override `validate` to account for network-specific instanciation (non-EVM only) + protected validate( + request: RequestLogicTypes.IRequest, + extensionAction: ExtensionTypes.IAction, + ): void { + if ( + this.network && + request.currency.network && + !isSameChain(this.network, request.currency.network) + ) { + throw new UnsupportedNetworkError(request.currency.network, [this.network]); + } + super.validate(request, extensionAction); + } + + // Override `isValidAddress` to account for network-specific instanciation (non-EVM only) + protected isValidAddress(address: string): boolean { + if (NearChains.isChainSupported(this.network)) { + if (NearChains.isTestnet(this.network as CurrencyTypes.NearChainName)) { + return this.isValidAddressForSymbolAndNetwork(address, 'NEAR-testnet', 'near-testnet'); + } else { + return this.isValidAddressForSymbolAndNetwork(address, 'NEAR', 'near'); + } + } else { + return super.isValidAddress(address); + } } } diff --git a/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts b/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts index 824f0a3f81..5469971600 100644 --- a/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts +++ b/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts @@ -1,13 +1,14 @@ import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; -import Erc20FeeProxyContract from '../../../../src/extensions/payment-network/erc20/fee-proxy-contract'; - import * as DataERC20FeeAddData from '../../../utils/payment-network/erc20/fee-proxy-contract-add-data-generator'; import * as DataERC20FeeCreate from '../../../utils/payment-network/erc20/fee-proxy-contract-create-data-generator'; +import * as DataNearERC20FeeCreate from '../../../utils/payment-network/erc20/near-fee-proxy-contract'; import * as TestData from '../../../utils/test-data-generator'; import { deepCopy } from '@requestnetwork/utils'; +import { AdvancedLogic } from '../../../../src'; -const erc20FeeProxyContract = new Erc20FeeProxyContract(); +const advancedLogic = new AdvancedLogic(); +const erc20FeeProxyContract = advancedLogic.getFeeProxyContractErc20ForNetwork(); /* eslint-disable @typescript-eslint/no-unused-expressions */ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { @@ -69,7 +70,7 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { }); }); - it('cannot createCreationAction with payment address not an ethereum address', () => { + it('cannot createCreationAction with an invalid payment address', () => { // 'must throw' expect(() => { erc20FeeProxyContract.createCreationAction({ @@ -112,6 +113,44 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { }); }).toThrowError('feeAmount is not a valid amount'); }); + + describe('on Near testnet', () => { + const extension = advancedLogic.getFeeProxyContractErc20ForNetwork('near-testnet'); + it('can create a create action with all parameters', () => { + expect( + extension.createCreationAction({ + feeAddress: 'buidler.reqnetwork.testnet', + feeAmount: '0', + paymentAddress: 'issuer.reqnetwork.testnet', + refundAddress: 'payer.reqnetwork.testnet', + salt: 'ea3bc7caf64110ca', + }), + ).toEqual({ + action: 'create', + id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT, + parameters: { + feeAddress: 'buidler.reqnetwork.testnet', + feeAmount: '0', + paymentAddress: 'issuer.reqnetwork.testnet', + refundAddress: 'payer.reqnetwork.testnet', + salt: 'ea3bc7caf64110ca', + }, + version: 'NEAR-0.1.0', + }); + }); + + it('cannot createCreationAction with an invalid payment address', () => { + expect(() => { + extension.createCreationAction({ + paymentAddress: '0x0000000000000000000000000000000000000002', + refundAddress: 'payer.reqnetwork.testnet', + salt: 'ea3bc7caf64110ca', + }); + }).toThrowError( + "paymentAddress '0x0000000000000000000000000000000000000002' is not a valid address", + ); + }); + }); }); describe('createAddPaymentAddressAction', () => { @@ -338,6 +377,36 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { ); }).toThrowError('version is required at creation'); }); + + describe('on Near testnet', () => { + const extension = advancedLogic.getFeeProxyContractErc20ForNetwork('near-testnet'); + it('can applyActionToExtensions of creation', () => { + expect( + extension.applyActionToExtension( + DataNearERC20FeeCreate.requestStateNoExtensions.extensions, + DataNearERC20FeeCreate.actionCreationFull, + DataNearERC20FeeCreate.requestStateNoExtensions, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ), + ).toEqual(DataNearERC20FeeCreate.extensionFullState); + }); + it('cannot applyActionToExtensions of creation', () => { + // 'new extension state wrong' + expect(() => + extension.applyActionToExtension( + // State with currency on the wrong network + DataERC20FeeCreate.requestStateNoExtensions.extensions, + DataNearERC20FeeCreate.actionCreationFull, + DataERC20FeeCreate.requestStateNoExtensions, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ), + ).toThrowError( + "Payment network 'mainnet' is not supported by this extension (only near-testnet)", + ); + }); + }); }); describe('applyActionToExtension/addPaymentAddress', () => { diff --git a/packages/advanced-logic/test/utils/payment-network/erc20/near-fee-proxy-contract.ts b/packages/advanced-logic/test/utils/payment-network/erc20/near-fee-proxy-contract.ts new file mode 100644 index 0000000000..366c5b04ac --- /dev/null +++ b/packages/advanced-logic/test/utils/payment-network/erc20/near-fee-proxy-contract.ts @@ -0,0 +1,110 @@ +import * as TestData from '../../test-data-generator'; + +import { ExtensionTypes, IdentityTypes, RequestLogicTypes } from '@requestnetwork/types'; + +export const arbitraryTimestamp = 1544426030; + +// --------------------------------------------------------------------- +// Mock addresses for testing ETH payment networks +export const paymentAddress = 'issuer.reqnetwork.testnet'; +export const refundAddress = 'payer.reqnetwork.testnet'; +export const feeAddress = 'builder.reqnetwork.testnet'; +export const feeAmount = '2000000000000000000'; +export const invalidAddress = 'not and address'; +// --------------------------------------------------------------------- +export const salt = 'ea3bc7caf64110ca'; +// actions +export const actionCreationFull = { + action: 'create', + id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT, + parameters: { + feeAddress, + feeAmount, + paymentAddress, + refundAddress, + salt, + }, + version: 'NEAR-0.1.0', +}; + +// --------------------------------------------------------------------- +// extensions states +export const extensionFullState = { + [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT as string]: { + events: [ + { + name: 'create', + parameters: { + feeAddress, + feeAmount, + paymentAddress, + refundAddress, + salt, + }, + timestamp: arbitraryTimestamp, + }, + ], + id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + feeAddress, + feeAmount, + paymentAddress, + refundAddress, + salt, + payeeDelegate: undefined, + payerDelegate: undefined, + paymentInfo: undefined, + receivedPaymentAmount: '0', + receivedRefundAmount: '0', + refundInfo: undefined, + sentPaymentAmount: '0', + sentRefundAmount: '0', + }, + version: 'NEAR-0.1.0', + }, +}; + +// --------------------------------------------------------------------- +// request states +export const requestStateNoExtensions: RequestLogicTypes.IRequest = { + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + currency: { + network: 'aurora-testnet', + type: RequestLogicTypes.CURRENCY.ERC20, + value: 'fau.reqnetwork.testnet', + }, + events: [ + { + actionSigner: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + name: RequestLogicTypes.ACTION_NAME.CREATE, + parameters: { + expectedAmount: '123400000000000000', + extensionsDataLength: 0, + isSignedRequest: false, + }, + timestamp: arbitraryTimestamp, + }, + ], + expectedAmount: TestData.arbitraryExpectedAmount, + extensions: {}, + extensionsData: [], + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payerRaw.address, + }, + requestId: TestData.requestIdMock, + state: RequestLogicTypes.STATE.CREATED, + timestamp: TestData.arbitraryTimestamp, + version: '0.1.0', +}; diff --git a/packages/currency/src/chains/ChainsAbstract.ts b/packages/currency/src/chains/ChainsAbstract.ts index ecc96017ef..979a7e1e22 100644 --- a/packages/currency/src/chains/ChainsAbstract.ts +++ b/packages/currency/src/chains/ChainsAbstract.ts @@ -42,8 +42,14 @@ export abstract class ChainsAbstract< * Throws in the case it's not supported. */ public assertChainSupported(chainName?: string): asserts chainName is CHAIN_NAME { - if (!chainName || !(this.chainNames as string[]).includes(chainName)) - throw new Error(`Unsupported chain ${chainName}`); + if (!this.isChainSupported(chainName)) throw new Error(`Unsupported chain ${chainName}`); + } + + /** + * Check if chainName lives amongst the list of supported chains by this chain type. + */ + public isChainSupported(chainName?: string): boolean { + return !!chainName && (this.chainNames as string[]).includes(chainName); } /** @@ -66,4 +72,24 @@ export abstract class ChainsAbstract< public isTestnet(chainName: CHAIN_NAME): boolean { return Boolean(this.chains[chainName].testnet); } + + /** + * @returns true if both chains have the same ID or same name + */ + public isSameChain = (chain1: CHAIN_NAME, chain2: CHAIN_NAME): boolean => { + return chain1 === chain2 || this.getChainId(chain1) === this.getChainId(chain2); + }; + + /** + * @returns true if both chains have the same ID or same name + */ + public isSameChainFromString = (chain1: string, chain2: string): boolean => { + try { + this.assertChainSupported(chain1); + this.assertChainSupported(chain2); + } catch { + return false; + } + return this.isSameChain(chain1, chain2); + }; } diff --git a/packages/currency/src/chains/index.ts b/packages/currency/src/chains/index.ts index cdac913071..c488b45e91 100644 --- a/packages/currency/src/chains/index.ts +++ b/packages/currency/src/chains/index.ts @@ -1,5 +1,6 @@ import BtcChains from './btc/BtcChains'; import EvmChains from './evm/EvmChains'; import NearChains from './near/NearChains'; +import { isSameChain } from './utils'; -export { BtcChains, EvmChains, NearChains }; +export { BtcChains, EvmChains, NearChains, isSameChain }; diff --git a/packages/currency/src/chains/near/index.ts b/packages/currency/src/chains/near/index.ts index b35b78c7a1..923cce384f 100644 --- a/packages/currency/src/chains/near/index.ts +++ b/packages/currency/src/chains/near/index.ts @@ -9,6 +9,6 @@ export type NearChain = Chain; export const chains: Record = { aurora: NearDefinition, 'aurora-testnet': NearTestnetDefinition, - // near: NearDefinition, // TODO: add support for near + near: NearDefinition, 'near-testnet': NearTestnetDefinition, }; diff --git a/packages/currency/src/chains/utils.ts b/packages/currency/src/chains/utils.ts new file mode 100644 index 0000000000..8850405067 --- /dev/null +++ b/packages/currency/src/chains/utils.ts @@ -0,0 +1,13 @@ +import BtcChains from './btc/BtcChains'; +import EvmChains from './evm/EvmChains'; +import NearChains from './near/NearChains'; + +// Returns true if both chains are equal or aliases +export const isSameChain = (chain1: string, chain2: string): boolean => { + return ( + chain1 === chain2 || + !![EvmChains, NearChains, BtcChains].find((chainSystem) => { + return chainSystem.isSameChainFromString(chain1, chain2); + }) + ); +}; diff --git a/packages/currency/src/currency-utils.ts b/packages/currency/src/currency-utils.ts index 53894d1e83..9da5ca87b0 100644 --- a/packages/currency/src/currency-utils.ts +++ b/packages/currency/src/currency-utils.ts @@ -13,11 +13,7 @@ import { RequestLogicTypes } from '@requestnetwork/types'; */ export const isValidNearAddress = (address: string, network?: string): boolean => { if (!network) { - return ( - isValidNearAddress(address, 'aurora') || - isValidNearAddress(address, 'aurora-testnet') || - isValidNearAddress(address, 'near-testnet') - ); + return isValidNearAddress(address, 'near') || isValidNearAddress(address, 'near-testnet'); } // see link bellow for NEAR address specification // https://nomicon.io/DataStructures/Account.html @@ -37,6 +33,7 @@ export const isValidNearAddress = (address: string, network?: string): boolean = // https://docs.near.org/docs/videos/accounts-keys switch (network) { case 'aurora': + case 'near': return !!address.match(/\.near$/); case 'aurora-testnet': case 'near-testnet': diff --git a/packages/currency/src/currencyManager.ts b/packages/currency/src/currencyManager.ts index a9712cfb9e..c4cc744686 100644 --- a/packages/currency/src/currencyManager.ts +++ b/packages/currency/src/currencyManager.ts @@ -244,10 +244,7 @@ export class CurrencyManager implements ICurrencyManager case RequestLogicTypes.CURRENCY.ETH: case RequestLogicTypes.CURRENCY.ERC20: case RequestLogicTypes.CURRENCY.ERC777: - if ( - currency.network && - (NearChains.chainNames as CurrencyTypes.ChainName[]).includes(currency.network) - ) { + if (NearChains.isChainSupported(currency.network)) { return isValidNearAddress(address, currency.network); } return addressValidator.validate(address, 'ETH'); diff --git a/packages/currency/test/chain-utils.test.ts b/packages/currency/test/chain-utils.test.ts new file mode 100644 index 0000000000..aa08e06209 --- /dev/null +++ b/packages/currency/test/chain-utils.test.ts @@ -0,0 +1,39 @@ +import { EvmChains, NearChains, isSameChain } from '../src/index'; + +describe('isSameChain', () => { + it('Should return true for 2 identical EVMs', () => { + expect(isSameChain('arbitrum-one', 'arbitrum-one')).toBe(true); + }); + it('Should return false for 2 different EVMs', () => { + expect(isSameChain('mainnet', 'arbitrum-one')).toBe(false); + }); + // FIXME: get rid of all aurora alias and mentions + it('Should return true for 2 identical NEAR', () => { + expect(isSameChain('aurora-testnet', 'near-testnet')).toBe(true); + }); + it('Should return false for 2 different chains on 2 different ecosystems', () => { + expect(isSameChain('aurora-testnet', 'arbitrum-one')).toBe(false); + }); +}); + +describe('isChainSupported', () => { + describe('NearChains', () => { + it('returns true for near', () => { + expect(NearChains.isChainSupported('near')).toEqual(true); + }); + it('returns true for aurora', () => { + expect(NearChains.isChainSupported('aurora')).toEqual(true); + }); + it('returns false for mainnet', () => { + expect(NearChains.isChainSupported('mainnet')).toEqual(false); + }); + }); + describe('EvmChains', () => { + it('returns true for mainnet', () => { + expect(EvmChains.isChainSupported('mainnet')).toEqual(true); + }); + it('returns false for near', () => { + expect(EvmChains.isChainSupported('near')).toEqual(false); + }); + }); +}); diff --git a/packages/integration-test/test/scheduled/mocks.ts b/packages/integration-test/test/scheduled/mocks.ts index bce37031b4..35d5a325fa 100644 --- a/packages/integration-test/test/scheduled/mocks.ts +++ b/packages/integration-test/test/scheduled/mocks.ts @@ -12,6 +12,7 @@ export const mockAdvancedLogic: AdvancedLogicTypes.IAdvancedLogic = { applyActionToExtensions: jest.fn(), getNativeTokenExtensionForNetwork: jest.fn(), getAnyToNativeTokenExtensionForNetwork: jest.fn(), + getFeeProxyContractErc20ForNetwork: jest.fn(), extensions: { addressBasedBtc: {} as Extension.PnAddressBased.IAddressBased, diff --git a/packages/payment-detection/codegen-near.yml b/packages/payment-detection/codegen-near.yml index 6f56ba7bf9..0af2c183c8 100644 --- a/packages/payment-detection/codegen-near.yml +++ b/packages/payment-detection/codegen-near.yml @@ -1,5 +1,5 @@ overwrite: true -schema: 'https://api.thegraph.com/subgraphs/name/requestnetwork/request-payments-near' +schema: 'https://api.thegraph.com/subgraphs/name/requestnetwork/request-payments-near-testnet' documents: src/thegraph/queries/near/*.graphql generates: src/thegraph/generated/graphql-near.ts: diff --git a/packages/payment-detection/src/any/any-to-erc20-proxy.ts b/packages/payment-detection/src/any/any-to-erc20-proxy.ts index 4a2a8645b9..e55f22fcf8 100644 --- a/packages/payment-detection/src/any/any-to-erc20-proxy.ts +++ b/packages/payment-detection/src/any/any-to-erc20-proxy.ts @@ -9,7 +9,7 @@ import { ERC20FeeProxyPaymentDetectorBase } from '../erc20/fee-proxy-contract'; import { AnyToErc20InfoRetriever } from './retrievers/any-to-erc20-proxy'; import { TheGraphConversionInfoRetriever } from '../thegraph/conversion-info-retriever'; import { makeGetDeploymentInformation } from '../utils'; -import { PaymentNetworkOptions, ReferenceBasedDetectorOptions } from '../types'; +import { PaymentNetworkOptions, ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types'; import { generate8randomBytes } from '@requestnetwork/utils'; import { EvmChains } from '@requestnetwork/currency'; @@ -24,7 +24,7 @@ export class AnyToERC20PaymentDetector extends ERC20FeeProxyPaymentDetectorBase< ExtensionTypes.PnAnyToErc20.IAnyToERC20, PaymentTypes.IERC20FeePaymentEventParameters > { - private readonly getSubgraphClient: PaymentNetworkOptions['getSubgraphClient']; + private readonly getSubgraphClient: TGetSubGraphClient; /** * @param extension The advanced logic payment network extensions @@ -34,7 +34,8 @@ export class AnyToERC20PaymentDetector extends ERC20FeeProxyPaymentDetectorBase< advancedLogic, currencyManager, getSubgraphClient, - }: ReferenceBasedDetectorOptions & Pick) { + }: ReferenceBasedDetectorOptions & + Pick, 'getSubgraphClient'>) { super( ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY, advancedLogic.extensions.anyToErc20Proxy, diff --git a/packages/payment-detection/src/any/any-to-eth-proxy.ts b/packages/payment-detection/src/any/any-to-eth-proxy.ts index b06a515bd6..f66b6cbde5 100644 --- a/packages/payment-detection/src/any/any-to-eth-proxy.ts +++ b/packages/payment-detection/src/any/any-to-eth-proxy.ts @@ -12,7 +12,7 @@ import { AnyToEthInfoRetriever } from './retrievers/any-to-eth-proxy'; import { AnyToAnyDetector } from '../any-to-any-detector'; import { makeGetDeploymentInformation } from '../utils'; import { TheGraphConversionInfoRetriever } from '../thegraph/conversion-info-retriever'; -import { PaymentNetworkOptions, ReferenceBasedDetectorOptions } from '../types'; +import { PaymentNetworkOptions, ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types'; // interface of the object indexing the proxy contract version interface IProxyContractVersion { @@ -31,7 +31,7 @@ export class AnyToEthFeeProxyPaymentDetector extends AnyToAnyDetector< ExtensionTypes.PnAnyToEth.IAnyToEth, PaymentTypes.IETHFeePaymentEventParameters > { - private readonly getSubgraphClient: PaymentNetworkOptions['getSubgraphClient']; + private readonly getSubgraphClient: TGetSubGraphClient; /** * @param extension The advanced logic payment network extensions */ @@ -39,7 +39,8 @@ export class AnyToEthFeeProxyPaymentDetector extends AnyToAnyDetector< advancedLogic, currencyManager, getSubgraphClient, - }: ReferenceBasedDetectorOptions & Pick) { + }: ReferenceBasedDetectorOptions & + Pick, 'getSubgraphClient'>) { super( ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ETH_PROXY, advancedLogic.extensions.anyToEthProxy, diff --git a/packages/payment-detection/src/erc20/fee-proxy-contract.ts b/packages/payment-detection/src/erc20/fee-proxy-contract.ts index cc0cb41623..6eea1531c4 100644 --- a/packages/payment-detection/src/erc20/fee-proxy-contract.ts +++ b/packages/payment-detection/src/erc20/fee-proxy-contract.ts @@ -5,18 +5,27 @@ import { PaymentTypes, RequestLogicTypes, } from '@requestnetwork/types'; -import { CurrencyDefinition, ICurrencyManager } from '@requestnetwork/currency'; +import { + CurrencyDefinition, + EvmChains, + ICurrencyManager, + NearChains, + isSameChain, +} from '@requestnetwork/currency'; import ProxyInfoRetriever from './proxy-info-retriever'; import { loadCurrencyFromContract } from './currency'; import { FeeReferenceBasedDetector } from '../fee-reference-based-detector'; import { makeGetDeploymentInformation } from '../utils'; -import { TheGraphInfoRetriever } from '../thegraph'; -import { PaymentNetworkOptions, ReferenceBasedDetectorOptions } from '../types'; +import { TheGraphClient, TheGraphInfoRetriever } from '../thegraph'; +import { ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types'; +import { NearInfoRetriever } from '../near'; +import { NetworkNotSupported } from '../balance-error'; const PROXY_CONTRACT_ADDRESS_MAP = { ['0.1.0']: '0.1.0', ['0.2.0']: '0.2.0', + ['NEAR-0.1.0']: 'near', }; /** @@ -58,27 +67,44 @@ export abstract class ERC20FeeProxyPaymentDetectorBase< } return contractCurrency; } + + /* + * Returns deployment information for the underlying smart contract for a given payment network version + */ + public static getDeploymentInformation = makeGetDeploymentInformation( + erc20FeeProxyArtifact, + PROXY_CONTRACT_ADDRESS_MAP, + ); } /** - * Handle payment networks with ERC20 fee proxy contract extension + * Handle payment networks with ERC20 fee proxy contract extension on EVM (default) or Near chains */ -export class ERC20FeeProxyPaymentDetector extends ERC20FeeProxyPaymentDetectorBase< +export class ERC20FeeProxyPaymentDetector< + TChain extends CurrencyTypes.VMChainName = CurrencyTypes.EvmChainName, +> extends ERC20FeeProxyPaymentDetectorBase< ExtensionTypes.PnFeeReferenceBased.IFeeReferenceBased, PaymentTypes.IERC20FeePaymentEventParameters > { - private readonly getSubgraphClient: PaymentNetworkOptions['getSubgraphClient']; + private readonly getSubgraphClient: TGetSubGraphClient; + protected readonly network: TChain | undefined; constructor({ advancedLogic, currencyManager, getSubgraphClient, - }: ReferenceBasedDetectorOptions & Pick) { + network, + }: ReferenceBasedDetectorOptions & { + network?: TChain; + getSubgraphClient: TGetSubGraphClient; + }) { super( ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT, - advancedLogic.extensions.feeProxyContractErc20, + advancedLogic.getFeeProxyContractErc20ForNetwork(network) ?? + advancedLogic.extensions.feeProxyContractErc20, currencyManager, ); this.getSubgraphClient = getSubgraphClient; + this.network = network; } /** @@ -89,9 +115,14 @@ export class ERC20FeeProxyPaymentDetector extends ERC20FeeProxyPaymentDetectorBa toAddress: string | undefined, paymentReference: string, requestCurrency: RequestLogicTypes.ICurrency, - paymentChain: CurrencyTypes.EvmChainName, - paymentNetwork: ExtensionTypes.IState, + paymentChain: TChain, + paymentNetwork: ExtensionTypes.IState, ): Promise> { + if (this.network && !isSameChain(paymentChain, this.network)) { + throw new NetworkNotSupported( + `Unsupported network '${paymentChain}' for payment detector instanciated with '${this.network}'`, + ); + } if (!toAddress) { return Promise.resolve({ paymentEvents: [], @@ -103,7 +134,7 @@ export class ERC20FeeProxyPaymentDetector extends ERC20FeeProxyPaymentDetectorBa const subgraphClient = this.getSubgraphClient(paymentChain); if (subgraphClient) { - const graphInfoRetriever = new TheGraphInfoRetriever(subgraphClient, this.currencyManager); + const graphInfoRetriever = this.getTheGraphInfoRetriever(paymentChain, subgraphClient); return graphInfoRetriever.getTransferEvents({ eventName, paymentReference, @@ -113,6 +144,11 @@ export class ERC20FeeProxyPaymentDetector extends ERC20FeeProxyPaymentDetectorBa acceptedTokens: [requestCurrency.value], }); } else { + if (!EvmChains.isChainSupported(paymentChain)) { + throw new Error( + `Could not get a TheGraph-based info retriever for chain ${paymentChain} and RPC-based info retrievers are only compatible with EVM chains.`, + ); + } const proxyInfoRetriever = new ProxyInfoRetriever( paymentReference, proxyContractAddress, @@ -129,11 +165,20 @@ export class ERC20FeeProxyPaymentDetector extends ERC20FeeProxyPaymentDetectorBa } } - /* - * Returns deployment information for the underlying smart contract for a given payment network version - */ - public static getDeploymentInformation = makeGetDeploymentInformation( - erc20FeeProxyArtifact, - PROXY_CONTRACT_ADDRESS_MAP, - ); + protected getTheGraphInfoRetriever( + paymentChain: TChain, + subgraphClient: TheGraphClient | TheGraphClient, + ): TheGraphInfoRetriever | NearInfoRetriever { + const graphInfoRetriever = EvmChains.isChainSupported(paymentChain) + ? new TheGraphInfoRetriever(subgraphClient as TheGraphClient, this.currencyManager) + : NearChains.isChainSupported(paymentChain) && this.network + ? new NearInfoRetriever(subgraphClient as TheGraphClient) + : undefined; + if (!graphInfoRetriever) { + throw new Error( + `Could not find graphInfoRetriever for chain ${paymentChain} in payment detector`, + ); + } + return graphInfoRetriever; + } } diff --git a/packages/payment-detection/src/erc20/proxy-contract.ts b/packages/payment-detection/src/erc20/proxy-contract.ts index 8b34772b98..ed7205509c 100644 --- a/packages/payment-detection/src/erc20/proxy-contract.ts +++ b/packages/payment-detection/src/erc20/proxy-contract.ts @@ -9,7 +9,7 @@ import ProxyInfoRetriever from './proxy-info-retriever'; import { TheGraphInfoRetriever } from '../thegraph'; import { makeGetDeploymentInformation } from '../utils'; import { ReferenceBasedDetector } from '../reference-based-detector'; -import { PaymentNetworkOptions, ReferenceBasedDetectorOptions } from '../types'; +import { PaymentNetworkOptions, ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types'; const PROXY_CONTRACT_ADDRESS_MAP = { ['0.1.0']: '0.1.0', @@ -22,7 +22,7 @@ export class ERC20ProxyPaymentDetector extends ReferenceBasedDetector< ExtensionTypes.PnReferenceBased.IReferenceBased, PaymentTypes.IERC20PaymentEventParameters > { - private readonly getSubgraphClient: PaymentNetworkOptions['getSubgraphClient']; + private readonly getSubgraphClient: TGetSubGraphClient; /** * @param extension The advanced logic payment network extensions @@ -31,7 +31,8 @@ export class ERC20ProxyPaymentDetector extends ReferenceBasedDetector< advancedLogic, currencyManager, getSubgraphClient, - }: ReferenceBasedDetectorOptions & Pick) { + }: ReferenceBasedDetectorOptions & + Pick, 'getSubgraphClient'>) { super( ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_PROXY_CONTRACT, advancedLogic.extensions.proxyContractErc20, diff --git a/packages/payment-detection/src/erc20/transferable-receivable.ts b/packages/payment-detection/src/erc20/transferable-receivable.ts index dcdb511f3c..ce5dbeaa2c 100644 --- a/packages/payment-detection/src/erc20/transferable-receivable.ts +++ b/packages/payment-detection/src/erc20/transferable-receivable.ts @@ -8,7 +8,7 @@ import { import { TheGraphInfoRetriever } from '../thegraph'; import { erc20TransferableReceivableArtifact } from '@requestnetwork/smart-contracts'; import { makeGetDeploymentInformation } from '../utils'; -import { PaymentNetworkOptions, ReferenceBasedDetectorOptions } from '../types'; +import { PaymentNetworkOptions, ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types'; import { FeeReferenceBasedDetector } from '../fee-reference-based-detector'; import ProxyERC20InfoRetriever from './proxy-info-retriever'; @@ -23,7 +23,7 @@ export class ERC20TransferableReceivablePaymentDetector extends FeeReferenceBase ExtensionTypes.PnFeeReferenceBased.IFeeReferenceBased, PaymentTypes.IERC20PaymentEventParameters > { - private readonly getSubgraphClient: PaymentNetworkOptions['getSubgraphClient']; + private readonly getSubgraphClient: TGetSubGraphClient; /** * @param extension The advanced logic payment network extensions @@ -32,7 +32,8 @@ export class ERC20TransferableReceivablePaymentDetector extends FeeReferenceBase advancedLogic, currencyManager, getSubgraphClient, - }: ReferenceBasedDetectorOptions & Pick) { + }: ReferenceBasedDetectorOptions & + Pick, 'getSubgraphClient'>) { super( ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_TRANSFERABLE_RECEIVABLE, advancedLogic.extensions.erc20TransferableReceivable, diff --git a/packages/payment-detection/src/eth/fee-proxy-detector.ts b/packages/payment-detection/src/eth/fee-proxy-detector.ts index 34b8f434b0..2f27e1b43f 100644 --- a/packages/payment-detection/src/eth/fee-proxy-detector.ts +++ b/packages/payment-detection/src/eth/fee-proxy-detector.ts @@ -10,7 +10,7 @@ import { EthProxyInfoRetriever } from './proxy-info-retriever'; import { FeeReferenceBasedDetector } from '../fee-reference-based-detector'; import { makeGetDeploymentInformation } from '../utils'; import { TheGraphInfoRetriever } from '../thegraph'; -import { PaymentNetworkOptions, ReferenceBasedDetectorOptions } from '../types'; +import { PaymentNetworkOptions, ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types'; // interface of the object indexing the proxy contract version interface IProxyContractVersion { @@ -29,7 +29,7 @@ export class EthFeeProxyPaymentDetector extends FeeReferenceBasedDetector< ExtensionTypes.PnFeeReferenceBased.IFeeReferenceBased, PaymentTypes.IETHFeePaymentEventParameters > { - private readonly getSubgraphClient: PaymentNetworkOptions['getSubgraphClient']; + private readonly getSubgraphClient: TGetSubGraphClient; /** * @param extension The advanced logic payment network extensions */ @@ -37,7 +37,8 @@ export class EthFeeProxyPaymentDetector extends FeeReferenceBasedDetector< advancedLogic, currencyManager, getSubgraphClient, - }: ReferenceBasedDetectorOptions & Pick) { + }: ReferenceBasedDetectorOptions & + Pick, 'getSubgraphClient'>) { super( ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT, advancedLogic.extensions.feeProxyContractEth, diff --git a/packages/payment-detection/src/eth/input-data.ts b/packages/payment-detection/src/eth/input-data.ts index 68e33f8025..75e822d755 100644 --- a/packages/payment-detection/src/eth/input-data.ts +++ b/packages/payment-detection/src/eth/input-data.ts @@ -10,7 +10,7 @@ import { EthProxyInfoRetriever } from './proxy-info-retriever'; import { ReferenceBasedDetector } from '../reference-based-detector'; import { makeGetDeploymentInformation } from '../utils'; import { TheGraphInfoRetriever } from '../thegraph'; -import { PaymentNetworkOptions, ReferenceBasedDetectorOptions } from '../types'; +import { PaymentNetworkOptions, ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types'; // interface of the object indexing the proxy contract version interface IProxyContractVersion { @@ -32,7 +32,7 @@ export class EthInputDataPaymentDetector extends ReferenceBasedDetector< PaymentTypes.IETHPaymentEventParameters > { private explorerApiKeys: Partial>; - private readonly getSubgraphClient: PaymentNetworkOptions['getSubgraphClient']; + private readonly getSubgraphClient: TGetSubGraphClient; /** * @param extension The advanced logic payment network extensions @@ -43,7 +43,10 @@ export class EthInputDataPaymentDetector extends ReferenceBasedDetector< explorerApiKeys, getSubgraphClient, }: ReferenceBasedDetectorOptions & - Pick) { + Pick< + PaymentNetworkOptions, + 'explorerApiKeys' | 'getSubgraphClient' + >) { super( ExtensionTypes.PAYMENT_NETWORK_ID.ETH_INPUT_DATA, advancedLogic.extensions.ethereumInputData, diff --git a/packages/payment-detection/src/native-token-detector.ts b/packages/payment-detection/src/native-token-detector.ts index 44d0bbd4a3..943d769160 100644 --- a/packages/payment-detection/src/native-token-detector.ts +++ b/packages/payment-detection/src/native-token-detector.ts @@ -1,4 +1,4 @@ -import { ExtensionTypes, PaymentTypes } from '@requestnetwork/types'; +import { CurrencyTypes, ExtensionTypes, PaymentTypes } from '@requestnetwork/types'; import { ReferenceBasedDetector } from './reference-based-detector'; import { NativeDetectorOptions } from './types'; @@ -10,7 +10,14 @@ export abstract class NativeTokenPaymentDetector extends ReferenceBasedDetector< ExtensionTypes.PnReferenceBased.IReferenceBased, PaymentTypes.IETHPaymentEventParameters > { - protected constructor({ network, advancedLogic, currencyManager }: NativeDetectorOptions) { + protected readonly network: CurrencyTypes.NearChainName | undefined; + protected readonly getSubgraphClient: NativeDetectorOptions['getSubgraphClient']; + protected constructor({ + network, + advancedLogic, + currencyManager, + getSubgraphClient, + }: NativeDetectorOptions) { const extensionId = ExtensionTypes.PAYMENT_NETWORK_ID.NATIVE_TOKEN; const extension = advancedLogic.getNativeTokenExtensionForNetwork( network, @@ -19,5 +26,7 @@ export abstract class NativeTokenPaymentDetector extends ReferenceBasedDetector< throw new Error(`the ${extensionId} extension is not supported for the network ${network}`); } super(extensionId, extension, currencyManager); + this.getSubgraphClient = getSubgraphClient; + this.network = network; } } diff --git a/packages/payment-detection/src/near/near-conversion-detector.ts b/packages/payment-detection/src/near/near-conversion-detector.ts index 7c9dc7f22a..fadac7f48f 100644 --- a/packages/payment-detection/src/near/near-conversion-detector.ts +++ b/packages/payment-detection/src/near/near-conversion-detector.ts @@ -24,8 +24,10 @@ const CONTRACT_ADDRESS_MAP: IProxyContractVersion = { * Handle payment detection for NEAR native token payment with conversion */ export class NearConversionNativeTokenPaymentDetector extends AnyToNativeDetector { + private readonly getSubgraphClient: NativeDetectorOptions['getSubgraphClient']; constructor(args: NativeDetectorOptions) { super(args); + this.getSubgraphClient = args.getSubgraphClient; } public static getContractName = ( @@ -36,6 +38,7 @@ export class NearConversionNativeTokenPaymentDetector extends AnyToNativeDetecto NearConversionNativeTokenPaymentDetector.getVersionOrThrow(paymentNetworkVersion); const versionMap: Record> = { aurora: { '0.1.0': 'native.conversion.reqnetwork.near' }, + near: { '0.1.0': 'native.conversion.reqnetwork.near' }, 'aurora-testnet': { '0.1.0': 'native.conversion.reqnetwork.testnet', }, @@ -54,7 +57,7 @@ export class NearConversionNativeTokenPaymentDetector extends AnyToNativeDetecto /** * Extracts the events for an address and a payment reference * - * @param address Address to check + * @param toAddress Address to check * @param eventName Indicate if it is an address for payment or refund * @param requestCurrency The request currency * @param paymentReference The reference to identify the payment @@ -63,13 +66,13 @@ export class NearConversionNativeTokenPaymentDetector extends AnyToNativeDetecto */ protected async extractEvents( eventName: PaymentTypes.EVENTS_NAMES, - address: string | undefined, + toAddress: string | undefined, paymentReference: string, requestCurrency: RequestLogicTypes.ICurrency, paymentChain: CurrencyTypes.NearChainName, paymentNetwork: ExtensionTypes.IState, ): Promise> { - if (!address) { + if (!toAddress) { return { paymentEvents: [], }; @@ -80,22 +83,25 @@ export class NearConversionNativeTokenPaymentDetector extends AnyToNativeDetecto throw new UnsupportedCurrencyError(requestCurrency.value); } - const infoRetriever = new NearConversionInfoRetriever( - currency, + const subgraphClient = this.getSubgraphClient(paymentChain); + + if (!subgraphClient) { + throw new Error(`Error getting subgraph client for ${paymentChain}`); + } + const infoRetriever = new NearConversionInfoRetriever(subgraphClient); + const transferEvents = await infoRetriever.getTransferEvents({ + requestCurrency: currency, paymentReference, - address, - NearConversionNativeTokenPaymentDetector.getContractName( + toAddress, + contractAddress: NearConversionNativeTokenPaymentDetector.getContractName( paymentChain, paymentNetwork.version, ), eventName, paymentChain, - paymentNetwork.values.maxRateTimespan, - ); - const paymentEvents = await infoRetriever.getTransferEvents(); - return { - paymentEvents, - }; + maxRateTimespan: paymentNetwork.values.maxRateTimespan, + }); + return transferEvents; } protected getPaymentChain(request: RequestLogicTypes.IRequest): CurrencyTypes.NearChainName { diff --git a/packages/payment-detection/src/near/near-detector.ts b/packages/payment-detection/src/near/near-detector.ts index 3a97a57580..97311a46fd 100644 --- a/packages/payment-detection/src/near/near-detector.ts +++ b/packages/payment-detection/src/near/near-detector.ts @@ -35,6 +35,7 @@ export class NearNativeTokenPaymentDetector extends NativeTokenPaymentDetector { const version = NearNativeTokenPaymentDetector.getVersionOrThrow(paymentNetworkVersion); const versionMap: Record> = { aurora: { '0.1.0': 'requestnetwork.near', '0.2.0': 'requestnetwork.near' }, + near: { '0.1.0': 'requestnetwork.near', '0.2.0': 'requestnetwork.near' }, 'aurora-testnet': { '0.1.0': 'dev-1626339335241-5544297', '0.2.0': 'dev-1631521265288-35171138540673', @@ -74,14 +75,23 @@ export class NearNativeTokenPaymentDetector extends NativeTokenPaymentDetector { paymentEvents: [], }; } - const infoRetriever = new NearInfoRetriever( + const subgraphClient = this.getSubgraphClient(paymentChain); + if (!subgraphClient) { + throw new Error( + `Could not find graphInfoRetriever for chain ${paymentChain} in payment detector`, + ); + } + const infoRetriever = new NearInfoRetriever(subgraphClient); + const { paymentEvents } = await infoRetriever.getTransferEvents({ paymentReference, - address, - NearNativeTokenPaymentDetector.getContractName(paymentChain, paymentNetwork.version), + toAddress: address, + contractAddress: NearNativeTokenPaymentDetector.getContractName( + paymentChain, + paymentNetwork.version, + ), eventName, paymentChain, - ); - const paymentEvents = await infoRetriever.getTransferEvents(); + }); return { paymentEvents, }; diff --git a/packages/payment-detection/src/near/retrievers/near-conversion-info-retriever.ts b/packages/payment-detection/src/near/retrievers/near-conversion-info-retriever.ts index 050c4ac6cf..3e4280bf0c 100644 --- a/packages/payment-detection/src/near/retrievers/near-conversion-info-retriever.ts +++ b/packages/payment-detection/src/near/retrievers/near-conversion-info-retriever.ts @@ -1,11 +1,24 @@ -import { PaymentTypes } from '@requestnetwork/types'; +import { CurrencyTypes, PaymentTypes } from '@requestnetwork/types'; import { CurrencyDefinition } from '@requestnetwork/currency'; -import { NearInfoRetriever } from './near-info-retriever'; +import { NearInfoRetriever, NearPaymentEvent } from './near-info-retriever'; +import { TheGraphClient } from '../../thegraph'; -// FIXME#1: when Near subgraphes can retrieve a txHash, replace the custom IPaymentNetworkEvent with PaymentTypes.ETHPaymentNetworkEvent -interface NearSubGraphPaymentEvent extends PaymentTypes.IETHPaymentEventParameters { - receiptId: string; -} +export type TransferEventsParams = { + /** The reference to identify the payment*/ + paymentReference: string; + /** Request denomination (usually fiat) */ + requestCurrency: CurrencyDefinition; + /** The recipient of the transfer */ + toAddress: string; + /** The address of the payment proxy */ + contractAddress: string; + /** The chain to check for payment */ + paymentChain: CurrencyTypes.VMChainName; + /** Indicates if it is an address for payment or refund */ + eventName: PaymentTypes.EVENTS_NAMES; + /** The maximum span between the time the rate was fetched and the payment */ + maxRateTimespan?: number; +}; /** * Gets a list of transfer events for a set of Near payment details @@ -17,46 +30,47 @@ export class NearConversionInfoRetriever extends NearInfoRetriever { * @param eventName Indicate if it is an address for payment or refund * @param network The id of network we want to check */ - constructor( - protected requestCurrency: CurrencyDefinition, - protected paymentReference: string, - protected toAddress: string, - protected proxyContractName: string, - protected eventName: PaymentTypes.EVENTS_NAMES, - protected network: string, - protected maxRateTimespan: number = 0, - ) { - super(paymentReference, toAddress, proxyContractName, eventName, network); + constructor(protected readonly client: TheGraphClient) { + super(client); } - - public async getTransferEvents(): Promise< - PaymentTypes.IPaymentNetworkEvent[] - > { + public async getTransferEvents( + params: TransferEventsParams, + ): Promise> { + const { + requestCurrency, + paymentReference, + toAddress, + contractAddress, + eventName, + maxRateTimespan, + } = params; const payments = await this.client.GetAnyToNativePayments({ - reference: this.paymentReference, - to: this.toAddress, - currency: this.requestCurrency.symbol, - maxRateTimespan: this.maxRateTimespan, - contractAddress: this.proxyContractName, + reference: paymentReference, + to: toAddress, + currency: requestCurrency.symbol, + maxRateTimespan: maxRateTimespan ?? 0, + contractAddress: contractAddress, }); - return payments.payments.map((p: any) => ({ - amount: p.amount, - name: this.eventName, - parameters: { - block: p.block, - feeAddress: p.feeAddress || undefined, - feeAmount: p.feeAmount, - feeAmountInCrypto: p.feeAmountInCrypto || undefined, - amountInCrypto: p.amountInCrypto, - to: this.toAddress, - maxRateTimespan: p.maxRateTimespan?.toString(), - from: p.from, - gasUsed: p.gasUsed, - gasPrice: p.gasPrice, - receiptId: p.receiptId, - currency: p.currency, - }, - timestamp: Number(p.timestamp), - })); + return { + paymentEvents: payments.payments.map((p: any) => ({ + amount: p.amount, + name: eventName, + parameters: { + block: p.block, + feeAddress: p.feeAddress || undefined, + feeAmount: p.feeAmount, + feeAmountInCrypto: p.feeAmountInCrypto || undefined, + amountInCrypto: p.amountInCrypto, + to: toAddress, + maxRateTimespan: p.maxRateTimespan?.toString(), + from: p.from, + gasUsed: p.gasUsed, + gasPrice: p.gasPrice, + receiptId: p.receiptId, + currency: p.currency, + }, + timestamp: Number(p.timestamp), + })), + }; } } diff --git a/packages/payment-detection/src/near/retrievers/near-info-retriever.ts b/packages/payment-detection/src/near/retrievers/near-info-retriever.ts index a3927d64a2..855bdc87f0 100644 --- a/packages/payment-detection/src/near/retrievers/near-info-retriever.ts +++ b/packages/payment-detection/src/near/retrievers/near-info-retriever.ts @@ -1,62 +1,81 @@ -import { PaymentTypes } from '@requestnetwork/types'; -import { getTheGraphNearClient, TheGraphClient } from '../../thegraph'; -import { NearChains } from '@requestnetwork/currency'; +import { CurrencyTypes, PaymentTypes } from '@requestnetwork/types'; +import { TheGraphClient } from '../../thegraph'; +import { GetNearPaymentsQuery } from 'payment-detection/src/thegraph/generated/graphql-near'; +import { ITheGraphBaseInfoRetriever } from 'payment-detection/src/types'; // FIXME#1: when Near subgraphes can retrieve a txHash, replace the custom IPaymentNetworkEvent with PaymentTypes.ETHPaymentNetworkEvent -interface NearSubGraphPaymentEvent extends PaymentTypes.IETHPaymentEventParameters { +export interface NearPaymentEvent extends PaymentTypes.IERC20FeePaymentEventParameters { receiptId: string; } +export type TransferEventsParams = { + /** The reference to identify the payment*/ + paymentReference: string; + /** The recipient of the transfer */ + toAddress: string; + /** The address of the payment proxy */ + contractAddress: string; + /** The chain to check for payment */ + paymentChain: CurrencyTypes.VMChainName; + /** Indicates if it is an address for payment or refund */ + eventName: PaymentTypes.EVENTS_NAMES; + /** The list of ERC20 tokens addresses accepted for payments and refunds. Set to `undefined` for payments in NEAR token. */ + acceptedTokens?: string[]; +}; /** * Gets a list of transfer events for a set of Near payment details + * TheGraph-based etriever for ERC20 Fee Proxy and Native token payments. */ -export class NearInfoRetriever { - protected client: TheGraphClient<'near'>; +export class NearInfoRetriever implements ITheGraphBaseInfoRetriever { /** * @param paymentReference The reference to identify the payment * @param toAddress Address to check * @param eventName Indicate if it is an address for payment or refund - * @param network The id of network we want to check * */ - constructor( - protected paymentReference: string, - protected toAddress: string, - protected proxyContractName: string, - protected eventName: PaymentTypes.EVENTS_NAMES, - network: string, - ) { - try { - NearChains.assertChainSupported(network); - } catch { - throw new Error('Near input data info-retriever only works with Near mainnet and testnet'); - } + constructor(protected readonly client: TheGraphClient) {} - network = network.replace('aurora', 'near'); - this.client = getTheGraphNearClient( - `https://api.thegraph.com/subgraphs/name/requestnetwork/request-payments-${network}`, - ); + public async getTransferEvents( + params: TransferEventsParams, + ): Promise> { + const { paymentReference, toAddress, contractAddress, acceptedTokens } = params; + if (acceptedTokens && acceptedTokens.length > 1) + throw new Error(`NearInfoRetriever does not support multiple accepted tokens.`); + const payments = + acceptedTokens?.length === 1 + ? await this.client.GetFungibleTokenPayments({ + reference: paymentReference, + to: toAddress, + contractAddress, + tokenAddress: acceptedTokens[0], + }) + : await this.client.GetNearPayments({ + reference: paymentReference, + to: toAddress, + contractAddress, + }); + return { + paymentEvents: payments.payments.map((p) => this.mapPaymentEvent(p, params)), + }; } - public async getTransferEvents(): Promise< - PaymentTypes.IPaymentNetworkEvent[] - > { - const payments = await this.client.GetNearPayments({ - reference: this.paymentReference, - to: this.toAddress, - contractAddress: this.proxyContractName, - }); - return payments.payments.map((p) => ({ - amount: p.amount, - name: this.eventName, + private mapPaymentEvent( + payment: GetNearPaymentsQuery['payments'][0], + params: TransferEventsParams, + ): PaymentTypes.IPaymentNetworkEvent { + return { + amount: payment.amount, + name: params.eventName, + timestamp: Number(payment.timestamp), parameters: { - block: p.block, - confirmations: p.block, - // Cf. FIXME#1 above - // txHash: transaction.txHash, - receiptId: p.receiptId, + feeAmount: payment.feeAmount, + receiptId: payment.receiptId, + block: payment.block, + to: params.toAddress, + from: payment.from, + feeAddress: payment.feeAddress ?? undefined, + tokenAddress: params.acceptedTokens ? params.acceptedTokens[0] : undefined, }, - timestamp: Number(p.timestamp), - })); + }; } } diff --git a/packages/payment-detection/src/payment-detector-base.ts b/packages/payment-detection/src/payment-detector-base.ts index b105ceaa12..8ecfdb58fc 100644 --- a/packages/payment-detection/src/payment-detector-base.ts +++ b/packages/payment-detection/src/payment-detector-base.ts @@ -8,7 +8,7 @@ import { export abstract class PaymentDetectorBase< TExtension extends ExtensionTypes.IExtension, - TPaymentEventParameters, + TPaymentEventParameters extends PaymentTypes.GenericEventParameters, > implements PaymentTypes.IPaymentNetwork { protected constructor( diff --git a/packages/payment-detection/src/payment-network-factory.ts b/packages/payment-detection/src/payment-network-factory.ts index d61ef44939..b97af4d648 100644 --- a/packages/payment-detection/src/payment-network-factory.ts +++ b/packages/payment-detection/src/payment-network-factory.ts @@ -46,6 +46,16 @@ const supportedPaymentNetwork: ISupportedPaymentNetworkByCurrency = { }, }, ERC20: { + aurora: { + [PN_ID.ERC20_FEE_PROXY_CONTRACT]: ERC20FeeProxyPaymentDetector, + }, + 'aurora-testnet': { + [PN_ID.ERC20_FEE_PROXY_CONTRACT]: ERC20FeeProxyPaymentDetector, + }, + 'near-testnet': { + [PN_ID.ERC20_FEE_PROXY_CONTRACT]: ERC20FeeProxyPaymentDetector, + }, + '*': { [PN_ID.ERC20_ADDRESS_BASED]: ERC20AddressBasedPaymentDetector, [PN_ID.ERC20_PROXY_CONTRACT]: ERC20ProxyPaymentDetector, @@ -108,7 +118,7 @@ export class PaymentNetworkFactory { } /** - * Creates a payment network according to payment network creation parameters + * Creates a payment network interpretor according to payment network creation parameters * It throws if the payment network given is not supported by this library * * @param paymentNetworkId the ID of the payment network to instantiate @@ -150,7 +160,7 @@ export class PaymentNetworkFactory { if (detector.extension && 'getDeploymentInformation' in detectorClass) { // this throws when the contract isn't deployed and was mandatory for payment detection (detectorClass as ContractBasedDetector).getDeploymentInformation( - network as CurrencyTypes.EvmChainName, + network as CurrencyTypes.VMChainName, paymentNetworkVersion || detector.extension.currentVersion, ); } diff --git a/packages/payment-detection/src/thegraph/client.ts b/packages/payment-detection/src/thegraph/client.ts index e1aa631af5..1803ab10e0 100644 --- a/packages/payment-detection/src/thegraph/client.ts +++ b/packages/payment-detection/src/thegraph/client.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { CurrencyTypes } from '@requestnetwork/types'; import { GraphQLClient } from 'graphql-request'; import { getSdk } from './generated/graphql'; import { getSdk as getNearSdk } from './generated/graphql-near'; @@ -11,8 +12,8 @@ import { getSdk as getNearSdk } from './generated/graphql-near'; * * @type TGraphClientVariant: null if no variant, 'near' if native token payments detection on Near */ -export type TheGraphClient = - TGraphClientVariant extends 'near' +export type TheGraphClient = + TChain extends CurrencyTypes.NearChainName ? ReturnType : ReturnType; diff --git a/packages/payment-detection/src/thegraph/queries/near/GetAnyToNativePayments.graphql b/packages/payment-detection/src/thegraph/queries/near/GetAnyToNativePayments.graphql index 7bbf13653f..267aef3fa9 100644 --- a/packages/payment-detection/src/thegraph/queries/near/GetAnyToNativePayments.graphql +++ b/packages/payment-detection/src/thegraph/queries/near/GetAnyToNativePayments.graphql @@ -12,6 +12,7 @@ query GetAnyToNativePayments( currency: $currency maxRateTimespan_gte: $maxRateTimespan contractAddress: $contractAddress + tokenAddress: null } orderBy: timestamp orderDirection: asc @@ -24,6 +25,8 @@ query GetAnyToNativePayments( from timestamp currency + gasPrice + gasUsed maxRateTimespan amountInCrypto feeAmountInCrypto diff --git a/packages/payment-detection/src/thegraph/queries/near/GetFungibleTokenPayments.graphql b/packages/payment-detection/src/thegraph/queries/near/GetFungibleTokenPayments.graphql new file mode 100644 index 0000000000..9e37d6be97 --- /dev/null +++ b/packages/payment-detection/src/thegraph/queries/near/GetFungibleTokenPayments.graphql @@ -0,0 +1,40 @@ +fragment NearPaymentEventResult on Payment { + amount + block + receiptId + txHash + feeAmount + feeAddress + from + gasUsed + gasPrice + timestamp + contractAddress + to + tokenAddress + currency + amountInCrypto + feeAmountInCrypto + maxRateTimespan +} + +query GetFungibleTokenPayments( + $reference: String! + $to: String! + $contractAddress: String! + $tokenAddress: String! +) { + payments( + where: { + reference: $reference + to: $to + tokenAddress: $tokenAddress + contractAddress: $contractAddress + currency: $tokenAddress + } + orderBy: timestamp + orderDirection: asc + ) { + ...NearPaymentEventResult + } +} diff --git a/packages/payment-detection/src/thegraph/queries/near/GetPayments.graphql b/packages/payment-detection/src/thegraph/queries/near/GetNearPayments.graphql similarity index 71% rename from packages/payment-detection/src/thegraph/queries/near/GetPayments.graphql rename to packages/payment-detection/src/thegraph/queries/near/GetNearPayments.graphql index f8af0845c6..e1ce826715 100644 --- a/packages/payment-detection/src/thegraph/queries/near/GetPayments.graphql +++ b/packages/payment-detection/src/thegraph/queries/near/GetNearPayments.graphql @@ -1,15 +1,19 @@ +# Getting payments in NEAR denominated in NEAR query GetNearPayments($reference: String!, $to: String!, $contractAddress: String!) { payments( - where: { reference: $reference, to: $to, contractAddress: $contractAddress } + where: { reference: $reference, to: $to, contractAddress: $contractAddress, tokenAddress: null } orderBy: timestamp orderDirection: asc ) { amount + currency block receiptId feeAmount feeAddress from timestamp + gasPrice + gasUsed } } diff --git a/packages/payment-detection/src/types.ts b/packages/payment-detection/src/types.ts index 3321e397ab..3aa34d3f01 100644 --- a/packages/payment-detection/src/types.ts +++ b/packages/payment-detection/src/types.ts @@ -25,7 +25,7 @@ export type TransferEventsParams = { /** The address of the payment proxy */ contractAddress: string; /** The chain to check for payment */ - paymentChain: CurrencyTypes.EvmChainName; + paymentChain: CurrencyTypes.VMChainName; /** Indicates if it is an address for payment or refund */ eventName: PaymentTypes.EVENTS_NAMES; /** The list of ERC20 tokens addresses accepted for payments and refunds OR undefined for native tokens (e.g. ETH) */ @@ -66,30 +66,45 @@ export interface IEventRetriever< } /** Object interface to list the payment network module by id */ -export type IPaymentNetworkModuleByType = Partial< +export type IPaymentNetworkModuleByType< + TPaymentEventParameters extends PaymentTypes.GenericEventParameters = PaymentTypes.GenericEventParameters, +> = Partial< Record< ExtensionTypes.PAYMENT_NETWORK_ID, - new (...pnParams: any) => PaymentDetectorBase + new (...pnParams: any) => PaymentDetectorBase< + ExtensionTypes.IExtension, + TPaymentEventParameters + > > >; /** Object interface to list the payment network module by network */ -export interface ISupportedPaymentNetworkByNetwork { - [network: string]: IPaymentNetworkModuleByType; +export interface ISupportedPaymentNetworkByNetwork< + TPaymentEventParameters extends PaymentTypes.GenericEventParameters = PaymentTypes.GenericEventParameters, +> { + [network: string]: IPaymentNetworkModuleByType; } /** Object interface to list the payment network id and its module by currency */ -export interface ISupportedPaymentNetworkByCurrency { - [currency: string]: ISupportedPaymentNetworkByNetwork; +export interface ISupportedPaymentNetworkByCurrency< + TPaymentEventParameters extends PaymentTypes.GenericEventParameters = PaymentTypes.GenericEventParameters, +> { + [currency: string]: ISupportedPaymentNetworkByNetwork; } -export type PaymentNetworkOptions = { +export type TGetSubGraphClient = ( + network: CurrencyTypes.ChainName, +) => TChain extends CurrencyTypes.VMChainName ? TheGraphClient | undefined : undefined; + +export type PaymentNetworkOptions< + TChain extends CurrencyTypes.ChainName = CurrencyTypes.ChainName, +> = { /** override default bitcoin detection provider */ bitcoinDetectionProvider?: PaymentTypes.IBitcoinDetectionProvider; /** the explorer API (e.g. Etherscan) api keys, for PNs that rely on it. Record by network name */ explorerApiKeys: Partial>; /** override the default Subgraph for payment detection (EVM, Near) */ - getSubgraphClient: (network: CurrencyTypes.ChainName) => TheGraphClient | undefined; + getSubgraphClient: TGetSubGraphClient; /** override the default RPC provider (EVM) */ getRpcProvider: (network: CurrencyTypes.ChainName) => providers.Provider; }; @@ -100,5 +115,7 @@ export type ReferenceBasedDetectorOptions = { }; export type NativeDetectorOptions = ReferenceBasedDetectorOptions & { - network: CurrencyTypes.ChainName; + network: CurrencyTypes.NearChainName; + /** override the default Subgraph for payment detection (EVM, Near) */ + getSubgraphClient: TGetSubGraphClient; }; diff --git a/packages/payment-detection/src/utils.ts b/packages/payment-detection/src/utils.ts index 6a64326a33..ff4c72ed6d 100644 --- a/packages/payment-detection/src/utils.ts +++ b/packages/payment-detection/src/utils.ts @@ -1,4 +1,4 @@ -import { CurrencyDefinition } from '@requestnetwork/currency'; +import { CurrencyDefinition, isValidNearAddress } from '@requestnetwork/currency'; import { CurrencyTypes, ExtensionTypes, @@ -62,7 +62,7 @@ const getChainlinkPaddingSize = ({ export type DeploymentInformationWithVersion = DeploymentInformation & { contractVersion: string }; export type GetDeploymentInformation = ( - network: CurrencyTypes.EvmChainName, + network: CurrencyTypes.VMChainName, paymentNetworkVersion: string, ) => TAllowUndefined extends false ? DeploymentInformationWithVersion @@ -177,13 +177,19 @@ export function getPaymentReference( return PaymentReferenceCalculator.calculate(requestId, salt, info); } -/** Alias to ethers.utils.getAddress that adds the key to error message, and supports nullish values */ +/** + * For EVMs: alias to ethers.utils.getAddress that adds the key to error message, and supports nullish values. + * For other chains: applies lower-case to the address. + */ export const formatAddress: { (address: string | null | undefined, key?: string, allowsUndefined?: false): string; (address: string | null | undefined, key?: string, allowsUndefined?: true): string | undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any } = (address: string | null | undefined, key?: string, allowsUndefined = false): any => { if (!address && allowsUndefined) return undefined; + if (address && isValidNearAddress(address)) { + return address?.toLowerCase(); + } try { return getAddress(address || ''); } catch (e) { diff --git a/packages/payment-detection/test/erc20/fee-proxy-contract.test.ts b/packages/payment-detection/test/erc20/fee-proxy-contract.test.ts index 18d2939d2b..255e3e635f 100644 --- a/packages/payment-detection/test/erc20/fee-proxy-contract.test.ts +++ b/packages/payment-detection/test/erc20/fee-proxy-contract.test.ts @@ -4,12 +4,13 @@ import { IdentityTypes, PaymentTypes, RequestLogicTypes, + CurrencyTypes, } from '@requestnetwork/types'; import { CurrencyManager } from '@requestnetwork/currency'; import { ERC20FeeProxyPaymentDetector } from '../../src/erc20/fee-proxy-contract'; import { mockAdvancedLogicBase } from '../utils'; -let erc20FeeProxyContract: ERC20FeeProxyPaymentDetector; +let erc20FeeProxyContract: ERC20FeeProxyPaymentDetector; const createAddPaymentAddressAction = jest.fn(); const createAddRefundAddressAction = jest.fn(); @@ -18,23 +19,25 @@ const createAddFeeAction = jest.fn(); const createAddPaymentInstructionAction = jest.fn(); const createAddRefundInstructionAction = jest.fn(); +const feeProxyContractErc20 = { + createAddPaymentAddressAction, + createAddRefundAddressAction, + createCreationAction, + createAddFeeAction, + // inherited from declarative + createAddPaymentInstructionAction, + createAddRefundInstructionAction, +} as any as ExtensionTypes.PnFeeReferenceBased.IFeeReferenceBased; + const mockAdvancedLogic: AdvancedLogicTypes.IAdvancedLogic = { ...mockAdvancedLogicBase, extensions: { - feeProxyContractErc20: { - createAddPaymentAddressAction, - createAddRefundAddressAction, - createCreationAction, - createAddFeeAction, - // inherited from declarative - createAddPaymentInstructionAction, - createAddRefundInstructionAction, - }, + feeProxyContractErc20, } as any as AdvancedLogicTypes.IAdvancedLogicExtensions, + getFeeProxyContractErc20ForNetwork: (_network) => feeProxyContractErc20, }; const currencyManager = CurrencyManager.getDefault(); - /* eslint-disable @typescript-eslint/no-unused-expressions */ describe('api/erc20/fee-proxy-contract', () => { beforeEach(() => { @@ -466,4 +469,121 @@ describe('api/erc20/fee-proxy-contract', () => { }, ]); }); + + describe('on Near', () => { + beforeEach(() => { + // Same Detector, but instanciated with a Near network and a mocked Near graph client + erc20FeeProxyContract = new ERC20FeeProxyPaymentDetector({ + advancedLogic: mockAdvancedLogic, + currencyManager, + network: 'aurora-testnet', + getSubgraphClient: (_network) => ({ + GetFungibleTokenPayments: jest.fn().mockImplementation(() => ({ + payments: [ + { + contractAddress: 'pay.reqnetwork.testnet', + tokenAddress: 'fau.reqnetwork.testnet', + to: 'issuer.reqnetwork.testnet', + from: 'payer.reqnetwork.testnet', + amount: '168040800000000000000000', + feeAmount: '13386000000000000000', + reference: 'f59c9445040531b1', + block: 15767215, + gasUsed: '73152', + gasPrice: '12709127644', + timestamp: 1666002347, + amountInCrypto: null, + feeAddress: 'builder.reqnetwork.testnet', + feeAmountInCrypto: null, + maxRateTimespan: null, + }, + ], + })), + GetPaymentsAndEscrowState: jest.fn(), + GetAnyToNativePayments: jest.fn(), + GetNearPayments: jest.fn(), + GetLastSyncedBlock: jest.fn(), + GetSyncedBlock: jest.fn(), + }), + }); + }); + it('can createExtensionsDataForCreation', async () => { + await erc20FeeProxyContract.createExtensionsDataForCreation({ + paymentAddress: 'issuer.reqnetwork.testnet', + salt: 'ea3bc7caf64110ca', + }); + expect(createCreationAction).toHaveBeenCalledWith({ + feeAddress: undefined, + feeAmount: undefined, + paymentAddress: 'issuer.reqnetwork.testnet', + refundAddress: undefined, + salt: 'ea3bc7caf64110ca', + }); + }); + + it('can retrieve payment using thegraph info retriever', async () => { + const mockRequest: RequestLogicTypes.IRequest = { + creator: { type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, value: '0x2' }, + currency: { + network: 'aurora-testnet', + type: RequestLogicTypes.CURRENCY.ERC20, + value: 'fau.reqnetwork.testnet', + }, + events: [], + expectedAmount: '168040800000000000000000', + extensions: { + [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: { + events: [ + { + name: 'create', + parameters: { + feeAddress: 'builder.reqnetwork.testnet', + feeAmount: '13386000000000000000', + paymentAddress: 'issuer.reqnetwork.testnet', + salt: 'c75c317e05c52f12', + }, + timestamp: 1665989825, + }, + ], + id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + salt: 'c75c317e05c52f12', + paymentAddress: 'issuer.reqnetwork.testnet', + feeAddress: 'builder.reqnetwork.testnet', + feeAmount: '13386000000000000000', + }, + version: 'NEAR-0.1.0', + }, + }, + extensionsData: [], + requestId: '01169f05b855a57396552cc0052b161f70590bdf9c5371649cd89a70c65fb586db', + state: RequestLogicTypes.STATE.CREATED, + timestamp: 0, + version: '0.2', + }; + + const { balance, error, events } = await erc20FeeProxyContract.getBalance(mockRequest); + expect; + expect(error).toBeUndefined(); + expect(balance).toBe('168040800000000000000000'); + expect(events).toMatchObject([ + { + amount: '168040800000000000000000', + name: 'payment', + parameters: { + // block: 15767215, + feeAddress: 'builder.reqnetwork.testnet', + feeAmount: '13386000000000000000', + from: 'payer.reqnetwork.testnet', + // gasPrice: '12709127644', + // gasUsed: '73152', + to: 'issuer.reqnetwork.testnet', + tokenAddress: 'fau.reqnetwork.testnet', + }, + // timestamp: 1666002347, + }, + ]); + }); + }); }); diff --git a/packages/payment-detection/test/near/near-native-conversion.test.ts b/packages/payment-detection/test/near/near-native-conversion.test.ts index 2bf898d7a8..072c270e86 100644 --- a/packages/payment-detection/test/near/near-native-conversion.test.ts +++ b/packages/payment-detection/test/near/near-native-conversion.test.ts @@ -1,4 +1,9 @@ -import { ExtensionTypes, PaymentTypes, RequestLogicTypes } from '@requestnetwork/types'; +import { + CurrencyTypes, + ExtensionTypes, + PaymentTypes, + RequestLogicTypes, +} from '@requestnetwork/types'; import { CurrencyDefinition, CurrencyManager } from '@requestnetwork/currency'; import { PaymentNetworkFactory } from '../../src/payment-network-factory'; import PaymentReferenceCalculator from '../../src/payment-reference-calculator'; @@ -7,12 +12,10 @@ import { NearConversionNativeTokenPaymentDetector, } from '../../src/near'; import { deepCopy } from 'ethers/lib/utils'; -import { GraphQLClient } from 'graphql-request'; -import { mocked } from 'ts-jest/utils'; import { AdvancedLogic } from '@requestnetwork/advanced-logic'; +import { TheGraphClient } from '../../src'; jest.mock('graphql-request'); -const graphql = mocked(GraphQLClient.prototype); const currencyManager = CurrencyManager.getDefault(); const advancedLogic = new AdvancedLogic(currencyManager); const salt = 'a6475e4c3d45feb6'; @@ -62,14 +65,20 @@ const graphPaymentEvent = { gasPrice: '2425000017', }; +const client = { + GetAnyToNativePayments: jest.fn().mockImplementation(() => ({ + payments: [graphPaymentEvent], + })), +} as any as TheGraphClient; + +const infoRetriever = new NearConversionInfoRetriever(client); +const mockedGetSubgraphClient = jest.fn().mockImplementation(() => client); + const paymentNetworkFactory = new PaymentNetworkFactory(advancedLogic, currencyManager); describe('Near payments detection', () => { - beforeAll(() => { - graphql.request.mockResolvedValue({ - payments: [graphPaymentEvent], - }); + afterEach(() => { + jest.clearAllMocks(); }); - it('NearConversionInfoRetriever can retrieve a NEAR payment', async () => { const paymentReference = PaymentReferenceCalculator.calculate( request.requestId, @@ -77,17 +86,16 @@ describe('Near payments detection', () => { paymentAddress, ); - const infoRetriever = new NearConversionInfoRetriever( + const events = await infoRetriever.getTransferEvents({ requestCurrency, paymentReference, - paymentAddress, - 'native.conversion.mock', - PaymentTypes.EVENTS_NAMES.PAYMENT, - 'aurora', - ); - const events = await infoRetriever.getTransferEvents(); - expect(events).toHaveLength(1); - expect(events[0]).toEqual({ + toAddress: paymentAddress, + contractAddress: 'native.conversion.mock', + eventName: PaymentTypes.EVENTS_NAMES.PAYMENT, + paymentChain: 'aurora', + }); + expect(events.paymentEvents).toHaveLength(1); + expect(events.paymentEvents[0]).toEqual({ amount: graphPaymentEvent.amount, name: 'payment', parameters: { @@ -131,9 +139,11 @@ describe('Near payments detection', () => { network: 'aurora', advancedLogic: advancedLogic, currencyManager, + getSubgraphClient: mockedGetSubgraphClient, }); const balance = await paymentDetector.getBalance(request); + expect(mockedGetSubgraphClient).toHaveBeenCalled(); expect(balance.events).toHaveLength(1); expect(balance.balance).toBe(graphPaymentEvent.amount); }); @@ -156,7 +166,9 @@ describe('Near payments detection', () => { network: 'aurora', advancedLogic: advancedLogic, currencyManager, + getSubgraphClient: mockedGetSubgraphClient, }); + expect(mockedGetSubgraphClient).not.toHaveBeenCalled(); expect(await paymentDetector.getBalance(requestWithWrongVersion)).toMatchObject({ balance: null, error: { code: 0, message: 'Near payment detection not implemented for version 3.14' }, @@ -186,7 +198,9 @@ describe('Near payments detection', () => { network: 'aurora', advancedLogic: advancedLogic, currencyManager, + getSubgraphClient: mockedGetSubgraphClient, }); + expect(mockedGetSubgraphClient).not.toHaveBeenCalled(); expect(await paymentDetector.getBalance(requestWithWrongNetwork)).toMatchObject({ balance: null, error: { diff --git a/packages/payment-detection/test/near/near-native.test.ts b/packages/payment-detection/test/near/near-native.test.ts index 962d116239..1060a87db0 100644 --- a/packages/payment-detection/test/near/near-native.test.ts +++ b/packages/payment-detection/test/near/near-native.test.ts @@ -1,6 +1,11 @@ -import { ExtensionTypes, PaymentTypes, RequestLogicTypes } from '@requestnetwork/types'; +import { + CurrencyTypes, + ExtensionTypes, + PaymentTypes, + RequestLogicTypes, +} from '@requestnetwork/types'; import { CurrencyManager } from '@requestnetwork/currency'; -import { PaymentNetworkFactory } from '../../src'; +import { PaymentNetworkFactory, TheGraphClient } from '../../src'; import PaymentReferenceCalculator from '../../src/payment-reference-calculator'; import { NearInfoRetriever, NearNativeTokenPaymentDetector } from '../../src/near'; import { deepCopy } from 'ethers/lib/utils'; @@ -32,7 +37,28 @@ const request: any = { const paymentNetworkFactory = new PaymentNetworkFactory(advancedLogic, currencyManager); +const graphPaymentEvent = { + amount: '1000000000000000000000000', + block: 47891257, + to: paymentAddress, + timestamp: 1631788427, + receiptId: 'FYVnCvJFoNtK7LE2uAdTFfReFMGiCUHMczLsvEni1Cpf', +}; + +const client = { + GetNearPayments: jest.fn().mockImplementation(() => ({ + payments: [graphPaymentEvent], + })), +} as any as TheGraphClient; +const mockedGetSubgraphClient = jest.fn().mockImplementation(() => client); + +const infoRetriever = new NearInfoRetriever(client); + describe('Near payments detection', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('NearInfoRetriever can retrieve a NEAR payment', async () => { const paymentReference = PaymentReferenceCalculator.calculate( request.requestId, @@ -40,21 +66,22 @@ describe('Near payments detection', () => { paymentAddress, ); - const infoRetriever = new NearInfoRetriever( + const events = await infoRetriever.getTransferEvents({ paymentReference, - 'gus.near', - 'requestnetwork.near', - PaymentTypes.EVENTS_NAMES.PAYMENT, - 'aurora', - ); - const events = await infoRetriever.getTransferEvents(); - expect(events).toHaveLength(1); + toAddress: 'gus.near', + contractAddress: 'requestnetwork.near', + eventName: PaymentTypes.EVENTS_NAMES.PAYMENT, + paymentChain: 'aurora', + }); + expect(events.paymentEvents).toHaveLength(1); - expect(events[0].amount).toBe('1000000000000000000000000'); - expect(events[0].timestamp).toBe(1631788427); - expect(events[0].parameters?.receiptId).toBe('FYVnCvJFoNtK7LE2uAdTFfReFMGiCUHMczLsvEni1Cpf'); - expect(events[0].parameters?.txHash).toBeUndefined(); - expect(events[0].parameters?.block).toBe(47891257); + expect(events.paymentEvents[0].amount).toBe('1000000000000000000000000'); + expect(events.paymentEvents[0].timestamp).toBe(1631788427); + expect(events.paymentEvents[0].parameters?.receiptId).toBe( + 'FYVnCvJFoNtK7LE2uAdTFfReFMGiCUHMczLsvEni1Cpf', + ); + expect(events.paymentEvents[0].parameters?.txHash).toBeUndefined(); + expect(events.paymentEvents[0].parameters?.block).toBe(47891257); }); it('PaymentNetworkFactory can get the detector (testnet)', async () => { @@ -77,9 +104,11 @@ describe('Near payments detection', () => { network: 'aurora', advancedLogic: advancedLogic, currencyManager: CurrencyManager.getDefault(), + getSubgraphClient: mockedGetSubgraphClient, }); const balance = await paymentDetector.getBalance(request); + expect(mockedGetSubgraphClient).toHaveBeenCalled(); expect(balance.events).toHaveLength(1); expect(balance.balance).toBe('1000000000000000000000000'); }); @@ -100,7 +129,9 @@ describe('Near payments detection', () => { network: 'aurora', advancedLogic: advancedLogic, currencyManager: CurrencyManager.getDefault(), + getSubgraphClient: mockedGetSubgraphClient, }); + expect(mockedGetSubgraphClient).not.toHaveBeenCalled(); expect(await paymentDetector.getBalance(requestWithWrongVersion)).toMatchObject({ balance: null, error: { code: 0, message: 'Near payment detection not implemented for version 3.14' }, @@ -118,7 +149,9 @@ describe('Near payments detection', () => { network: 'aurora', advancedLogic: advancedLogic, currencyManager: CurrencyManager.getDefault(), + getSubgraphClient: mockedGetSubgraphClient, }); + expect(mockedGetSubgraphClient).not.toHaveBeenCalled(); expect(await paymentDetector.getBalance(requestWithWrongNetwork)).toMatchObject({ balance: null, error: { diff --git a/packages/payment-detection/test/payment-network-factory.test.ts b/packages/payment-detection/test/payment-network-factory.test.ts index 3a7063afe4..acd60a1da4 100644 --- a/packages/payment-detection/test/payment-network-factory.test.ts +++ b/packages/payment-detection/test/payment-network-factory.test.ts @@ -1,4 +1,4 @@ -import { AdvancedLogicTypes, ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; +import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; import { CurrencyManager } from '@requestnetwork/currency'; import { BtcMainnetAddressBasedDetector } from '../src/btc'; import { @@ -6,16 +6,14 @@ import { EthInputDataPaymentDetector, PaymentNetworkFactory, } from '../src'; -import { mockAdvancedLogicBase } from './utils'; - -const mockAdvancedLogic: AdvancedLogicTypes.IAdvancedLogic = { - ...mockAdvancedLogicBase, - extensions: {} as AdvancedLogicTypes.IAdvancedLogicExtensions, -}; +import { AdvancedLogic } from '@requestnetwork/advanced-logic'; +import { ERC20FeeProxyPaymentDetector } from '../src/erc20/fee-proxy-contract'; const currencyManager = CurrencyManager.getDefault(); +const advancedLogic = new AdvancedLogic(currencyManager); + const paymentNetworkFactory = new PaymentNetworkFactory( - mockAdvancedLogic, + advancedLogic, CurrencyManager.getDefault(), ); // Most of the tests are done as integration tests in ../index.test.ts @@ -40,6 +38,16 @@ describe('api/payment-network/payment-network-factory', () => { ).toBeInstanceOf(DeclarativePaymentDetector); }); + it('can createPaymentNetwork with a NEAR network for en extension supporting both EVM and NEAR', async () => { + const pnInterpretor = paymentNetworkFactory.createPaymentNetwork( + ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT, + RequestLogicTypes.CURRENCY.ERC20, + 'aurora-testnet', + 'NEAR-0.1.0', + ); + expect(pnInterpretor).toBeInstanceOf(ERC20FeeProxyPaymentDetector); + }); + it('cannot createPaymentNetwork with extension id not handled', async () => { expect(() => { paymentNetworkFactory.createPaymentNetwork( @@ -145,7 +153,7 @@ describe('api/payment-network/payment-network-factory', () => { }, }, }; - const paymentNetworkFactory = new PaymentNetworkFactory(mockAdvancedLogic, currencyManager, { + const paymentNetworkFactory = new PaymentNetworkFactory(advancedLogic, currencyManager, { explorerApiKeys: { mainnet: 'abcd' }, }); const pn = paymentNetworkFactory.getPaymentNetworkFromRequest(request); diff --git a/packages/payment-detection/test/utils.ts b/packages/payment-detection/test/utils.ts index f9b18c1c7f..be56104e76 100644 --- a/packages/payment-detection/test/utils.ts +++ b/packages/payment-detection/test/utils.ts @@ -4,5 +4,6 @@ export const mockAdvancedLogicBase: AdvancedLogicTypes.IAdvancedLogic = { applyActionToExtensions: jest.fn(), getNativeTokenExtensionForNetwork: jest.fn(), getAnyToNativeTokenExtensionForNetwork: jest.fn(), + getFeeProxyContractErc20ForNetwork: jest.fn(), extensions: {} as AdvancedLogicTypes.IAdvancedLogicExtensions, }; diff --git a/packages/payment-processor/src/payment/index.ts b/packages/payment-processor/src/payment/index.ts index 143c80ce32..4236b795a4 100644 --- a/packages/payment-processor/src/payment/index.ts +++ b/packages/payment-processor/src/payment/index.ts @@ -15,8 +15,8 @@ import { RequestLogicTypes } from '@requestnetwork/types'; import { payAnyToErc20ProxyRequest } from './any-to-erc20-proxy'; import { payAnyToEthProxyRequest } from './any-to-eth-proxy'; import { WalletConnection } from 'near-api-js'; -import { isNearNetwork, isNearAccountSolvent } from './utils-near'; -import { ICurrencyManager } from '@requestnetwork/currency'; +import { isNearAccountSolvent } from './utils-near'; +import { ICurrencyManager, NearChains } from '@requestnetwork/currency'; import { encodeRequestErc20Approval } from './encoder-approval'; import { encodeRequestPayment } from './encoder-payment'; import { IPreparedTransaction } from './prepared-transaction'; @@ -242,7 +242,7 @@ export async function isSolvent( }, ): Promise { // Near case - if (isNearNetwork(currency.network) && providerOptions?.nearWalletConnection) { + if (NearChains.isChainSupported(currency.network) && providerOptions?.nearWalletConnection) { return isNearAccountSolvent(amount, providerOptions.nearWalletConnection); } // Main case (web3) @@ -330,7 +330,7 @@ export function _getPaymentUrl(request: ClientTypes.IRequestData, amount?: BigNu // FIXME: should also compare the signer.chainId with the request.currencyInfo.network... const throwIfNotWeb3 = (request: ClientTypes.IRequestData) => { // FIXME: there is a near web3Provider equivalent: https://github.com/aurora-is-near/near-web3-provider - if (request.currencyInfo?.network && isNearNetwork(request.currencyInfo.network)) { + if (request.currencyInfo?.network && NearChains.isChainSupported(request.currencyInfo.network)) { throw new UnsupportedPaymentChain(request.currencyInfo.network); } }; diff --git a/packages/payment-processor/src/payment/near-conversion.ts b/packages/payment-processor/src/payment/near-conversion.ts index 6917fabd18..198a908978 100644 --- a/packages/payment-processor/src/payment/near-conversion.ts +++ b/packages/payment-processor/src/payment/near-conversion.ts @@ -9,11 +9,7 @@ import { getAmountToPay, getPaymentExtensionVersion, } from './utils'; -import { - INearTransactionCallback, - isNearNetwork, - processNearPaymentWithConversion, -} from './utils-near'; +import { INearTransactionCallback, processNearPaymentWithConversion } from './utils-near'; import { IConversionPaymentSettings } from '.'; import { CurrencyManager, NearChains, UnsupportedCurrencyError } from '@requestnetwork/currency'; @@ -45,7 +41,7 @@ export async function payNearConversionRequest( throw new Error('Cannot pay without a paymentReference'); } - if (!network || !isNearNetwork(network)) { + if (!network || !NearChains.isChainSupported(network)) { throw new Error('Should be a Near network'); } NearChains.assertChainSupported(network); diff --git a/packages/payment-processor/src/payment/near-input-data.ts b/packages/payment-processor/src/payment/near-input-data.ts index 7783aaec6a..bbad8237b3 100644 --- a/packages/payment-processor/src/payment/near-input-data.ts +++ b/packages/payment-processor/src/payment/near-input-data.ts @@ -9,7 +9,7 @@ import { getRequestPaymentValues, validateRequest, } from './utils'; -import { INearTransactionCallback, isNearNetwork, processNearPayment } from './utils-near'; +import { INearTransactionCallback, processNearPayment } from './utils-near'; import { NearChains } from '@requestnetwork/currency'; /** @@ -24,7 +24,7 @@ export async function payNearInputDataRequest( amount?: BigNumberish, callback?: INearTransactionCallback, ): Promise { - if (!request.currencyInfo.network || !isNearNetwork(request.currencyInfo.network)) { + if (!request.currencyInfo.network || !NearChains.isChainSupported(request.currencyInfo.network)) { throw new Error('request.currencyInfo should be a Near network'); } diff --git a/packages/payment-processor/src/payment/utils-near.ts b/packages/payment-processor/src/payment/utils-near.ts index 1c8bd4b9d2..3d8c9ca5d9 100644 --- a/packages/payment-processor/src/payment/utils-near.ts +++ b/packages/payment-processor/src/payment/utils-near.ts @@ -4,7 +4,6 @@ import { NearConversionNativeTokenPaymentDetector, NearNativeTokenPaymentDetector, } from '@requestnetwork/payment-detection'; -import { NearChains } from '@requestnetwork/currency'; import { CurrencyTypes } from '@requestnetwork/types'; /** @@ -26,15 +25,6 @@ export const isValidNearAddress = async (nearNetwork: Near, address: string): Pr } }; -export const isNearNetwork = (network?: string): boolean => { - try { - NearChains.assertChainSupported(network); - return true; - } catch { - return false; - } -}; - export const isNearAccountSolvent = ( amount: BigNumberish, nearWalletConnection: WalletConnection, diff --git a/packages/request-logic/test/index.test.ts b/packages/request-logic/test/index.test.ts index 39ebb24ec8..8f67b1b24d 100644 --- a/packages/request-logic/test/index.test.ts +++ b/packages/request-logic/test/index.test.ts @@ -84,12 +84,10 @@ describe('index', () => { it('cannot createRequest if apply fails in the advanced request logic', async () => { const fakeAdvancedLogic: AdvancedLogicTypes.IAdvancedLogic = { - getAnyToNativeTokenExtensionForNetwork: jest.fn(), - getNativeTokenExtensionForNetwork: jest.fn(), + ...fakeAdvancedLogicBase, applyActionToExtensions: (): RequestLogicTypes.IExtensionStates => { throw new Error('Expected throw'); }, - extensions: {} as AdvancedLogicTypes.IAdvancedLogicExtensions, }; const requestLogic = new RequestLogic( diff --git a/packages/smart-contracts/scripts-create2/tenderly.ts b/packages/smart-contracts/scripts-create2/tenderly.ts index f34a14eabd..b4e247e452 100644 --- a/packages/smart-contracts/scripts-create2/tenderly.ts +++ b/packages/smart-contracts/scripts-create2/tenderly.ts @@ -55,6 +55,11 @@ export const tenderlyImportAll = async (hre: HardhatRuntimeEnvironmentExtended): const deployments = artifact.getAllAddressesFromAllNetworks(); for (const deployment of deployments) { const { networkName, address, version } = deployment; + try { + EvmChains.assertChainSupported(networkName); + } catch { + continue; + } if (!supportedTenderlyChains.includes(networkName)) continue; const chainId = EvmChains.getChainId(networkName); const contract: TenderlyContract = { diff --git a/packages/smart-contracts/src/lib/ContractArtifact.ts b/packages/smart-contracts/src/lib/ContractArtifact.ts index 5413510acb..f64412dda7 100644 --- a/packages/smart-contracts/src/lib/ContractArtifact.ts +++ b/packages/smart-contracts/src/lib/ContractArtifact.ts @@ -13,9 +13,7 @@ export type ArtifactNetworkInfo = { }; /** Deployment information and ABI per network */ -export type ArtifactDeploymentInfo< - TNetwork extends CurrencyTypes.EvmChainName = CurrencyTypes.EvmChainName, -> = { +export type ArtifactDeploymentInfo = { abi: JsonFragment[]; deployment: Partial>; }; @@ -23,7 +21,7 @@ export type ArtifactDeploymentInfo< /** Deployment information and ABI per version and network */ export type ArtifactInfo< TVersion extends string = string, - TNetwork extends CurrencyTypes.EvmChainName = CurrencyTypes.EvmChainName, + TNetwork extends CurrencyTypes.VMChainName = CurrencyTypes.VMChainName, > = Record>; export type DeploymentInformation = { @@ -80,7 +78,7 @@ export class ContractArtifact { * @param networkName the name of the network where the contract is deployed * @returns the address of the deployed contract */ - getAddress(networkName: CurrencyTypes.EvmChainName, version = this.lastVersion): string { + getAddress(networkName: CurrencyTypes.VMChainName, version = this.lastVersion): string { return this.getDeploymentInformation(networkName, version).address; } @@ -90,7 +88,7 @@ export class ContractArtifact { * @returns the addresses of the deployed contract and the associated version. */ getAllAddresses( - networkName: CurrencyTypes.EvmChainName, + networkName: CurrencyTypes.VMChainName, ): { version: string; address: string | undefined }[] { const entries = Object.entries(this.info); return entries.map(([version, { deployment }]) => ({ @@ -106,11 +104,11 @@ export class ContractArtifact { getAllAddressesFromAllNetworks(): { version: string; address: string; - networkName: CurrencyTypes.EvmChainName; + networkName: CurrencyTypes.VMChainName; }[] { const deployments = []; for (const version in this.info) { - let networkName: CurrencyTypes.EvmChainName; + let networkName: CurrencyTypes.VMChainName; for (networkName in this.info[version].deployment) { const address = this.info[version].deployment[networkName]?.address; if (!address) continue; @@ -131,7 +129,7 @@ export class ContractArtifact { * @returns the number of the block where the contract was deployed */ getCreationBlockNumber( - networkName: CurrencyTypes.EvmChainName, + networkName: CurrencyTypes.VMChainName, version = this.lastVersion, ): number { return this.getDeploymentInformation(networkName, version).creationBlockNumber; @@ -144,7 +142,7 @@ export class ContractArtifact { * @returns The address and the number of the creation block */ getDeploymentInformation( - networkName: CurrencyTypes.EvmChainName, + networkName: CurrencyTypes.VMChainName, version = this.lastVersion, ): DeploymentInformation { const versionInfo = this.info[version]; @@ -166,7 +164,7 @@ export class ContractArtifact { * @returns The address and the number of the creation block, or null if not found */ getOptionalDeploymentInformation( - networkName: CurrencyTypes.EvmChainName, + networkName: CurrencyTypes.VMChainName, version = this.lastVersion, ): DeploymentInformation | null { return this.info[version]?.deployment[networkName] || null; diff --git a/packages/smart-contracts/src/lib/artifacts/ERC20FeeProxy/index.ts b/packages/smart-contracts/src/lib/artifacts/ERC20FeeProxy/index.ts index 8ad42c2ccf..0c36174a87 100644 --- a/packages/smart-contracts/src/lib/artifacts/ERC20FeeProxy/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/ERC20FeeProxy/index.ts @@ -137,6 +137,15 @@ export const erc20FeeProxyArtifact = new ContractArtifact( }, }, }, + near: { + abi: [], + deployment: { + 'aurora-testnet': { + address: 'pay.reqnetwork.testnet', + creationBlockNumber: 120566834, + }, + }, + }, // Additional deployments of same versions, not worth upgrading the version number but worth using within next versions /* '0.2.0-next': { diff --git a/packages/types/src/advanced-logic-types.ts b/packages/types/src/advanced-logic-types.ts index 4f63f378b9..7861176483 100644 --- a/packages/types/src/advanced-logic-types.ts +++ b/packages/types/src/advanced-logic-types.ts @@ -38,6 +38,9 @@ export interface IAdvancedLogic { getAnyToNativeTokenExtensionForNetwork: ( network: ChainName, ) => Extension.IExtension | undefined; + getFeeProxyContractErc20ForNetwork: ( + network?: ChainName, + ) => Extension.PnFeeReferenceBased.IFeeReferenceBased | undefined; extensions: IAdvancedLogicExtensions; } diff --git a/packages/types/src/currency-types.ts b/packages/types/src/currency-types.ts index 94843e4917..4a8e048400 100644 --- a/packages/types/src/currency-types.ts +++ b/packages/types/src/currency-types.ts @@ -31,11 +31,13 @@ export type BtcChainName = 'mainnet' | 'testnet'; /** * List of supported NEAR chains + * FIXME: get rid of the wrong 'aurora' alias */ -export type NearChainName = - | 'aurora' - | 'aurora-testnet' - // | 'near' // TODO: add support for near - | 'near-testnet'; +export type NearChainName = 'aurora' | 'aurora-testnet' | 'near' | 'near-testnet'; export type ChainName = EvmChainName | BtcChainName | NearChainName; + +/** + * Virtual machin chains, where payment proxy contracts can be deployed + */ +export type VMChainName = EvmChainName | NearChainName; diff --git a/packages/types/src/payment-types.ts b/packages/types/src/payment-types.ts index d44c31b996..fb7ad00ff1 100644 --- a/packages/types/src/payment-types.ts +++ b/packages/types/src/payment-types.ts @@ -6,7 +6,9 @@ import { ICreationParameters as ICreationParametersAnyToAny } from './extensions import { EvmChainName } from './currency-types'; /** Interface for payment network extensions state and interpretation */ -export interface IPaymentNetwork { +export interface IPaymentNetwork< + TEventParameters extends GenericEventParameters = GenericEventParameters, +> { paymentNetworkId: ExtensionTypes.PAYMENT_NETWORK_ID; extension: ExtensionTypes.IExtension; createExtensionsDataForCreation: (paymentNetworkCreationParameters: any) => Promise;