Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add payment detection for any-to-eth
- Loading branch information
Showing
13 changed files
with
471 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { ExtensionTypes, PaymentTypes, RequestLogicTypes } from '@requestnetwork/types'; | ||
import Utils from '@requestnetwork/utils'; | ||
import FeeReferenceBasedDetector from './fee-reference-based-detector'; | ||
|
||
import { ICurrencyManager } from '@requestnetwork/currency'; | ||
|
||
/** | ||
* Abstract class to extend to get the payment balance of conversion requests | ||
*/ | ||
export default abstract class AnyToAnyDetector< | ||
TPaymentEventParameters | ||
> extends FeeReferenceBasedDetector<TPaymentEventParameters> { | ||
/** | ||
* @param extension The advanced logic payment network extension, with conversion | ||
* @param extensionType Example : ExtensionTypes.ID.ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY | ||
*/ | ||
public constructor( | ||
protected extension: ExtensionTypes.PnFeeReferenceBased.IFeeReferenceBased, | ||
protected extensionType: ExtensionTypes.ID, | ||
protected currencyManager: ICurrencyManager, | ||
) { | ||
super(extension, extensionType); | ||
} | ||
|
||
/** | ||
* Creates the extensions data for the creation of this extension. | ||
* Will set a salt if none is already given | ||
* | ||
* @param paymentNetworkCreationParameters Parameters to create the extension | ||
* @returns The extensionData object | ||
*/ | ||
public async createExtensionsDataForCreation( | ||
paymentNetworkCreationParameters: ExtensionTypes.PnAnyToAnyConversion.ICreationParameters, | ||
): Promise<ExtensionTypes.IAction> { | ||
// If no salt is given, generate one | ||
paymentNetworkCreationParameters.salt = | ||
paymentNetworkCreationParameters.salt || (await Utils.crypto.generate8randomBytes()); | ||
|
||
return this.extension.createCreationAction({ | ||
feeAddress: paymentNetworkCreationParameters.feeAddress, | ||
feeAmount: paymentNetworkCreationParameters.feeAmount, | ||
paymentAddress: paymentNetworkCreationParameters.paymentAddress, | ||
refundAddress: paymentNetworkCreationParameters.refundAddress, | ||
network: paymentNetworkCreationParameters.network, | ||
maxRateTimespan: paymentNetworkCreationParameters.maxRateTimespan, | ||
...paymentNetworkCreationParameters, | ||
}); | ||
} | ||
/** | ||
* Extracts payment events of an address matching an address and a payment reference | ||
* | ||
* @param address 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 | ||
* @param paymentNetwork the payment network | ||
* @returns The balance | ||
*/ | ||
protected abstract extractEvents( | ||
address: string, | ||
eventName: PaymentTypes.EVENTS_NAMES, | ||
requestCurrency: RequestLogicTypes.ICurrency, | ||
paymentReference: string, | ||
paymentNetwork: ExtensionTypes.IState<any>, | ||
): Promise<PaymentTypes.IPaymentNetworkEvent<TPaymentEventParameters>[]>; | ||
} |
127 changes: 127 additions & 0 deletions
127
packages/payment-detection/src/any/any-to-eth-proxy-detector.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import * as SmartContracts from '@requestnetwork/smart-contracts'; | ||
import { | ||
AdvancedLogicTypes, | ||
ExtensionTypes, | ||
PaymentTypes, | ||
RequestLogicTypes, | ||
} from '@requestnetwork/types'; | ||
|
||
import { ICurrencyManager } from '@requestnetwork/currency'; | ||
|
||
import ProxyInfoRetriever from './any-to-eth-proxy-info-retriever'; | ||
import AnyToAnyDetector from '../any-to-any-detector'; | ||
|
||
// interface of the object indexing the proxy contract version | ||
interface IProxyContractVersion { | ||
[version: string]: string; | ||
} | ||
|
||
const PROXY_CONTRACT_ADDRESS_MAP: IProxyContractVersion = { | ||
['0.1.0']: '0.1.0', | ||
}; | ||
|
||
/** | ||
* Handle payment networks with ETH input data extension | ||
*/ | ||
export default class ETHFeeProxyDetector extends AnyToAnyDetector<PaymentTypes.IETHPaymentEventParameters> { | ||
/** | ||
* @param extension The advanced logic payment network extensions | ||
*/ | ||
public constructor({ | ||
advancedLogic, | ||
currencyManager, | ||
}: { | ||
advancedLogic: AdvancedLogicTypes.IAdvancedLogic; | ||
currencyManager: ICurrencyManager; | ||
}) { | ||
super( | ||
advancedLogic.extensions.feeProxyContractEth, | ||
ExtensionTypes.ID.PAYMENT_NETWORK_ETH_FEE_PROXY_CONTRACT, | ||
currencyManager, | ||
); | ||
} | ||
|
||
/** | ||
* Extracts payment events of an address matching an address and a payment reference | ||
* | ||
* @param address 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 | ||
* @param paymentNetwork the payment network | ||
* @returns The balance | ||
*/ | ||
protected async extractEvents( | ||
address: string, | ||
eventName: PaymentTypes.EVENTS_NAMES, | ||
requestCurrency: RequestLogicTypes.ICurrency, | ||
paymentReference: string, | ||
paymentNetwork: ExtensionTypes.IState<any>, | ||
): Promise<PaymentTypes.ETHPaymentNetworkEvent[]> { | ||
const network = requestCurrency.network; | ||
if (!network) { | ||
throw Error('requestCurrency.network must be defined'); | ||
} | ||
|
||
const { ethFeeProxyContract, conversionProxyContract } = await this.safeGetProxiesArtifacts( | ||
network, | ||
paymentNetwork.version, | ||
); | ||
|
||
if (!ethFeeProxyContract) { | ||
throw Error('ETH fee proxy contract not found'); | ||
} | ||
if (!conversionProxyContract) { | ||
throw Error('ETH conversion proxy contract not found'); | ||
} | ||
|
||
const currency = this.currencyManager.fromStorageCurrency(requestCurrency); | ||
if (!currency) { | ||
throw Error('requestCurrency not found in currency manager'); | ||
} | ||
|
||
const proxyInfoRetriever = new ProxyInfoRetriever( | ||
currency, | ||
paymentReference, | ||
conversionProxyContract.address, | ||
conversionProxyContract.creationBlockNumber, | ||
ethFeeProxyContract.address, | ||
ethFeeProxyContract.creationBlockNumber, | ||
address, | ||
eventName, | ||
network, | ||
paymentNetwork.values?.maxRateTimespan, | ||
); | ||
|
||
return await proxyInfoRetriever.getTransferEvents(); | ||
} | ||
|
||
/* | ||
* Fetches events from the Ethereum Proxy, or returns null | ||
*/ | ||
private async safeGetProxiesArtifacts(network: string, paymentNetworkVersion: string) { | ||
const contractVersion = PROXY_CONTRACT_ADDRESS_MAP[paymentNetworkVersion]; | ||
let ethFeeProxyContract = null; | ||
let conversionProxyContract = null; | ||
|
||
try { | ||
ethFeeProxyContract = SmartContracts.ethConversionArtifact.getDeploymentInformation( | ||
network, | ||
contractVersion, | ||
); | ||
} catch (error) { | ||
console.warn(error); | ||
} | ||
|
||
try { | ||
conversionProxyContract = SmartContracts.ethereumFeeProxyArtifact.getDeploymentInformation( | ||
network, | ||
contractVersion, | ||
); | ||
} catch (error) { | ||
console.warn(error); | ||
} | ||
|
||
return { ethFeeProxyContract, conversionProxyContract }; | ||
} | ||
} |
174 changes: 174 additions & 0 deletions
174
packages/payment-detection/src/any/any-to-eth-proxy-info-retriever.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import { CurrencyDefinition } from '@requestnetwork/currency'; | ||
import { PaymentTypes } from '@requestnetwork/types'; | ||
import { BigNumber, ethers } from 'ethers'; | ||
import { getDefaultProvider } from '../provider'; | ||
import { parseLogArgs, unpadAmountFromChainlink } from '../utils'; | ||
|
||
// The conversion proxy smart contract ABI fragment containing TransferWithConversionAndReference event | ||
const ethConversionProxyContractAbiFragment = [ | ||
'event TransferWithConversionAndReference(uint256 amount, address currency, bytes indexed paymentReference, uint256 feeAmount, uint256 maxRateTimespan)', | ||
]; | ||
|
||
// The ETH proxy smart contract ABI fragment containing TransferWithReference event | ||
const ethFeeProxyContractAbiFragment = [ | ||
'event TransferWithReferenceAndFee(address to,uint256 amount,bytes indexed paymentReference,uint256 feeAmount,address feeAddress)', | ||
]; | ||
|
||
/** TransferWithConversionAndReference event */ | ||
type TransferWithConversionAndReferenceArgs = { | ||
amount: BigNumber; | ||
currency: string; | ||
paymentReference: string; | ||
feeAmount: BigNumber; | ||
maxRateTimespan: BigNumber; | ||
}; | ||
|
||
/** TransferWithReferenceAndFee event */ | ||
type TransferWithReferenceAndFeeArgs = { | ||
to: string; | ||
amount: BigNumber; | ||
paymentReference: string; | ||
feeAmount: BigNumber; | ||
feeAddress: string; | ||
}; | ||
|
||
/** | ||
* Retrieves a list of payment events from a payment reference, a destination address, a token address and a proxy contract | ||
*/ | ||
export default class AnyToEthProxyInfoRetriever | ||
implements PaymentTypes.IPaymentNetworkInfoRetriever<PaymentTypes.ETHPaymentNetworkEvent> { | ||
public contractConversionProxy: ethers.Contract; | ||
public contractETHFeeProxy: ethers.Contract; | ||
public provider: ethers.providers.Provider; | ||
|
||
/** | ||
* @param requestCurrency The request currency | ||
* @param paymentReference The reference to identify the payment | ||
* @param conversionProxyContractAddress The address of the proxy contract | ||
* @param conversionProxyCreationBlockNumber The block that created the proxy contract | ||
* @param toAddress Address of the balance we want to check | ||
* @param eventName Indicate if it is an address for payment or refund | ||
* @param network The Ethereum network to use | ||
*/ | ||
constructor( | ||
private requestCurrency: CurrencyDefinition, | ||
private paymentReference: string, | ||
private conversionProxyContractAddress: string, | ||
private conversionProxyCreationBlockNumber: number, | ||
private ethFeeProxyContractAddress: string, | ||
private ethFeeProxyCreationBlockNumber: number, | ||
private toAddress: string, | ||
private eventName: PaymentTypes.EVENTS_NAMES, | ||
private network: string, | ||
private maxRateTimespan: number = 0, | ||
) { | ||
// Creates a local or default provider | ||
this.provider = getDefaultProvider(this.network); | ||
|
||
// Setup the conversion proxy contract interface | ||
this.contractConversionProxy = new ethers.Contract( | ||
this.conversionProxyContractAddress, | ||
ethConversionProxyContractAbiFragment, | ||
this.provider, | ||
); | ||
|
||
this.contractETHFeeProxy = new ethers.Contract( | ||
this.ethFeeProxyContractAddress, | ||
ethFeeProxyContractAbiFragment, | ||
this.provider, | ||
); | ||
} | ||
|
||
/** | ||
* Retrieves transfer events from the payment proxy and conversion proxy. | ||
* Logs from both proxies are matched by transaction hash, as both proxies should | ||
* be called in one transaction. | ||
* | ||
* The conversion proxy's logs are used to compute the amounts in request currency (typically fiat). | ||
* The payment proxy's logs are used the same way as for a pn-fee-proxy request. | ||
*/ | ||
public async getTransferEvents(): Promise<PaymentTypes.ETHPaymentNetworkEvent[]> { | ||
// Create a filter to find all the Fee Transfer logs with the payment reference | ||
const conversionFilter = this.contractConversionProxy.filters.TransferWithConversionAndReference( | ||
null, | ||
null, | ||
'0x' + this.paymentReference, | ||
) as ethers.providers.Filter; | ||
conversionFilter.fromBlock = this.conversionProxyCreationBlockNumber; | ||
conversionFilter.toBlock = 'latest'; | ||
|
||
// Get the fee proxy contract event logs | ||
const conversionLogs = await this.provider.getLogs(conversionFilter); | ||
|
||
// Create a filter to find all the Fee Transfer logs with the payment reference | ||
const feeFilter = this.contractETHFeeProxy.filters.TransferWithReferenceAndFee( | ||
null, | ||
null, | ||
'0x' + this.paymentReference, | ||
null, | ||
null, | ||
) as ethers.providers.Filter; | ||
feeFilter.fromBlock = this.ethFeeProxyCreationBlockNumber; | ||
feeFilter.toBlock = 'latest'; | ||
|
||
// Get the fee proxy contract event logs | ||
const feeLogs = await this.provider.getLogs(feeFilter); | ||
|
||
// Parses, filters and creates the events from the logs with the payment reference | ||
const eventPromises = conversionLogs | ||
// Parses the logs | ||
.map((log) => { | ||
const parsedConversionLog = this.contractConversionProxy.interface.parseLog(log); | ||
const proxyLog = feeLogs.find((l) => l.transactionHash === log.transactionHash); | ||
if (!proxyLog) { | ||
throw new Error('proxy log not found'); | ||
} | ||
const parsedProxyLog = this.contractETHFeeProxy.interface.parseLog(proxyLog); | ||
return { | ||
transactionHash: log.transactionHash, | ||
blockNumber: log.blockNumber, | ||
conversionLog: parseLogArgs<TransferWithConversionAndReferenceArgs>(parsedConversionLog), | ||
proxyLog: parseLogArgs<TransferWithReferenceAndFeeArgs>(parsedProxyLog), | ||
}; | ||
}) | ||
// Keeps only the log with the right token and the right destination address | ||
// With ethers v5, the criteria below can be added to the conversionFilter (PROT-1234) | ||
.filter( | ||
({ conversionLog, proxyLog }) => | ||
// check the rate timespan | ||
this.maxRateTimespan >= conversionLog.maxRateTimespan.toNumber() && | ||
// check the requestCurrency | ||
this.requestCurrency.hash.toLowerCase() === conversionLog.currency.toLowerCase() && | ||
// check to address | ||
proxyLog.to.toLowerCase() === this.toAddress.toLowerCase(), | ||
) | ||
// Creates the balance events | ||
.map(async ({ conversionLog, proxyLog, blockNumber, transactionHash }) => { | ||
const requestCurrency = this.requestCurrency; | ||
|
||
const amount = unpadAmountFromChainlink(conversionLog.amount, requestCurrency).toString(); | ||
const feeAmount = unpadAmountFromChainlink( | ||
conversionLog.feeAmount, | ||
requestCurrency, | ||
).toString(); | ||
|
||
return { | ||
amount, | ||
name: this.eventName, | ||
parameters: { | ||
block: blockNumber, | ||
feeAddress: proxyLog.feeAddress || undefined, | ||
feeAmount, | ||
feeAmountInCrypto: proxyLog.feeAmount.toString() || undefined, | ||
amountInCrypto: proxyLog.amount.toString(), | ||
to: this.toAddress, | ||
txHash: transactionHash, | ||
maxRateTimespan: conversionLog.maxRateTimespan.toString(), | ||
}, | ||
timestamp: (await this.provider.getBlock(blockNumber || 0)).timestamp, | ||
}; | ||
}); | ||
|
||
return Promise.all(eventPromises); | ||
} | ||
} |
Oops, something went wrong.