import { BigNumberish, ethers } from 'ethers' import ERC20ABI from '../abis/erc20.json' import WTokenABI from '../abis/wToken.json' import WithdrawQABI from '../abis/withdrawQ.json' import { type Chain } from 'viem' export enum WithdrawStatus { Pending = 0, Completed = 1, Cancelled = 2, } export abstract class BaseProject { abstract name: string abstract miningToken: { name: string symbol: string decimals: number isNative?: boolean icon: string } abstract pToken: { name: string symbol: string decimals: number icon: string } abstract chainConfig: Chain abstract contracts: { core?: string delegationPool: string pToken: string miningToken: string dataHelper?: string thawing?: string vesting?: string wpToken?: string withdrawQueue?: string } isTestnet?: boolean metadata: { guideLink?: string description?: string website?: string about?: string withdrawTips?: string } = { description: 'Mining rewards consist of locked and unlocked portion. Rewards will be unlocked daily until it reaches the expected returns. Unlocked rewards can be withdrawn anytime.', guideLink: '', website: '', about: '', withdrawTips: '', } confirmations = 1 thawingEnabled?: boolean withdrawQueueEnabled?: boolean minRequestAmount?: number provider?: ethers.providers.JsonRpcProvider constructor(provider?: ethers.providers.JsonRpcProvider) { if (provider) { this.provider = provider } } abstract getAssetsInfo: () => Promise<{ apy: number lockedAssets: number }> getWithdrawRequest?: (address: string) => Promise getProvider = () => { if (this.provider) { return this.provider } const provider = new ethers.providers.JsonRpcProvider( this.chainConfig.rpcUrls.default.http[0] ) this.provider = provider return provider } getPoolContract = () => { const provider = this.getProvider() return new ethers.Contract( this.contracts.delegationPool, [ 'function deposit(uint256, address) external payable', 'function withdraw(uint256, address) external', ], provider ) } getThawingContract = () => { const provider = this.getProvider() return new ethers.Contract( this.contracts.thawing!, [ 'function getWithdrawRequest(address user) external view returns (uint256 amount, uint256 unlockTime)', 'function cancelWithdraw() external', 'function requestWithdraw(uint256 amount) external', ], provider ) } getWithdrawQueueContract = () => { const provider = this.getProvider() return new ethers.Contract( this.contracts.withdrawQueue!, WithdrawQABI, provider ) } getVestingContract = () => { const provider = this.getProvider() return new ethers.Contract( this.contracts.vesting!, [ 'function totalVestingAmount() external view returns (uint256)', 'function vestingPeriod() external view returns (uint256)', ], provider ) } getTokenContract = (address: string) => { const provider = this.getProvider() return new ethers.Contract(address, ERC20ABI, provider) } getWPTokenContract = () => { const provider = this.getProvider() return new ethers.Contract(this.contracts.wpToken!, WTokenABI, provider) } getMiningTokenBalance: (address: string) => Promise = async (address) => { if (this.miningToken.isNative) { return this.getProvider().getBalance(address) } return this.getTokenContract(this.contracts.miningToken).balanceOf( address ) } getPTokenBalance: (address: string) => Promise = async ( address ) => { return this.getTokenContract(this.contracts.pToken).balanceOf(address) } getWPTokenBalance: (address: string) => Promise = async ( address ) => { if (!this.contracts.wpToken) { throw new Error('wpToken not found') } return this.getWPTokenContract().balanceOf(address) } deposit = async (address: string, amount: string, signer: ethers.Signer) => { const contract = this.getPoolContract() const tx = await contract .connect(signer) .deposit( ethers.utils.parseUnits(amount, this.miningToken.decimals), address, { value: this.miningToken.isNative ? ethers.utils.parseUnits(amount, this.miningToken.decimals) : undefined, } ) return tx } withdraw = async (address: string, amount: string, signer: ethers.Signer) => { const contract = this.getPoolContract().connect(signer) const tx = await contract.withdraw( ethers.utils.parseUnits(amount, this.pToken.decimals), address ) return tx } approveTokenToContract = async ( token: string, spender: string, amount: string, decimals: number, signer: ethers.Signer ) => { const erc20 = this.getTokenContract(token).connect(signer) const allowance = await erc20.allowance(signer.getAddress(), spender) if (allowance.lt(ethers.utils.parseUnits(amount, decimals))) { const tx = await erc20.approve( spender, ethers.utils.parseUnits(amount, decimals) ) return tx } return null } approveWithdrawQueue = async (amount: string, signer: ethers.Signer) => { const erc20 = this.getTokenContract(this.contracts.pToken).connect(signer) const allowance = await erc20.allowance( signer.getAddress(), this.contracts.withdrawQueue! ) if (allowance.lt(ethers.utils.parseUnits(amount, this.pToken.decimals))) { const tx = await erc20.approve( this.contracts.withdrawQueue!, ethers.utils.parseUnits(amount, this.pToken.decimals) ) return tx } return null } getPoolBalance = async () => { return this.getMiningTokenBalance(this.contracts.delegationPool) } getThawingInfo = async (address: string) => { const thawingContract = this.getThawingContract() const [amount, unlockTime] = await thawingContract.getWithdrawRequest( address ) return { amount, unlockTime: Number(unlockTime), } } requestWithdraw = async (amount: string, signer: ethers.Signer) => { const pool = this.getThawingContract() const tx = await pool .connect(signer) .requestWithdraw(ethers.utils.parseUnits(amount, this.pToken.decimals)) return tx } cancelWithdraw = async (address: string, signer: ethers.Signer) => { const thawingContract = this.getThawingContract().connect(signer) const tx = await thawingContract.cancelWithdraw() return tx } cancelWithdrawQueue = async (requestId: number, signer: ethers.Signer) => { const thawingContract = this.getWithdrawQueueContract().connect(signer) const tx = await thawingContract.cancelQueuedWithdraw(requestId) return tx } wrap = async (amount: string, signer: ethers.Signer) => { const wTokenContract = this.getWPTokenContract().connect(signer) const tx = await wTokenContract.wrap( ethers.utils.parseUnits(amount, this.pToken.decimals) ) return tx } unwrap = async (amount: string, signer: ethers.Signer) => { const wTokenContract = this.getWPTokenContract().connect(signer) const tx = await wTokenContract.unwrap( ethers.utils.parseUnits(amount, this.pToken.decimals) ) return tx } getRequestIdsInQueue = async (address: string) => { if (!this.withdrawQueueEnabled) { throw new Error('Withdraw queue is not enabled') } const poolContract = this.getWithdrawQueueContract() const result = await poolContract.getUserQueuedRequests(address) return result } getRequestCountInQueue = async (address: string) => { if (!this.withdrawQueueEnabled) { throw new Error('Withdraw queue is not enabled') } const ids = await this.getRequestIdsInQueue(address) return ids.length } getRequestInQueue = async (requestId: number) => { if (!this.withdrawQueueEnabled) { throw new Error('Withdraw queue is not enabled') } const poolContract = this.getWithdrawQueueContract() const result = await poolContract.getRequest(requestId) const status = Number(result.status) return { owner: result.user, totalShares: result.totalShares, timestamp: result.timestamp, status: status as WithdrawStatus, remainingShares: result.remainingShares, fullfilled: status === WithdrawStatus.Completed, } } getWithdrawQueueByAddress = async (address: string) => { if (!this.withdrawQueueEnabled) { throw new Error('Withdraw queue is not enabled') } const ids = await this.getRequestIdsInQueue(address) const requests = await Promise.all( ids.map(async (id: BigNumberish) => { const result = await this.getRequestInQueue(Number(id)) return { ...result, requestId: Number(id), } }) ) return requests } requestWithdrawQueue = async (amount: string, signer: ethers.Signer) => { if (!this.withdrawQueueEnabled) { throw new Error('Withdraw queue is not enabled') } const poolContract = this.getWithdrawQueueContract().connect(signer) const tx = await poolContract.requestWithdraw( ethers.utils.parseUnits(amount, this.pToken.decimals) ) return tx } convertToAssets = async (amount: string) => { const tokenContract = this.getTokenContract(this.contracts.pToken!) return await tokenContract.convertToAssets( ethers.utils.parseUnits(amount, this.pToken.decimals) ) } convertToShares = async (amount: string) => { const tokenContract = this.getTokenContract(this.contracts.pToken!) return await tokenContract.convertToShares( ethers.utils.parseUnits(amount, this.pToken.decimals) ) } }