import { TypedDataSigner } from '@ethersproject/abstract-signer'; import { Signature } from '@ethersproject/bytes'; import BigNumberJs from 'bignumber.js'; import { constants, providers, utils } from 'ethers'; import BaseService from '../commons/BaseService'; import { eEthereumTxType, EthereumTransactionTypeExtended, transactionType, } from '../commons/types'; import { convertSignatureToEIP2098, getDomainSeparator, } from '../commons/utils'; import { IP2PPairStaking, P2PPairStaking } from './typechain/P2PPairStaking'; import { P2PPairStaking__factory } from './typechain/P2PPairStaking__factory'; import ListingOrderStruct = IP2PPairStaking.ListingOrderStruct; const APE_COIN_DECIMAL = 18; export enum ApeListingOrderStatus { PENDING, MATCHED, CANCELLED, } export enum StakingType { BAYCStaking, MAYCStaking, BAKCPairStaking, } export interface P2PListing { stakingType: StakingType; offerer: string; token: string; tokenId: number; share: number; startTime: number; endTime: number; } export type P2PListingWithSign = P2PListing & Signature; const LISTING_ORDER_TYPE_SIG = utils.keccak256( utils.toUtf8Bytes( 'ListingOrder(uint8 stakingType,address offerer,address token,uint256 tokenId,uint256 share,uint256 startTime,uint256 endTime)', ), ); const SIGN_LISTING_TYPE = { ListingOrder: [ { name: 'stakingType', type: 'uint8' }, { name: 'offerer', type: 'address' }, { name: 'token', type: 'address' }, { name: 'tokenId', type: 'uint256' }, { name: 'share', type: 'uint256' }, { name: 'startTime', type: 'uint256' }, { name: 'endTime', type: 'uint256' }, ], }; export class P2PPairStakingService extends BaseService { instance: P2PPairStaking; constructor(provider: providers.Provider, address: string) { super(provider, P2PPairStaking__factory); this.instance = this.getContractInstance(address); } public getListingOrderHash(listing: P2PListing) { const hash = utils.keccak256( utils.defaultAbiCoder.encode( [ 'bytes32', 'uint8', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', ], [ LISTING_ORDER_TYPE_SIG, listing.stakingType, listing.offerer, listing.token, listing.tokenId, listing.share, listing.startTime, listing.endTime, ], ), ); return hash; } public async createListing(signer: TypedDataSigner, listing: P2PListing) { const domainSeparator = await getDomainSeparator( this.provider, this.instance.address, '1', ); const signature = await signer._signTypedData( domainSeparator, SIGN_LISTING_TYPE, listing, ); const vrs = utils.splitSignature(convertSignatureToEIP2098(signature)); return { ...listing, ...vrs, }; } public async verifyListingSignature(listing: P2PListingWithSign) { const { offerer, v, r, s } = listing; const orderHash = this.getListingOrderHash(listing); return this.instance.validateOrderSignature(offerer, orderHash, v, r, s); } public cancelListing(listing: ListingOrderStruct, user: string) { const tx: () => Promise = this.generateTxCallback({ rawTxMethod: async () => this.instance.populateTransaction.cancelListing(listing), from: user, }); return { tx, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], tx), }; } public breakUpMatchedOrder(orderHash: string, user: string) { const tx: () => Promise = this.generateTxCallback({ rawTxMethod: async () => this.instance.populateTransaction.breakUpMatchedOrder(orderHash), from: user, }); return { tx, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], tx), }; } public matchPairStakingList( apeListing: ListingOrderStruct, apeCoinListing: ListingOrderStruct, user: string, ): EthereumTransactionTypeExtended { const tx: () => Promise = this.generateTxCallback({ rawTxMethod: async () => this.instance.populateTransaction.matchPairStakingList( apeListing, apeCoinListing, ), from: user, }); return { tx, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], tx), }; } public matchBAKCPairStakingList( apeListing: ListingOrderStruct, bakcListing: ListingOrderStruct, apeCoinListing: ListingOrderStruct, user: string, ): EthereumTransactionTypeExtended { const tx: () => Promise = this.generateTxCallback({ rawTxMethod: async () => this.instance.populateTransaction.matchBAKCPairStakingList( apeListing, bakcListing, apeCoinListing, ), from: user, }); return { tx, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], tx), }; } public claimCApeReward(receiver: string) { const tx: () => Promise = this.generateTxCallback({ rawTxMethod: async () => this.instance.populateTransaction.claimCApeReward(receiver), from: receiver, }); return { tx, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], tx), }; } public async pendingCApeReward(user: string) { const result = await this.instance.pendingCApeReward(user); return new BigNumberJs(result.toString()).shiftedBy(-1 * APE_COIN_DECIMAL); } public async isListingOrderCanceled(hash: string) { const status = await this.instance.listingOrderStatus(hash); return status === ApeListingOrderStatus.CANCELLED; } public async isOrderMatched(hash: string) { const order = await this.instance.matchedOrders(hash); return order.apeCoinOfferer !== constants.AddressZero; } }