From c4c22765df68a6438e4a0e9bc3d6255e844da791 Mon Sep 17 00:00:00 2001 From: Darko Kolev Date: Thu, 10 Mar 2022 12:59:21 +0100 Subject: [PATCH] feat: escrow detector class (#773) --- .../test/scheduled/erc20-fee-proxy.test.ts | 62 +--- .../test/scheduled/escrow-detector.test.ts | 46 +++ packages/integration-test/test/utils.ts | 42 +++ .../src/any/any-to-erc20-proxy.ts | 13 +- .../src/any/any-to-eth-proxy.ts | 62 ++-- .../src/btc/address-based.ts | 7 +- packages/payment-detection/src/declarative.ts | 6 +- .../src/erc20/address-based.ts | 7 +- .../src/erc20/escrow-info-retriever.ts | 158 ++++++---- .../src/erc20/fee-proxy-contract.ts | 55 ++-- .../src/erc20/proxy-contract.ts | 52 ++-- .../src/erc20/thegraph-info-retriever.ts | 23 +- .../src/erc777/superfluid-detector.ts | 13 +- .../src/eth/fee-proxy-detector.ts | 50 ++-- .../payment-detection/src/eth/input-data.ts | 66 ++-- .../src/eth/proxy-info-retriever.ts | 3 +- .../payment-detection/src/near-detector.ts | 12 +- .../src/payment-detector-base.ts | 17 +- .../src/reference-based-detector.ts | 17 +- .../thegraph/queries/GetEscrowEvents.graphql | 13 + .../queries/GetPaymentsAndEscrowState.graphql | 32 ++ packages/payment-detection/src/types.ts | 7 + packages/payment-detection/src/utils.ts | 6 +- .../any/any-to-erc20-proxy-contract.test.ts | 106 +++---- .../test/erc20/escrow-info-retriever.test.ts | 22 +- .../test/erc20/escrow-proxy-contract.test.ts | 283 ++++++++++++++++++ .../escrow-thegraph-info-retriever.test.ts | 74 +++++ .../test/erc20/fee-proxy-contract.test.ts | 110 +++---- .../erc20/thegraph-info-retriever.test.ts | 6 +- .../src/payment/erc20-escrow-payment.ts | 4 + packages/types/src/payment-types.ts | 84 +++++- 31 files changed, 1080 insertions(+), 378 deletions(-) create mode 100644 packages/integration-test/test/scheduled/escrow-detector.test.ts create mode 100644 packages/integration-test/test/utils.ts create mode 100644 packages/payment-detection/src/thegraph/queries/GetEscrowEvents.graphql create mode 100644 packages/payment-detection/src/thegraph/queries/GetPaymentsAndEscrowState.graphql create mode 100644 packages/payment-detection/test/erc20/escrow-proxy-contract.test.ts create mode 100644 packages/payment-detection/test/erc20/escrow-thegraph-info-retriever.test.ts diff --git a/packages/integration-test/test/scheduled/erc20-fee-proxy.test.ts b/packages/integration-test/test/scheduled/erc20-fee-proxy.test.ts index 6e1a4fdb6c..470713306f 100644 --- a/packages/integration-test/test/scheduled/erc20-fee-proxy.test.ts +++ b/packages/integration-test/test/scheduled/erc20-fee-proxy.test.ts @@ -1,10 +1,5 @@ import { Erc20PaymentNetwork } from '@requestnetwork/payment-detection'; -import { - ExtensionTypes, - IdentityTypes, - PaymentTypes, - RequestLogicTypes, -} from '@requestnetwork/types'; +import { PaymentTypes, RequestLogicTypes } from '@requestnetwork/types'; import { CurrencyManager } from '@requestnetwork/currency'; import { mockAdvancedLogic } from './mocks'; @@ -17,57 +12,24 @@ import { privateErc20Address, requestNetwork, } from './fixtures'; - -const createMockRequest = ({ - network, - tokenAddress, - paymentAddress, - salt, - requestId, -}: Record< - 'network' | 'tokenAddress' | 'paymentAddress' | 'salt' | 'requestId', - string ->): RequestLogicTypes.IRequest => ({ - creator: { type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, value: '0x2' }, - currency: { - network, - type: RequestLogicTypes.CURRENCY.ERC20, - value: tokenAddress, - }, - events: [], - expectedAmount: '0', - extensions: { - [ExtensionTypes.ID.PAYMENT_NETWORK_ERC20_FEE_PROXY_CONTRACT]: { - events: [], - id: ExtensionTypes.ID.PAYMENT_NETWORK_ERC20_FEE_PROXY_CONTRACT, - type: ExtensionTypes.TYPE.PAYMENT_NETWORK, - values: { - paymentAddress, - salt, - }, - version: '0.1.0', - }, - }, - extensionsData: [], - requestId, - state: RequestLogicTypes.STATE.CREATED, - timestamp: 0, - version: '0.2', -}); +import { createMockErc20FeeRequest } from '../utils'; const erc20FeeProxy = new Erc20PaymentNetwork.ERC20FeeProxyPaymentDetector({ + // FIXME: the mocked advanced logic is address based advancedLogic: mockAdvancedLogic, currencyManager: CurrencyManager.getDefault(), }); describe('ERC20 Fee Proxy detection test-suite', () => { it('can getBalance on a mainnet request', async () => { - const mockRequest = createMockRequest({ + const mockRequest = createMockErc20FeeRequest({ network: 'mainnet', requestId: '016d4cf8006982f7d91a437f8c72700aa62767de00a605133ee5f84ad8d224ba04', paymentAddress: '0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93', salt: '8097784e131ee627', tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI + feeAddress: '0x35d0e078755cd84d3e0656caab417dee1d7939c7', + feeAmount: '10', }); const balance = await erc20FeeProxy.getBalance(mockRequest); @@ -82,12 +44,14 @@ describe('ERC20 Fee Proxy detection test-suite', () => { }); it('can getBalance on a rinkeby request', async () => { - const mockRequest = createMockRequest({ + const mockRequest = createMockErc20FeeRequest({ network: 'rinkeby', requestId: '0188791633ff0ec72a7dbdefb886d2db6cccfa98287320839c2f173c7a4e3ce7e1', paymentAddress: '0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93', salt: '0ee84db293a752c6', tokenAddress: '0xFab46E002BbF0b4509813474841E0716E6730136', // FAU + feeAddress: '0x35d0e078755cd84d3e0656caab417dee1d7939c7', + feeAmount: '1000000000000000', }); const balance = await erc20FeeProxy.getBalance(mockRequest); @@ -96,18 +60,20 @@ describe('ERC20 Fee Proxy detection test-suite', () => { expect(balance.events).toHaveLength(1); expect(balance.events[0].name).toBe('payment'); const params = balance.events[0].parameters as PaymentTypes.IERC20FeePaymentEventParameters; - expect(params).toBe('0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93'); + expect(params?.to).toBe('0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93'); expect(balance.events[0].amount).toBe('1000000000000000000000'); expect(balance.events[0].timestamp).toBe(1599013969); }); it('can getBalance on a matic request, with TheGraph', async () => { - const mockRequest = createMockRequest({ + const mockRequest = createMockErc20FeeRequest({ network: 'matic', requestId: '014bcd076791fb915af457df1d3f26c81ff66f7e278e4a18f0e48a1705572a6306', paymentAddress: '0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93', salt: '8c5ea6f8b4a14fe0', tokenAddress: '0x282d8efce846a88b159800bd4130ad77443fa1a1', // FAU + feeAddress: '0x35d0e078755cd84d3e0656caab417dee1d7939c7', + feeAmount: '1000000000000000', }); const balance = await erc20FeeProxy.getBalance(mockRequest); @@ -116,7 +82,7 @@ describe('ERC20 Fee Proxy detection test-suite', () => { expect(balance.events).toHaveLength(1); expect(balance.events[0].name).toBe('payment'); const params = balance.events[0].parameters as PaymentTypes.IERC20FeePaymentEventParameters; - expect(params).toBe('0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93'); + expect(params.to).toBe('0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93'); expect(balance.events[0].amount).toBe('1000000000000000000'); expect(balance.events[0].timestamp).toBe(1621953168); }, 15000); diff --git a/packages/integration-test/test/scheduled/escrow-detector.test.ts b/packages/integration-test/test/scheduled/escrow-detector.test.ts new file mode 100644 index 0000000000..50e34dfce2 --- /dev/null +++ b/packages/integration-test/test/scheduled/escrow-detector.test.ts @@ -0,0 +1,46 @@ +import { Erc20PaymentNetwork } from '../../../payment-detection/dist'; +import { CurrencyManager } from '@requestnetwork/currency'; +import { createMockErc20FeeRequest } from '../utils'; +import { mockAdvancedLogic } from './mocks'; + +const feeProxyDetector = new Erc20PaymentNetwork.ERC20FeeProxyPaymentDetector({ + advancedLogic: mockAdvancedLogic, + currencyManager: CurrencyManager.getDefault(), +}); + +describe('ERC20 with Escrow detection test-suite', () => { + it('can getBalance on a matic request, with TheGraph', async () => { + const mockRequest = createMockErc20FeeRequest({ + network: 'matic', + requestId: '014bcd076791fb915af457df1d3f26c81ff66f7e278e4a18f0e48a1705572a6306', + paymentAddress: '0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93', + salt: '8c5ea6f8b4a14fe0', + tokenAddress: '0x282d8efce846a88b159800bd4130ad77443fa1a1', // FAU + feeAddress: '0x35d0e078755cd84d3e0656caab417dee1d7939c7', + feeAmount: '1000000000000000', + }); + + const balance = await feeProxyDetector.getBalance(mockRequest); + + expect(balance.balance).toBe('1000000000000000000'); + }, 15000); + + it('can getBalance on a rinkeby request', async () => { + const mockRequest = createMockErc20FeeRequest({ + network: 'rinkeby', + requestId: '0188791633ff0ec72a7dbdefb886d2db6cccfa98287320839c2f173c7a4e3ce7e1', + paymentAddress: '0x4E64C2d06d19D13061e62E291b2C4e9fe5679b93', + salt: '0ee84db293a752c6', + tokenAddress: '0xFab46E002BbF0b4509813474841E0716E6730136', // FAU + feeAddress: '0x35d0e078755cd84d3e0656caab417dee1d7939c7', + feeAmount: '1000000000000000', + }); + + const balance = await feeProxyDetector.getBalance(mockRequest); + + // Sanity check + expect(balance.balance).toBe('1000000000000000000'); + const paymentEvents = balance.events; + expect(paymentEvents).toHaveLength(3); + }); +}); diff --git a/packages/integration-test/test/utils.ts b/packages/integration-test/test/utils.ts new file mode 100644 index 0000000000..2077c09264 --- /dev/null +++ b/packages/integration-test/test/utils.ts @@ -0,0 +1,42 @@ +import { ExtensionTypes, IdentityTypes, RequestLogicTypes } from '@requestnetwork/types'; + +export const createMockErc20FeeRequest = ({ + network, + tokenAddress, + paymentAddress, + salt, + requestId, + feeAddress, + feeAmount, +}: Record< + 'network' | 'tokenAddress' | 'paymentAddress' | 'salt' | 'requestId' | 'feeAddress' | 'feeAmount', + string +>): RequestLogicTypes.IRequest => ({ + creator: { type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, value: '0x2' }, + currency: { + network, + type: RequestLogicTypes.CURRENCY.ERC20, + value: tokenAddress, + }, + events: [], + expectedAmount: '0', + extensions: { + [ExtensionTypes.ID.PAYMENT_NETWORK_ERC20_FEE_PROXY_CONTRACT]: { + events: [], + id: ExtensionTypes.ID.PAYMENT_NETWORK_ERC20_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + paymentAddress, + salt, + feeAddress, + feeAmount, + }, + version: '0.1.0', + }, + }, + extensionsData: [], + requestId, + state: RequestLogicTypes.STATE.CREATED, + timestamp: 0, + version: '0.2.0', +}); 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 3af3eb2910..f028c5494a 100644 --- a/packages/payment-detection/src/any/any-to-erc20-proxy.ts +++ b/packages/payment-detection/src/any/any-to-erc20-proxy.ts @@ -86,9 +86,11 @@ export class AnyToERC20PaymentDetector extends ERC20FeeProxyPaymentDetectorBase< requestCurrency: RequestLogicTypes.ICurrency, paymentChain: string, paymentNetwork: ExtensionTypes.IState, - ): Promise[]> { + ): Promise> { if (!address) { - return []; + return { + paymentEvents: [], + }; } const { acceptedTokens, maxRateTimespan = 0 } = paymentNetwork.values; @@ -125,9 +127,10 @@ export class AnyToERC20PaymentDetector extends ERC20FeeProxyPaymentDetectorBase< maxRateTimespan, ); - return infoRetriever.getTransferEvents() as Promise< - PaymentTypes.IPaymentNetworkEvent[] - >; + const paymentEvents = (await infoRetriever.getTransferEvents()) as PaymentTypes.IPaymentNetworkEvent[]; + return { + paymentEvents, + }; } protected getPaymentChain(request: RequestLogicTypes.IRequest): string { 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 796ea43038..8882e65bc4 100644 --- a/packages/payment-detection/src/any/any-to-eth-proxy.ts +++ b/packages/payment-detection/src/any/any-to-eth-proxy.ts @@ -69,46 +69,52 @@ export class AnyToEthFeeProxyPaymentDetector extends AnyToAnyDetector< requestCurrency: RequestLogicTypes.ICurrency, paymentChain: string, paymentNetwork: ExtensionTypes.IState, - ): Promise[]> { + ): Promise> { if (!address) { - return []; + return { + paymentEvents: [], + }; } + const contractInfo = AnyToEthFeeProxyPaymentDetector.getDeploymentInformation( paymentChain, paymentNetwork.version, ); + const abi = SmartContracts.ethConversionArtifact.getContractAbi(contractInfo.contractVersion); const currency = this.currencyManager.fromStorageCurrency(requestCurrency); if (!currency) { throw new UnsupportedCurrencyError(requestCurrency.value); + } else { + const proxyInfoRetriever = this.useTheGraph(paymentChain) + ? new TheGraphConversionRetriever( + currency, + paymentReference, + contractInfo.address, + address, + eventName, + paymentChain, + undefined, + paymentNetwork.values?.maxRateTimespan, + ) + : new AnyToEthInfoRetriever( + currency, + paymentReference, + contractInfo.address, + contractInfo.creationBlockNumber, + abi, + address, + eventName, + paymentChain, + undefined, + paymentNetwork.values?.maxRateTimespan, + ); + const paymentEvents = await proxyInfoRetriever.getTransferEvents(); + return { + paymentEvents, + }; } - - const proxyInfoRetriever = this.useTheGraph(paymentChain) - ? new TheGraphConversionRetriever( - currency, - paymentReference, - contractInfo.address, - address, - eventName, - paymentChain, - undefined, - paymentNetwork.values?.maxRateTimespan, - ) - : new AnyToEthInfoRetriever( - currency, - paymentReference, - contractInfo.address, - contractInfo.creationBlockNumber, - abi, - address, - eventName, - paymentChain, - undefined, - paymentNetwork.values?.maxRateTimespan, - ); - - return await proxyInfoRetriever.getTransferEvents(); } /** diff --git a/packages/payment-detection/src/btc/address-based.ts b/packages/payment-detection/src/btc/address-based.ts index b928218e73..df339c974d 100644 --- a/packages/payment-detection/src/btc/address-based.ts +++ b/packages/payment-detection/src/btc/address-based.ts @@ -74,7 +74,7 @@ export abstract class BtcAddressBasedDetector extends PaymentDetectorBase< */ protected async getEvents( request: RequestLogicTypes.IRequest, - ): Promise[]> { + ): Promise> { const { paymentAddress, refundAddress } = this.getPaymentExtension(request).values; this.checkRequiredParameter(paymentAddress, 'paymentAddress'); @@ -93,6 +93,9 @@ export abstract class BtcAddressBasedDetector extends PaymentDetectorBase< ) : { events: [] }, ]); - return [...payments.events, ...refunds.events]; + const paymentEvents = [...payments.events, ...refunds.events]; + return { + paymentEvents, + }; } } diff --git a/packages/payment-detection/src/declarative.ts b/packages/payment-detection/src/declarative.ts index da38e45d00..3286d70a63 100644 --- a/packages/payment-detection/src/declarative.ts +++ b/packages/payment-detection/src/declarative.ts @@ -197,7 +197,9 @@ export class DeclarativePaymentDetector extends DeclarativePaymentDetectorBase< protected async getEvents( request: RequestLogicTypes.IRequest, - ): Promise[]> { - return this.getDeclarativeEvents(request); + ): Promise> { + return { + paymentEvents: this.getDeclarativeEvents(request), + }; } } diff --git a/packages/payment-detection/src/erc20/address-based.ts b/packages/payment-detection/src/erc20/address-based.ts index 9a23bbedc0..5bd11e632e 100644 --- a/packages/payment-detection/src/erc20/address-based.ts +++ b/packages/payment-detection/src/erc20/address-based.ts @@ -78,7 +78,7 @@ export class ERC20AddressBasedPaymentDetector extends PaymentDetectorBase< */ protected async getEvents( request: RequestLogicTypes.IRequest, - ): Promise[]> { + ): Promise> { if (!request.currency.network) { request.currency.network = 'mainnet'; } @@ -110,7 +110,10 @@ export class ERC20AddressBasedPaymentDetector extends PaymentDetectorBase< request.currency.value, ); - return [...paymentEvents, ...refundEvents]; + const allPaymentEvents = [...paymentEvents, ...refundEvents]; + return { + paymentEvents: allPaymentEvents, + }; } /** diff --git a/packages/payment-detection/src/erc20/escrow-info-retriever.ts b/packages/payment-detection/src/erc20/escrow-info-retriever.ts index f11a282a68..573ae54557 100644 --- a/packages/payment-detection/src/erc20/escrow-info-retriever.ts +++ b/packages/payment-detection/src/erc20/escrow-info-retriever.ts @@ -1,6 +1,8 @@ import { PaymentTypes } from '@requestnetwork/types'; + +import { BigNumber, ethers } from 'ethers'; import { IEventRetriever } from '../types'; -import { ethers } from 'ethers'; + import { getDefaultProvider } from '../provider'; import { parseLogArgs } from '../utils'; @@ -9,6 +11,7 @@ const erc20EscrowContractAbiFragment = [ 'event RequestFrozen(bytes indexed paymentReference)', 'event InitiatedEmergencyClaim(bytes indexed paymentReference)', 'event RevertedEmergencyClaim(bytes indexed paymentReference)', + 'event TransferWithReferenceAndFee(address tokenAddress, address to,uint256 amount,bytes indexed paymentReference,uint256 feeAmount,address feeAddress)', ]; /** Escrow contract event arguments. */ @@ -16,10 +19,19 @@ type EscrowArgs = { paymentReference: string; }; +type TransferWithReferenceAndFeeArgs = EscrowArgs & { + tokenAddress: string; + to: string; + amount: BigNumber; + feeAmount: BigNumber; + feeAddress: string; +}; + /** * Retrieves a list of payment events from a escrow contract. + * */ -export default class EscrowERC20InfoRetriever +export class EscrowERC20InfoRetriever implements IEventRetriever< PaymentTypes.IPaymentNetworkBaseEvent, @@ -32,13 +44,19 @@ export default class EscrowERC20InfoRetriever * @param paymentReference The reference to identify the payment. * @param escrowContractAddress The address of the escrow contract. * @param escrowCreationBlockNumber The block that created the escrow contract. + * @param tokenContractAddress The address of the ERC20 contract + * @param toAddress Address of the balance we want to check + * @param eventName Indicate if it is an address for payment or escrow * @param network The Ethereum network to use. */ constructor( private paymentReference: string, private escrowContractAddress: string, private escrowCreationBlockNumber: number, + private tokenContractAddress: string, + private toAddress: string, private network: string, + private eventName?: PaymentTypes.ESCROW_EVENTS_NAMES, ) { // Creates a local or default provider. this.provider = getDefaultProvider(this.network); @@ -50,62 +68,79 @@ export default class EscrowERC20InfoRetriever this.provider, ); } - /** * Retrieves events for the current contract, address and network. */ + public async getAllContractEvents(): Promise< + PaymentTypes.IPaymentNetworkEscrowEvent< + PaymentTypes.GenericEventParameters, + PaymentTypes.ESCROW_EVENTS_NAMES + >[] + > { + const freezeEvents = await this.getContractEventsForEventName( + PaymentTypes.ESCROW_EVENTS_NAMES.FREEZE_ESCROW, + ); + const initEmergencyEvents = await this.getContractEventsForEventName( + PaymentTypes.ESCROW_EVENTS_NAMES.INITIATED_EMERGENCY_CLAIM, + ); + const revertEmergencyEvents = await this.getContractEventsForEventName( + PaymentTypes.ESCROW_EVENTS_NAMES.REVERTED_EMERGENCY_CLAIM, + ); + + return [...freezeEvents, ...initEmergencyEvents, ...revertEmergencyEvents]; + } + public async getContractEvents(): Promise< - PaymentTypes.IPaymentNetworkBaseEvent[] + PaymentTypes.IPaymentNetworkEscrowEvent< + PaymentTypes.GenericEventParameters, + PaymentTypes.ESCROW_EVENTS_NAMES + >[] + > { + if (!this.eventName) { + throw new Error('Missing event name in EscrowInfoRetriever for getContractEvents()'); + } + return this.getContractEventsForEventName(this.eventName); + } + + /** + * Retrieves events for the current contract, address and network. + */ + public async getContractEventsForEventName( + eventName: PaymentTypes.ESCROW_EVENTS_NAMES, + ): Promise< + PaymentTypes.IPaymentNetworkEscrowEvent< + PaymentTypes.GenericEventParameters, + PaymentTypes.ESCROW_EVENTS_NAMES + >[] > { - // Create a filter to find all the RequestFrozen logs with the payment reference - const freezeFilter = this.contractEscrow.filters.RequestFrozen( - '0x' + this.paymentReference, - ) as ethers.providers.Filter; - freezeFilter.fromBlock = this.escrowCreationBlockNumber; - freezeFilter.toBlock = 'latest'; - - // Create a filter to find all the Init Emergency logs with the payment reference. - const initEmergencyFilter = this.contractEscrow.filters.InitiatedEmergencyClaim( - '0x' + this.paymentReference, - ) as ethers.providers.Filter; - freezeFilter.fromBlock = this.escrowCreationBlockNumber; - freezeFilter.toBlock = 'latest'; - - // Create a filter to find all the Fee Transfer logs with the payment reference. - const revertEmergencyFilter = this.contractEscrow.filters.RevertedEmergencyClaim( - '0x' + this.paymentReference, - ) as ethers.providers.Filter; - freezeFilter.fromBlock = this.escrowCreationBlockNumber; - freezeFilter.toBlock = 'latest'; - - // Get the RequestFrozen event logs. - const freezeLog = await this.provider.getLogs(freezeFilter); - - // Get the InitiateEmergencyClaim event logs. - const initEmergencyLog = await this.provider.getLogs(initEmergencyFilter); - - // Get the RequestFrozen event logs. - const revertEmergencyLog = await this.provider.getLogs(revertEmergencyFilter); - - interface EthersLogWithEventName extends ethers.providers.Log { - eventName: PaymentTypes.ESCROW_EVENTS_NAMES; + const filter: ethers.providers.Filter | undefined = + eventName === PaymentTypes.ESCROW_EVENTS_NAMES.FREEZE_ESCROW + ? // Create a filter to find all the RequestFrozen logs with the payment reference + this.contractEscrow.filters.RequestFrozen('0x' + this.paymentReference) + : eventName === PaymentTypes.ESCROW_EVENTS_NAMES.INITIATED_EMERGENCY_CLAIM + ? this.contractEscrow.filters.InitiatedEmergencyClaim('0x' + this.paymentReference) + : eventName === PaymentTypes.ESCROW_EVENTS_NAMES.REVERTED_EMERGENCY_CLAIM + ? this.contractEscrow.filters.RevertedEmergencyClaim('0x' + this.paymentReference) + : eventName === PaymentTypes.ESCROW_EVENTS_NAMES.PAID_ESCROW + ? this.contractEscrow.filters.TransferWithReferenceAndFee( + null, + // TODO: be sure null is a good idea + null, + null, + '0x' + this.paymentReference, + null, + null, + ) + : undefined; + + if (!filter) { + throw new Error('Wrong eventName for Escrow event retriever'); } - // Merge events if multiple logs. - const logs: EthersLogWithEventName[] = [ - ...freezeLog.map((i) => ({ - ...i, - eventName: PaymentTypes.ESCROW_EVENTS_NAMES.FROZEN_PAYMENT, - })), - ...initEmergencyLog.map((i) => ({ - ...i, - eventName: PaymentTypes.ESCROW_EVENTS_NAMES.INITIATED_EMERGENCY_CLAIM, - })), - ...revertEmergencyLog.map((i) => ({ - ...i, - eventName: PaymentTypes.ESCROW_EVENTS_NAMES.REVERTED_EMERGENCY_CLAIM, - })), - ]; + filter.fromBlock = this.escrowCreationBlockNumber; + filter.toBlock = 'latest'; + + const logs = await this.provider.getLogs(filter); // Parses, filters and creates the events from the logs with the payment reference. const eventPromises = logs @@ -114,16 +149,33 @@ export default class EscrowERC20InfoRetriever const parsedLog = this.contractEscrow.interface.parseLog(log); return { ...log, - parsedLog: parseLogArgs(parsedLog), + parsedLog: parseLogArgs(parsedLog), }; }) + // Keeps only the log with the right token and the right destination address + .filter(({ parsedLog }) => { + if (parsedLog.tokenAddress) { + return ( + parsedLog.tokenAddress.toLowerCase() === this.tokenContractAddress.toLowerCase() && + parsedLog.to.toLowerCase() === this.toAddress.toLowerCase() + ); + } else { + return true; + } + }) + // Creates the escrow events. - .map(async ({ parsedLog, blockNumber, transactionHash, eventName }) => ({ + .map(async ({ parsedLog, blockNumber, transactionHash }) => ({ + // TODO fix me + amount: parsedLog.amount?.toString() || undefined, name: eventName, parameters: { block: blockNumber, paymentReference: parsedLog.paymentReference, + feeAddress: parsedLog.feeAddress || undefined, + feeAmount: parsedLog.feeAmount?.toString() || undefined, + to: this.toAddress || undefined, txHash: transactionHash, }, timestamp: (await this.provider.getBlock(blockNumber || 0)).timestamp, diff --git a/packages/payment-detection/src/erc20/fee-proxy-contract.ts b/packages/payment-detection/src/erc20/fee-proxy-contract.ts index 471f19756a..22ddb300ac 100644 --- a/packages/payment-detection/src/erc20/fee-proxy-contract.ts +++ b/packages/payment-detection/src/erc20/fee-proxy-contract.ts @@ -84,16 +84,18 @@ export class ERC20FeeProxyPaymentDetector extends ERC20FeeProxyPaymentDetectorBa /** * Extracts the payment events of a request */ - protected extractEvents( + protected async extractEvents( eventName: PaymentTypes.EVENTS_NAMES, address: string | undefined, paymentReference: string, requestCurrency: RequestLogicTypes.ICurrency, paymentChain: string, paymentNetwork: ExtensionTypes.IState, - ): Promise[]> { + ): Promise> { if (!address) { - return Promise.resolve([]); + return Promise.resolve({ + paymentEvents: [], + }); } const { @@ -101,28 +103,31 @@ export class ERC20FeeProxyPaymentDetector extends ERC20FeeProxyPaymentDetectorBa creationBlockNumber: proxyCreationBlockNumber, } = ERC20FeeProxyPaymentDetector.getDeploymentInformation(paymentChain, paymentNetwork.version); - const infoRetriever = networkSupportsTheGraph(paymentChain) - ? new TheGraphInfoRetriever( - paymentReference, - proxyContractAddress, - requestCurrency.value, - address, - eventName, - paymentChain, - ) - : new ProxyInfoRetriever( - paymentReference, - proxyContractAddress, - proxyCreationBlockNumber, - requestCurrency.value, - address, - eventName, - paymentChain, - ); - - return infoRetriever.getTransferEvents() as Promise< - PaymentTypes.IPaymentNetworkEvent[] - >; + if (networkSupportsTheGraph(paymentChain)) { + const graphInfoRetriever = new TheGraphInfoRetriever( + paymentReference, + proxyContractAddress, + requestCurrency.value, + address, + eventName, + paymentChain, + ); + return graphInfoRetriever.getTransferEvents(); + } else { + const proxyInfoRetriever = new ProxyInfoRetriever( + paymentReference, + proxyContractAddress, + proxyCreationBlockNumber, + requestCurrency.value, + address, + eventName, + paymentChain, + ); + const paymentEvents = await proxyInfoRetriever.getTransferEvents(); + return { + paymentEvents, + }; + } } /* diff --git a/packages/payment-detection/src/erc20/proxy-contract.ts b/packages/payment-detection/src/erc20/proxy-contract.ts index ecae0c1c07..015b3dcbbb 100644 --- a/packages/payment-detection/src/erc20/proxy-contract.ts +++ b/packages/payment-detection/src/erc20/proxy-contract.ts @@ -4,6 +4,7 @@ import { PaymentTypes, RequestLogicTypes, } from '@requestnetwork/types'; + import { erc20ProxyArtifact } from '@requestnetwork/smart-contracts'; import ProxyInfoRetriever from './proxy-info-retriever'; import TheGraphInfoRetriever from './thegraph-info-retriever'; @@ -49,9 +50,11 @@ export class ERC20ProxyPaymentDetector extends ReferenceBasedDetector< requestCurrency: RequestLogicTypes.ICurrency, paymentChain: string, paymentNetwork: ExtensionTypes.IState, - ): Promise[]> { + ): Promise> { if (!address) { - return []; + return { + paymentEvents: [], + }; } const { @@ -59,26 +62,31 @@ export class ERC20ProxyPaymentDetector extends ReferenceBasedDetector< creationBlockNumber: proxyCreationBlockNumber, } = ERC20ProxyPaymentDetector.getDeploymentInformation(paymentChain, paymentNetwork.version); - const infoRetriever = networkSupportsTheGraph(paymentChain) - ? new TheGraphInfoRetriever( - paymentReference, - proxyContractAddress, - requestCurrency.value, - address, - eventName, - paymentChain, - ) - : new ProxyInfoRetriever( - paymentReference, - proxyContractAddress, - proxyCreationBlockNumber, - requestCurrency.value, - address, - eventName, - paymentChain, - ); - - return infoRetriever.getTransferEvents(); + if (networkSupportsTheGraph(paymentChain)) { + const graphInfoRetriever = new TheGraphInfoRetriever( + paymentReference, + proxyContractAddress, + requestCurrency.value, + address, + eventName, + paymentChain, + ); + return graphInfoRetriever.getTransferEvents(); + } else { + const proxyInfoRetriever = new ProxyInfoRetriever( + paymentReference, + proxyContractAddress, + proxyCreationBlockNumber, + requestCurrency.value, + address, + eventName, + paymentChain, + ); + const paymentEvents = await proxyInfoRetriever.getTransferEvents(); + return { + paymentEvents, + }; + } } /* diff --git a/packages/payment-detection/src/erc20/thegraph-info-retriever.ts b/packages/payment-detection/src/erc20/thegraph-info-retriever.ts index 6bc89aaadf..1f75089eef 100644 --- a/packages/payment-detection/src/erc20/thegraph-info-retriever.ts +++ b/packages/payment-detection/src/erc20/thegraph-info-retriever.ts @@ -43,10 +43,12 @@ export class TheGraphInfoRetriever { }; } - public async getTransferEvents(): Promise { + public async getTransferEvents(): Promise< + PaymentTypes.AllNetworkEvents + > { const variables = this.getGraphVariables(); - const payments = await this.client.GetPayments(variables); - return payments.payments.map((p) => ({ + const paymentsAndEscrows = await this.client.GetPaymentsAndEscrowState(variables); + const paymentEvents = paymentsAndEscrows.payments.map((p) => ({ amount: p.amount, name: this.eventName, parameters: { @@ -58,6 +60,21 @@ export class TheGraphInfoRetriever { }, timestamp: p.timestamp, })); + const escrowEvents = paymentsAndEscrows.escrowEvents.map((p) => ({ + name: PaymentTypes.EVENTS_NAMES.ESCROW, + parameters: { + to: this.toAddress, + from: p.from, + txHash: p.txHash, + block: p.block, + eventName: p.eventName, + }, + timestamp: p.timestamp, + })); + return { + paymentEvents: paymentEvents, + escrowEvents: escrowEvents, + }; } } diff --git a/packages/payment-detection/src/erc777/superfluid-detector.ts b/packages/payment-detection/src/erc777/superfluid-detector.ts index 66f3f2370f..6effdb7afa 100644 --- a/packages/payment-detection/src/erc777/superfluid-detector.ts +++ b/packages/payment-detection/src/erc777/superfluid-detector.ts @@ -40,11 +40,12 @@ export class SuperFluidPaymentDetector extends ReferenceBasedDetector< paymentReference: string, requestCurrency: RequestLogicTypes.ICurrency, paymentChain: string, - ): Promise[]> { + ): Promise> { if (!address) { - return []; + return { + paymentEvents: [], + }; } - const infoRetriever = new SuperFluidInfoRetriever( paymentReference, requestCurrency.value, @@ -52,7 +53,9 @@ export class SuperFluidPaymentDetector extends ReferenceBasedDetector< eventName, paymentChain, ); - - return infoRetriever.getTransferEvents(); + const paymentEvents = await infoRetriever.getTransferEvents(); + return { + paymentEvents, + }; } } diff --git a/packages/payment-detection/src/eth/fee-proxy-detector.ts b/packages/payment-detection/src/eth/fee-proxy-detector.ts index 1c879eb922..3286d4e4ef 100644 --- a/packages/payment-detection/src/eth/fee-proxy-detector.ts +++ b/packages/payment-detection/src/eth/fee-proxy-detector.ts @@ -60,35 +60,41 @@ export class EthFeeProxyPaymentDetector extends FeeReferenceBasedDetector< > ? ExtensionTypes.IState : never, - ): Promise { + ): Promise> { if (!address) { - return []; + return { + paymentEvents: [], + }; } const proxyContractArtifact = EthFeeProxyPaymentDetector.getDeploymentInformation( paymentChain, paymentNetwork.version, ); - - const proxyInfoRetriever = networkSupportsTheGraph(paymentChain) - ? new TheGraphInfoRetriever( - paymentReference, - proxyContractArtifact.address, - null, - address, - eventName, - paymentChain, - ) - : new EthProxyInfoRetriever( - paymentReference, - proxyContractArtifact.address, - proxyContractArtifact.creationBlockNumber, - address, - eventName, - paymentChain, - ); - - return proxyInfoRetriever.getTransferEvents(); + if (networkSupportsTheGraph(paymentChain)) { + const graphInfoRetriever = new TheGraphInfoRetriever( + paymentReference, + proxyContractArtifact.address, + null, + address, + eventName, + paymentChain, + ); + return graphInfoRetriever.getTransferEvents(); + } else { + const proxyInfoRetriever = new EthProxyInfoRetriever( + paymentReference, + proxyContractArtifact.address, + proxyContractArtifact.creationBlockNumber, + address, + eventName, + paymentChain, + ); + const paymentEvents = await proxyInfoRetriever.getTransferEvents(); + return { + paymentEvents, + }; + } } /* diff --git a/packages/payment-detection/src/eth/input-data.ts b/packages/payment-detection/src/eth/input-data.ts index 075bc47702..9e26da4100 100644 --- a/packages/payment-detection/src/eth/input-data.ts +++ b/packages/payment-detection/src/eth/input-data.ts @@ -67,9 +67,15 @@ export class EthInputDataPaymentDetector extends ReferenceBasedDetector< _requestCurrency: RequestLogicTypes.ICurrency, paymentChain: string, paymentNetwork: ExtensionTypes.IState, - ): Promise { + ): Promise< + PaymentTypes.AllNetworkEvents< + PaymentTypes.IETHPaymentEventParameters | PaymentTypes.IETHFeePaymentEventParameters + > + > { if (!address) { - return []; + return { + paymentEvents: [], + }; } const infoRetriever = new EthInputDataInfoRetriever( address, @@ -83,30 +89,42 @@ export class EthInputDataPaymentDetector extends ReferenceBasedDetector< paymentChain, paymentNetwork.version, ); - + let allEvents: PaymentTypes.AllNetworkEvents< + PaymentTypes.IETHPaymentEventParameters | PaymentTypes.IETHFeePaymentEventParameters + >; + let escrowEvents: PaymentTypes.EscrowNetworkEvent[] | undefined = []; if (proxyContractArtifact) { - const proxyInfoRetriever = networkSupportsTheGraph(paymentChain) - ? new TheGraphInfoRetriever( - paymentReference, - proxyContractArtifact.address, - null, - address, - eventName, - paymentChain, - ) - : new EthProxyInfoRetriever( - paymentReference, - proxyContractArtifact.address, - proxyContractArtifact.creationBlockNumber, - address, - eventName, - paymentChain, - ); - - const proxyEvents = await proxyInfoRetriever.getTransferEvents(); - events.push(...proxyEvents); + if (networkSupportsTheGraph(paymentChain)) { + const graphInfoRetriever = new TheGraphInfoRetriever( + paymentReference, + proxyContractArtifact.address, + null, + address, + eventName, + paymentChain, + ); + allEvents = await graphInfoRetriever.getTransferEvents(); + } else { + const ethInfoRetriever = new EthProxyInfoRetriever( + paymentReference, + proxyContractArtifact.address, + proxyContractArtifact.creationBlockNumber, + address, + eventName, + paymentChain, + ); + const paymentEvents = await ethInfoRetriever.getTransferEvents(); + allEvents = { + paymentEvents, + }; + } + events.push(...allEvents.paymentEvents); + escrowEvents = allEvents.escrowEvents; } - return events; + return { + paymentEvents: events, + escrowEvents: escrowEvents, + }; } /* diff --git a/packages/payment-detection/src/eth/proxy-info-retriever.ts b/packages/payment-detection/src/eth/proxy-info-retriever.ts index 1c89b0237d..f138e09f50 100644 --- a/packages/payment-detection/src/eth/proxy-info-retriever.ts +++ b/packages/payment-detection/src/eth/proxy-info-retriever.ts @@ -117,7 +117,6 @@ export class EthProxyInfoRetriever }, timestamp: (await this.provider.getBlock(blockNumber || 0)).timestamp, })); - - return Promise.all(eventPromises); + return await Promise.all(eventPromises); } } diff --git a/packages/payment-detection/src/near-detector.ts b/packages/payment-detection/src/near-detector.ts index 2fa8d91efd..2fb65268e7 100644 --- a/packages/payment-detection/src/near-detector.ts +++ b/packages/payment-detection/src/near-detector.ts @@ -82,9 +82,11 @@ export class NearNativeTokenPaymentDetector extends ReferenceBasedDetector< _requestCurrency: RequestLogicTypes.ICurrency, paymentChain: string, paymentNetwork: ExtensionTypes.IState, - ): Promise { + ): Promise> { if (!address) { - return []; + return { + paymentEvents: [], + }; } const infoRetriever = new NearInfoRetriever( paymentReference, @@ -94,8 +96,10 @@ export class NearNativeTokenPaymentDetector extends ReferenceBasedDetector< eventName, paymentChain, ); - const events = await infoRetriever.getTransferEvents(); - return events; + const paymentEvents = await infoRetriever.getTransferEvents(); + return { + paymentEvents, + }; } protected static getVersionOrThrow = (paymentNetworkVersion: string): string => { diff --git a/packages/payment-detection/src/payment-detector-base.ts b/packages/payment-detection/src/payment-detector-base.ts index 3e60dacdde..92580907c5 100644 --- a/packages/payment-detection/src/payment-detector-base.ts +++ b/packages/payment-detection/src/payment-detector-base.ts @@ -28,13 +28,16 @@ export abstract class PaymentDetectorBase< request: RequestLogicTypes.IRequest, ): Promise> { try { - const rawEvents = await this.getEvents(request); - const events = this.sortEvents(rawEvents); - const balance = this.computeBalance(events).toString(); + const allNetworkEvents = await this.getEvents(request); + const rawPaymentEvents = allNetworkEvents.paymentEvents; + const events = this.sortEvents(rawPaymentEvents); + const balance = this.computeBalance(events).toString(); + const escrowEvents = this.sortEscrowEvents(allNetworkEvents.escrowEvents || []); return { balance, events, + escrowEvents, }; } catch (error) { return getBalanceErrorObject(error); @@ -46,7 +49,7 @@ export abstract class PaymentDetectorBase< */ protected abstract getEvents( request: RequestLogicTypes.IRequest, - ): Promise[]>; + ): Promise>; protected getPaymentExtension( request: RequestLogicTypes.IRequest, @@ -79,6 +82,12 @@ export abstract class PaymentDetectorBase< return events.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); } + protected sortEscrowEvents( + events: PaymentTypes.EscrowNetworkEvent[], + ): PaymentTypes.EscrowNetworkEvent[] { + return events.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); + } + protected checkRequiredParameter(value: T | undefined, name: string): asserts value is T { if (!value) { throw new ExtensionMissingRequiredValue(this.paymentNetworkId, name); diff --git a/packages/payment-detection/src/reference-based-detector.ts b/packages/payment-detection/src/reference-based-detector.ts index 5fe780c3b7..7d88180e87 100644 --- a/packages/payment-detection/src/reference-based-detector.ts +++ b/packages/payment-detection/src/reference-based-detector.ts @@ -81,9 +81,9 @@ export abstract class ReferenceBasedDetector< protected async getEvents( request: RequestLogicTypes.IRequest, ): Promise< - PaymentTypes.IPaymentNetworkEvent< + PaymentTypes.AllNetworkEvents< TPaymentEventParameters | PaymentTypes.IDeclarativePaymentEventParameters - >[] + > > { const paymentExtension = this.getPaymentExtension(request); const paymentChain = this.getPaymentChain(request); @@ -100,7 +100,7 @@ export abstract class ReferenceBasedDetector< this.checkRequiredParameter(paymentExtension.values.salt, 'salt'); this.checkRequiredParameter(paymentExtension.values.paymentAddress, 'paymentAddress'); - const [paymentEvents, refundEvents] = await Promise.all([ + const [paymentAndEscrowEvents, refundAndEscrowEvents] = await Promise.all([ this.extractEvents( PaymentTypes.EVENTS_NAMES.PAYMENT, paymentExtension.values.paymentAddress, @@ -118,9 +118,16 @@ export abstract class ReferenceBasedDetector< paymentExtension, ), ]); + const paymentEvents = paymentAndEscrowEvents.paymentEvents; + const escrowEvents = paymentAndEscrowEvents.escrowEvents; + const refundEvents = refundAndEscrowEvents.paymentEvents; const declaredEvents = this.getDeclarativeEvents(request); - return [...declaredEvents, ...paymentEvents, ...refundEvents]; + const allPaymentEvents = [...declaredEvents, ...paymentEvents, ...refundEvents]; + return { + paymentEvents: allPaymentEvents, + escrowEvents: escrowEvents, + }; } /** @@ -143,7 +150,7 @@ export abstract class ReferenceBasedDetector< paymentNetwork: TExtension extends ExtensionTypes.IExtension ? ExtensionTypes.IState : never, - ): Promise[]>; + ): Promise>; /** * Get the network of the payment diff --git a/packages/payment-detection/src/thegraph/queries/GetEscrowEvents.graphql b/packages/payment-detection/src/thegraph/queries/GetEscrowEvents.graphql new file mode 100644 index 0000000000..e40041bd09 --- /dev/null +++ b/packages/payment-detection/src/thegraph/queries/GetEscrowEvents.graphql @@ -0,0 +1,13 @@ +query GetEscrowEvents($contractAddress: Bytes!, $reference: Bytes!) { + escrowEvents( + where: { contractAddress: $contractAddress, reference: $reference } + orderBy: timestamp + orderDirection: asc + ) { + block + txHash + eventName + from + timestamp + } +} diff --git a/packages/payment-detection/src/thegraph/queries/GetPaymentsAndEscrowState.graphql b/packages/payment-detection/src/thegraph/queries/GetPaymentsAndEscrowState.graphql new file mode 100644 index 0000000000..b4230e02b8 --- /dev/null +++ b/packages/payment-detection/src/thegraph/queries/GetPaymentsAndEscrowState.graphql @@ -0,0 +1,32 @@ +query GetPaymentsAndEscrowState( + $contractAddress: Bytes! + $reference: Bytes! + $tokenAddress: Bytes + $to: Bytes! +) { + payments( + where: { + contractAddress: $contractAddress + reference: $reference + tokenAddress: $tokenAddress + to: $to + } + orderBy: timestamp + orderDirection: asc + ) { + amount + block + txHash + feeAmount + feeAddress + from + timestamp + } + escrowEvents(where: { reference: $reference }, orderBy: timestamp, orderDirection: asc) { + txHash + eventName + from + timestamp + block + } +} diff --git a/packages/payment-detection/src/types.ts b/packages/payment-detection/src/types.ts index 946a482ee3..8afc67d6bf 100644 --- a/packages/payment-detection/src/types.ts +++ b/packages/payment-detection/src/types.ts @@ -8,6 +8,13 @@ export interface IPaymentRetriever< getTransferEvents(): Promise; } +export interface IGraphEventsRetriever< + TPaymentNetworkEvent extends PaymentTypes.IPaymentNetworkEvent, + TEventNames = PaymentTypes.EVENTS_NAMES +> { + getTransferEvents(): Promise>; +} + /** Generic info retriever interface without transfers */ export interface IEventRetriever< TPaymentNetworkEvent extends PaymentTypes.IPaymentNetworkBaseEvent, diff --git a/packages/payment-detection/src/utils.ts b/packages/payment-detection/src/utils.ts index cfb2b3fff6..324a9f9fea 100644 --- a/packages/payment-detection/src/utils.ts +++ b/packages/payment-detection/src/utils.ts @@ -1,7 +1,7 @@ import { CurrencyDefinition } from '@requestnetwork/currency'; import { RequestLogicTypes } from '@requestnetwork/types'; import { BigNumber, BigNumberish, Contract } from 'ethers'; -import { LogDescription } from 'ethers/lib/utils'; +import { keccak256, LogDescription } from 'ethers/lib/utils'; import { ContractArtifact, DeploymentInformation } from '@requestnetwork/smart-contracts'; import { NetworkNotSupported, VersionNotSupported } from './balance-error'; @@ -96,3 +96,7 @@ export const makeGetDeploymentInformation = < return { ...info, contractVersion }; }; }; + +export const hashReference = (paymentReference: string): string => { + return keccak256(`0x${paymentReference}`); +}; diff --git a/packages/payment-detection/test/any/any-to-erc20-proxy-contract.test.ts b/packages/payment-detection/test/any/any-to-erc20-proxy-contract.test.ts index 9d9459cec4..0c421d4bd0 100644 --- a/packages/payment-detection/test/any/any-to-erc20-proxy-contract.test.ts +++ b/packages/payment-detection/test/any/any-to-erc20-proxy-contract.test.ts @@ -202,11 +202,56 @@ describe('api/any/conversion-fee-proxy-contract', () => { const mockExtractEvents = (eventName: PaymentTypes.EVENTS_NAMES) => { if (eventName === PaymentTypes.EVENTS_NAMES.PAYMENT) { - return Promise.resolve([ + return Promise.resolve({ + paymentEvents: [ + // Wrong fee address + { + amount: '100', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, + parameters: { + block: 1, + feeAddress: 'fee address', + feeAmount: '5', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABC', + }, + timestamp: 10, + }, + // Correct fee address and a fee value + { + amount: '500', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, + parameters: { + block: 1, + feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', + feeAmount: '5', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABCD', + }, + timestamp: 11, + }, + // No fee + { + amount: '500', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, + parameters: { + block: 1, + feeAddress: '', + feeAmount: '0', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABCDE', + }, + timestamp: 12, + }, + ], + }); + } + return Promise.resolve({ + paymentEvents: [ // Wrong fee address { amount: '100', - name: PaymentTypes.EVENTS_NAMES.PAYMENT, + name: PaymentTypes.EVENTS_NAMES.REFUND, parameters: { block: 1, feeAddress: 'fee address', @@ -218,73 +263,32 @@ describe('api/any/conversion-fee-proxy-contract', () => { }, // Correct fee address and a fee value { - amount: '500', - name: PaymentTypes.EVENTS_NAMES.PAYMENT, + amount: '100', + name: PaymentTypes.EVENTS_NAMES.REFUND, parameters: { block: 1, feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', feeAmount: '5', - to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + to: '0x666666151EbEF6C7334FAD080c5704D77216b732', txHash: '0xABCD', }, timestamp: 11, }, // No fee { - amount: '500', - name: PaymentTypes.EVENTS_NAMES.PAYMENT, + amount: '100', + name: PaymentTypes.EVENTS_NAMES.REFUND, parameters: { block: 1, feeAddress: '', feeAmount: '0', - to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + to: '0x666666151EbEF6C7334FAD080c5704D77216b732', txHash: '0xABCDE', }, timestamp: 12, }, - ]); - } - return Promise.resolve([ - // Wrong fee address - { - amount: '100', - name: PaymentTypes.EVENTS_NAMES.REFUND, - parameters: { - block: 1, - feeAddress: 'fee address', - feeAmount: '5', - to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', - txHash: '0xABC', - }, - timestamp: 10, - }, - // Correct fee address and a fee value - { - amount: '100', - name: PaymentTypes.EVENTS_NAMES.REFUND, - parameters: { - block: 1, - feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', - feeAmount: '5', - to: '0x666666151EbEF6C7334FAD080c5704D77216b732', - txHash: '0xABCD', - }, - timestamp: 11, - }, - // No fee - { - amount: '100', - name: PaymentTypes.EVENTS_NAMES.REFUND, - parameters: { - block: 1, - feeAddress: '', - feeAmount: '0', - to: '0x666666151EbEF6C7334FAD080c5704D77216b732', - txHash: '0xABCDE', - }, - timestamp: 12, - }, - ]); + ], + }); }; jest .spyOn(anyToErc20Proxy as any, 'extractEvents') diff --git a/packages/payment-detection/test/erc20/escrow-info-retriever.test.ts b/packages/payment-detection/test/erc20/escrow-info-retriever.test.ts index ab91069d1c..ab2c853361 100644 --- a/packages/payment-detection/test/erc20/escrow-info-retriever.test.ts +++ b/packages/payment-detection/test/erc20/escrow-info-retriever.test.ts @@ -1,6 +1,6 @@ /* eslint-disable no-invalid-this */ /* eslint-disable no-magic-numbers */ -import EscrowERC20InfoRetriever from '../../src/erc20/escrow-info-retriever'; +import { EscrowERC20InfoRetriever } from '../../src/erc20/escrow-info-retriever'; import { ethers } from 'ethers'; import { PaymentTypes } from '@requestnetwork/types'; @@ -66,6 +66,8 @@ describe('api/erc20/escrow-info-retriever', () => { paymentReferenceMock, escrowContractAddress, 0, + '0x4c88a33fb62e2999fc365141f99d5f278eb68e8f68165be07c74839921cdb564', + '0xB9B7e0cb2EDF5Ea031C8B297A5A1Fa20379b6A0a', 'private', ); @@ -98,19 +100,21 @@ describe('api/erc20/escrow-info-retriever', () => { jest.resetAllMocks(); }); - it('can get the FROZEN_PAYMENT event of an address out of mocked logs', async () => { - const events = await infoRetriever.getContractEvents(); - expect(events).toHaveLength(3); + it('can get the FREEZE_ESCROW event of an address out of mocked logs', async () => { + const events = await infoRetriever.getContractEventsForEventName( + PaymentTypes.ESCROW_EVENTS_NAMES.FREEZE_ESCROW, + ); + expect(events).toHaveLength(1); expect(events).toEqual( expect.arrayContaining([ - expect.objectContaining({ name: PaymentTypes.ESCROW_EVENTS_NAMES.FROZEN_PAYMENT }), + expect.objectContaining({ name: PaymentTypes.ESCROW_EVENTS_NAMES.FREEZE_ESCROW }), ]), ); - expect(getBlockSpy).toHaveBeenCalledTimes(3); - expect(getLogsSpy).toHaveBeenCalledTimes(3); + expect(getBlockSpy).toHaveBeenCalledTimes(1); + expect(getLogsSpy).toHaveBeenCalledTimes(1); }); it('can get the INITIATED_EMERGENCY_CLAIM event of an address out of mocked logs', async () => { - const events = await infoRetriever.getContractEvents(); + const events = await infoRetriever.getAllContractEvents(); expect(events).toHaveLength(3); expect(events).toEqual( expect.arrayContaining([ @@ -123,7 +127,7 @@ describe('api/erc20/escrow-info-retriever', () => { expect(getLogsSpy).toHaveBeenCalledTimes(3); }); it('can get the REVERTED_EMERGENCY_CLAIM event of an address out of mocked logs', async () => { - const events = await infoRetriever.getContractEvents(); + const events = await infoRetriever.getAllContractEvents(); expect(events).toHaveLength(3); expect(events).toEqual( expect.arrayContaining([ diff --git a/packages/payment-detection/test/erc20/escrow-proxy-contract.test.ts b/packages/payment-detection/test/erc20/escrow-proxy-contract.test.ts new file mode 100644 index 0000000000..9298117001 --- /dev/null +++ b/packages/payment-detection/test/erc20/escrow-proxy-contract.test.ts @@ -0,0 +1,283 @@ +import { + AdvancedLogicTypes, + ExtensionTypes, + IdentityTypes, + PaymentTypes, + RequestLogicTypes, +} from '@requestnetwork/types'; +import { CurrencyManager } from '@requestnetwork/currency'; +import { Erc20PaymentNetwork } from '../../src'; + +let erc20FeeProxyDetector: Erc20PaymentNetwork.ERC20FeeProxyPaymentDetector; + +const createAddPaymentAddressAction = jest.fn(); +const createAddRefundAddressAction = jest.fn(); +const createCreationAction = jest.fn(); +const createAddFeeAction = jest.fn(); +const createAddPaymentInstructionAction = jest.fn(); +const createAddRefundInstructionAction = jest.fn(); + +const mockAdvancedLogic: AdvancedLogicTypes.IAdvancedLogic = { + applyActionToExtensions(): any { + return; + }, + extensions: { + feeProxyContractErc20: { + supportedNetworks: ['mainnet', 'private'], + createAddPaymentAddressAction, + createAddRefundAddressAction, + createCreationAction, + createAddFeeAction, + // inherited from declarative + createAddPaymentInstructionAction, + createAddRefundInstructionAction, + }, + }, +}; + +const currencyManager = CurrencyManager.getDefault(); + +/* eslint-disable @typescript-eslint/no-unused-expressions */ +describe('api/erc20/escrow-proxy-contract', () => { + beforeEach(() => { + erc20FeeProxyDetector = new Erc20PaymentNetwork.ERC20FeeProxyPaymentDetector({ + advancedLogic: mockAdvancedLogic, + currencyManager, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('can createExtensionsDataForCreation', async () => { + await erc20FeeProxyDetector.createExtensionsDataForCreation({ + paymentAddress: 'ethereum address', + salt: 'ea3bc7caf64110ca', + }); + + expect(createCreationAction).toHaveBeenCalledWith({ + feeAddress: undefined, + feeAmount: undefined, + paymentAddress: 'ethereum address', + refundAddress: undefined, + salt: 'ea3bc7caf64110ca', + }); + }); + + it('can createExtensionsDataForCreation with fee amount and address', async () => { + await erc20FeeProxyDetector.createExtensionsDataForCreation({ + feeAddress: 'fee address', + feeAmount: '2000', + paymentAddress: 'ethereum address', + salt: 'ea3bc7caf64110ca', + }); + + expect(createCreationAction).toHaveBeenCalledWith({ + feeAddress: 'fee address', + feeAmount: '2000', + paymentAddress: 'ethereum address', + refundAddress: undefined, + salt: 'ea3bc7caf64110ca', + }); + }); + + it('can createExtensionsDataForCreation without salt', async () => { + await erc20FeeProxyDetector.createExtensionsDataForCreation({ + paymentAddress: 'ethereum address', + }); + + // Can't check parameters since salt is generated in createExtensionsDataForCreation + expect(createCreationAction).toHaveBeenCalled(); + }); + + it('can createExtensionsDataForAddPaymentInformation', async () => { + erc20FeeProxyDetector.createExtensionsDataForAddPaymentInformation({ + paymentInfo: 'ethereum address', + }); + + expect(createAddPaymentInstructionAction).toHaveBeenCalledWith({ + paymentInfo: 'ethereum address', + }); + }); + + it('can createExtensionsDataForAddPaymentAddress', async () => { + erc20FeeProxyDetector.createExtensionsDataForAddPaymentAddress({ + paymentAddress: 'ethereum address', + }); + + expect(createAddPaymentAddressAction).toHaveBeenCalledWith({ + paymentAddress: 'ethereum address', + }); + }); + + it('can createExtensionsDataForAddRefundAddress', async () => { + erc20FeeProxyDetector.createExtensionsDataForAddRefundAddress({ + refundAddress: 'ethereum address', + }); + + expect(createAddRefundAddressAction).toHaveBeenCalledWith({ + refundAddress: 'ethereum address', + }); + }); + + it('can createExtensionsDataForAddRefundInformation', async () => { + erc20FeeProxyDetector.createExtensionsDataForAddRefundInformation({ + refundInfo: 'ethereum address', + }); + + expect(createAddRefundInstructionAction).toHaveBeenCalledWith({ + refundInfo: 'ethereum address', + }); + }); + + it('can createExtensionsDataForAddFeeInformation', async () => { + erc20FeeProxyDetector.createExtensionsDataForAddFeeInformation({ + feeAddress: 'ethereum address', + feeAmount: '2000', + }); + + expect(createAddFeeAction).toHaveBeenCalledWith({ + feeAddress: 'ethereum address', + feeAmount: '2000', + }); + }); + + it('should not throw when getBalance fail', async () => { + expect( + await erc20FeeProxyDetector.getBalance({ extensions: {} } as RequestLogicTypes.IRequest), + ).toEqual({ + balance: null, + error: { + code: PaymentTypes.BALANCE_ERROR_CODE.WRONG_EXTENSION, + message: 'The request does not have the extension: pn-erc20-fee-proxy-contract', + }, + events: [], + }); + }); + + it('can get the fees out of payment events', async () => { + const mockRequest: RequestLogicTypes.IRequest = { + creator: { type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, value: '0x2' }, + currency: { + network: 'private', + type: RequestLogicTypes.CURRENCY.ERC20, + value: '0x9FBDa871d559710256a2502A2517b794B482Db40', // local ERC20 token + }, + events: [], + expectedAmount: '1000', + extensions: { + [ExtensionTypes.ID.PAYMENT_NETWORK_ERC20_FEE_PROXY_CONTRACT]: { + events: [], + id: ExtensionTypes.ID.PAYMENT_NETWORK_ERC20_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', + feeAmount: '5', + paymentAddress: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + refundAddress: '0xrefundAddress', + salt: 'abcd', + }, + version: '0', + }, + }, + extensionsData: [], + requestId: '0x1', + state: RequestLogicTypes.STATE.CREATED, + timestamp: 0, + version: '0.2', + }; + + const mockExtractTransferEvents = (eventName: any) => { + if (eventName === 'refund') { + return Promise.resolve({ + paymentEvents: [ + // wrong fee address, but still counts as refund + { + amount: '10', + name: PaymentTypes.EVENTS_NAMES.REFUND, + parameters: { + block: 1, + feeAddress: 'fee address', + feeAmount: '0', + to: '0xrefundAddress', + }, + }, + // valid refund + { + amount: '10', + name: PaymentTypes.EVENTS_NAMES.REFUND, + parameters: { + block: 1, + feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', + feeAmount: '0', + to: '0xrefundAddress', + }, + }, + ], + }); + } + return Promise.resolve({ + paymentEvents: [ + // Wrong fee address + { + amount: '100', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, + parameters: { + block: 1, + feeAddress: 'fee address', + feeAmount: '5', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABC', + }, + timestamp: 10, + }, + // Correct fee address and a fee value + { + amount: '500', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, + parameters: { + block: 1, + feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', + feeAmount: '5', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABCD', + }, + timestamp: 11, + }, + // No fee + { + amount: '500', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, + parameters: { + block: 1, + feeAddress: '', + feeAmount: '0', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABCDE', + }, + timestamp: 12, + }, + ], + }); + }; + erc20FeeProxyDetector = new Erc20PaymentNetwork.ERC20FeeProxyPaymentDetector({ + advancedLogic: mockAdvancedLogic, + currencyManager, + }); + + jest + .spyOn(erc20FeeProxyDetector as any, 'extractEvents') + .mockImplementation(mockExtractTransferEvents); + + const balance = await erc20FeeProxyDetector.getBalance(mockRequest); + + expect(balance.error).toBeUndefined(); + // 500 + 500 + 100 (3 payments) - 10 - 10 (2 refunds) = 1100 - 20 = 1080 + expect(balance.balance).toBe('1080'); + expect( + mockRequest.extensions[ExtensionTypes.ID.PAYMENT_NETWORK_ERC20_FEE_PROXY_CONTRACT].values + .feeBalance.balance, + ).toBe('5'); + }); +}); diff --git a/packages/payment-detection/test/erc20/escrow-thegraph-info-retriever.test.ts b/packages/payment-detection/test/erc20/escrow-thegraph-info-retriever.test.ts new file mode 100644 index 0000000000..6c68f820a7 --- /dev/null +++ b/packages/payment-detection/test/erc20/escrow-thegraph-info-retriever.test.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +import { PaymentTypes } from '@requestnetwork/types'; +import TheGraphInfoRetriever from '../../src/erc20/thegraph-info-retriever'; + +describe('api/erc20/escrow-thegraph-info-retriever', () => { + describe('on rinkeby', () => { + const RINKEBY_ESCROW_CONTRACT = '0x8230e703b1c4467a4543422b2cc3284133b9ab5e'; + + it('should get escrow event list in correct order via subgraph', async () => { + const eventList = [ + { + eventName: 'paidEscrow', + from: '0x9dce3840976e4254aa073d5edcf59aa4007f50ac', + txHash: '0x93e59e2f43e2ea4e7c6f093f106e10577969122ad434821f960dd57a43e6bcde', + block: 9669873, + timestamp: 1637331138, + }, + { + eventName: 'paidIssuer', + from: '0x9dce3840976e4254aa073d5edcf59aa4007f50ac', + txHash: '0x360de11d0c69c178dc207c57d540fcb9d37b4dde6dcd3c2d7ea329ecdffa2d29', + block: 9669879, + timestamp: 1637331228, + }, + + { + eventName: 'paidEscrow', + from: '0xc24cd7f1085e0424d57531a466945b7530d510f0', + txHash: '0x94d993f967c01ff64ab6fe96e456c1e29ef38d81eb1fc84b0272ddc52723ba01', + block: 9769660, + timestamp: 1638828593, + }, + { + eventName: 'revertEmergencyClaim', + from: '0xc24cd7f1085e0424d57531a466945b7530d510f0', + txHash: '0xf36b97332e8d87da83dbf9caf0fb7ff0c52e9076a964f77155f62873cd19b027', + block: 9769706, + timestamp: 1638829283, + }, + ]; + const paymentReference = 'aaaa'; + + const graphRetriever = new TheGraphInfoRetriever( + paymentReference, + RINKEBY_ESCROW_CONTRACT, + '0xfab46e002bbf0b4509813474841e0716e6730136', + '0x8230e703b1c4467a4543422b2cc3284133b9ab5e', + PaymentTypes.EVENTS_NAMES.PAYMENT, + 'rinkeby', + ); + const allNetworkEvents = await graphRetriever.getTransferEvents(); + const escrowEvents = allNetworkEvents.escrowEvents || []; + expect(escrowEvents).toHaveLength(7); + expect(escrowEvents[0].name).toEqual('escrow'); + expect(escrowEvents[0].parameters?.block).toEqual(eventList[0].block); + expect(escrowEvents[0].parameters?.eventName).toEqual(eventList[0].eventName); + expect(escrowEvents[0].parameters?.from).toEqual(eventList[0].from); + expect(escrowEvents[0].timestamp).toEqual(eventList[0].timestamp); + expect(escrowEvents[0].parameters?.txHash).toEqual(eventList[0].txHash); + + expect(escrowEvents[1].parameters?.block).toEqual(eventList[1].block); + expect(escrowEvents[1].parameters?.eventName).toEqual(eventList[1].eventName); + expect(escrowEvents[1].parameters?.from).toEqual(eventList[1].from); + expect(escrowEvents[1].timestamp).toEqual(eventList[1].timestamp); + expect(escrowEvents[1].parameters?.txHash).toEqual(eventList[1].txHash); + + expect(escrowEvents[2].parameters?.block).toEqual(eventList[2].block); + expect(escrowEvents[2].parameters?.eventName).toEqual(eventList[2].eventName); + expect(escrowEvents[2].parameters?.from).toEqual(eventList[2].from); + expect(escrowEvents[2].timestamp).toEqual(eventList[2].timestamp); + expect(escrowEvents[2].parameters?.txHash).toEqual(eventList[2].txHash); + }); + }); +}); 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 4cf7c403bb..8679647c1a 100644 --- a/packages/payment-detection/test/erc20/fee-proxy-contract.test.ts +++ b/packages/payment-detection/test/erc20/fee-proxy-contract.test.ts @@ -190,72 +190,76 @@ describe('api/erc20/fee-proxy-contract', () => { const mockExtractTransferEvents = (eventName: any) => { if (eventName === 'refund') { - return Promise.resolve([ - // wrong fee address + return Promise.resolve({ + paymentEvents: [ + // wrong fee address + { + amount: '5', + name: PaymentTypes.EVENTS_NAMES.REFUND, + parameters: { + block: 1, + feeAddress: 'fee address', + feeAmount: '0', + to: '0xrefundAddress', + }, + }, + // valid refund + { + amount: '10', + name: PaymentTypes.EVENTS_NAMES.REFUND, + parameters: { + block: 1, + feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', + feeAmount: '2', + to: '0xrefundAddress', + }, + }, + ], + }); + } + return Promise.resolve({ + paymentEvents: [ + // Wrong fee address { - amount: '5', - name: PaymentTypes.EVENTS_NAMES.REFUND, + amount: '100', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, parameters: { block: 1, feeAddress: 'fee address', - feeAmount: '0', - to: '0xrefundAddress', + feeAmount: '5', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABC', }, + timestamp: 10, }, - // valid refund + // Correct fee address and a fee value { - amount: '10', - name: PaymentTypes.EVENTS_NAMES.REFUND, + amount: '500', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, parameters: { block: 1, feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', - feeAmount: '2', - to: '0xrefundAddress', + feeAmount: '5', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABCD', }, + timestamp: 11, }, - ]); - } - return Promise.resolve([ - // Wrong fee address - { - amount: '100', - name: PaymentTypes.EVENTS_NAMES.PAYMENT, - parameters: { - block: 1, - feeAddress: 'fee address', - feeAmount: '5', - to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', - txHash: '0xABC', - }, - timestamp: 10, - }, - // Correct fee address and a fee value - { - amount: '500', - name: PaymentTypes.EVENTS_NAMES.PAYMENT, - parameters: { - block: 1, - feeAddress: '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef', - feeAmount: '5', - to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', - txHash: '0xABCD', - }, - timestamp: 11, - }, - // No fee - { - amount: '400', - name: PaymentTypes.EVENTS_NAMES.PAYMENT, - parameters: { - block: 1, - feeAddress: '', - feeAmount: '0', - to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', - txHash: '0xABCDE', + // No fee + { + amount: '400', + name: PaymentTypes.EVENTS_NAMES.PAYMENT, + parameters: { + block: 1, + feeAddress: '', + feeAmount: '0', + to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732', + txHash: '0xABCDE', + }, + timestamp: 12, }, - timestamp: 12, - }, - ]); + ], + }); }; erc20FeeProxyContract = new ERC20FeeProxyPaymentDetector({ advancedLogic: mockAdvancedLogic, diff --git a/packages/payment-detection/test/erc20/thegraph-info-retriever.test.ts b/packages/payment-detection/test/erc20/thegraph-info-retriever.test.ts index 4a2a3d2b3f..b2bfde1bcb 100644 --- a/packages/payment-detection/test/erc20/thegraph-info-retriever.test.ts +++ b/packages/payment-detection/test/erc20/thegraph-info-retriever.test.ts @@ -37,7 +37,8 @@ describe('api/erc20/thegraph-info-retriever', () => { PaymentTypes.EVENTS_NAMES.PAYMENT, paymentData.network, ); - const transferEvents = await graphRetriever.getTransferEvents(); + const allNetworkEvents = await graphRetriever.getTransferEvents(); + const transferEvents = allNetworkEvents.paymentEvents; expect(transferEvents).toHaveLength(1); expect(transferEvents[0].amount).toEqual('30000000000000'); expect(transferEvents[0].name).toEqual('payment'); @@ -75,7 +76,8 @@ describe('api/erc20/thegraph-info-retriever', () => { PaymentTypes.EVENTS_NAMES.PAYMENT, paymentData.network, ); - const transferEvents = await graphRetriever.getTransferEvents(); + const allNetworkEvents = await graphRetriever.getTransferEvents(); + const transferEvents = allNetworkEvents.paymentEvents; expect(transferEvents).toHaveLength(1); expect(transferEvents[0].amount).toEqual(paymentData.amount); expect(transferEvents[0].parameters?.to).toEqual(paymentData.to); diff --git a/packages/payment-processor/src/payment/erc20-escrow-payment.ts b/packages/payment-processor/src/payment/erc20-escrow-payment.ts index d4e2b1e55b..aea56cda7f 100644 --- a/packages/payment-processor/src/payment/erc20-escrow-payment.ts +++ b/packages/payment-processor/src/payment/erc20-escrow-payment.ts @@ -43,6 +43,8 @@ export async function approveErc20ForEscrow( * Processes a transaction to payEscrow(). * @param request request to pay. * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum. + * @param amount optional, if you want to override the amount in the request. + * @param feeAmount optional, if you want to override the feeAmount in the request. * @param overrides optionally, override default transaction values, like gas. */ export async function payEscrow( @@ -50,6 +52,7 @@ export async function payEscrow( signerOrProvider: providers.Web3Provider | Signer = getProvider(), amount?: BigNumberish, feeAmount?: BigNumberish, + overrides?: ITransactionOverrides, ): Promise { const encodedTx = encodePayEscrow(request, signerOrProvider, amount, feeAmount); const contractAddress = erc20EscrowToPayArtifact.getAddress(request.currencyInfo.network!); @@ -59,6 +62,7 @@ export async function payEscrow( data: encodedTx, to: contractAddress, value: 0, + ...overrides, }); return tx; } diff --git a/packages/types/src/payment-types.ts b/packages/types/src/payment-types.ts index 5b3b66e867..23f0f8c482 100644 --- a/packages/types/src/payment-types.ts +++ b/packages/types/src/payment-types.ts @@ -65,6 +65,7 @@ export interface IBalanceWithEvents { balance: string | null; events: Array>; error?: IBalanceError; + escrowEvents?: Array; } /** Interface for error encounter when getting the balance */ @@ -77,12 +78,15 @@ export interface IBalanceError { export enum EVENTS_NAMES { PAYMENT = 'payment', REFUND = 'refund', + ESCROW = 'escrow', } export enum ESCROW_EVENTS_NAMES { - FROZEN_PAYMENT = 'frozenPayment', - INITIATED_EMERGENCY_CLAIM = 'initiatedEmergencyClaim', - REVERTED_EMERGENCY_CLAIM = 'revertedEmergencyClaim', + PAID_ESCROW = 'paidEscrow', + PAID_ISSUER = 'paidIssuer', + INITIATED_EMERGENCY_CLAIM = 'initializeEmergencyClaim', + REVERTED_EMERGENCY_CLAIM = 'revertEmergencyClaim', + FREEZE_ESCROW = 'freezeEscrow', } /** Balance error codes */ @@ -121,18 +125,34 @@ export type DeclarativePaymentNetworkEvent = IPaymentNetworkEvent; +/** Generic info retriever interface without transfers */ +export interface IPaymentNetworkBaseInfoRetriever< + TPaymentNetworkEvent extends IPaymentNetworkBaseEvent, + TEventNames = EVENTS_NAMES +> { + getAllContractEvents(): Promise; +} /** * ERC20 networks and events */ /** Parameters for events of ERC20 payments */ -export interface IERC20PaymentEventParameters { - from?: string; - to: string; +export interface GenericEventParameters { block?: number; txHash?: string; } +export interface EscrowEventParameters extends GenericEventParameters { + from?: string; + to?: string; +} + +/** Parameters for events of ERC20 payments */ +export interface IERC20PaymentEventParameters extends GenericEventParameters { + from?: string; + to: string; +} + /** Parameters for events of ERC20 payments with fees */ export interface IERC20FeePaymentEventParameters extends IERC20PaymentEventParameters { feeAddress?: string; @@ -146,6 +166,7 @@ export interface IERC20FeePaymentEventParameters extends IERC20PaymentEventParam export type ERC20PaymentNetworkEvent = IPaymentNetworkEvent< IERC20PaymentEventParameters | IERC20FeePaymentEventParameters >; + /** ERC20 BalanceWithEvents */ export type ERC20BalanceWithEvents = IBalanceWithEvents; @@ -209,3 +230,54 @@ export interface IBTCPaymentEventParameters { export type BTCPaymentNetworkEvent = IPaymentNetworkEvent; /** BTC BalanceWithEvents */ export type BTCBalanceWithEvents = IBalanceWithEvents; + +/** Parameters for escrow events from EscrowERC20 contract state changes */ +export interface IEscrowEventParameters { + block: number; + txHash: string; + from?: string; + to?: string; + eventName: string; +} +/** Escrow events that change the state of the Escrow */ +export type EscrowEvents = IEscrowEventParameters; + +export enum ESCROW_STATE { + PAID_ESCROW = 'paidEscrow', + IN_FROZEN = 'frozen', + IN_EMERGENCY = 'emergency', + PAID_ISSUER = 'paidIssuer', +} + +/** Parameters that describe the current escrow state */ +export interface IEscrowParameters { + creationBlock: number; + creationTimestamp: number; + escrowState: string; + tokenAddress: string; + amount: string; + from: string; + to: string; + feeAmount: string; + feeAddress: string; +} +/** Represents the current state of an escrow instance */ +export type EscrowData = IEscrowParameters; + +/** escrow payment network event */ +export interface IPaymentNetworkEscrowEvent + extends IPaymentNetworkBaseEvent { + parameters?: TEventParameters; +} + +export type EscrowNetworkEvent = IPaymentNetworkEscrowEvent; + +export type AllNetworkEvents = { + paymentEvents: IPaymentNetworkEvent[]; + escrowEvents?: EscrowNetworkEvent[]; +}; + +export type AllNetworkRetrieverEvents = { + paymentEvents: TPaymentNetworkEventType[]; + escrowEvents?: EscrowNetworkEvent[]; +};