From d03e3a01f4e51a7e465727e8a136054af3e3ff72 Mon Sep 17 00:00:00 2001 From: Kaden Zipfel Date: Mon, 26 Apr 2021 15:16:16 -0700 Subject: [PATCH] Add cpk.addFundingAndStake --- .../market_pooling/market_pool_liquidity.tsx | 23 +++ app/src/services/cpk.ts | 159 ++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/app/src/components/market/sections/market_pooling/market_pool_liquidity.tsx b/app/src/components/market/sections/market_pooling/market_pool_liquidity.tsx index 0a409dc024..fce9b545e3 100644 --- a/app/src/components/market/sections/market_pooling/market_pool_liquidity.tsx +++ b/app/src/components/market/sections/market_pooling/market_pool_liquidity.tsx @@ -318,6 +318,28 @@ const MarketPoolLiquidityWrapper: React.FC = (props: Props) => { } } + const addFundingAndStake = async () => { + if (!cpk) { + return + } + + let useBaseToken = false + if (displayCollateral.address !== collateral.address && collateral.symbol.toLowerCase() in CompoundTokenType) { + useBaseToken = true + } + + await cpk.addFundingAndStake({ + amount: amountToFundNormalized || Zero, + campaignAddress: liquidityMiningCampaign?.id || '', + compoundService, + collateral, + marketMaker, + setTxHash, + setTxState, + useBaseToken, + }) + } + const removeFunding = async () => { try { if (!cpk) { @@ -900,6 +922,7 @@ const MarketPoolLiquidityWrapper: React.FC = (props: Props) => { Withdraw )} + diff --git a/app/src/services/cpk.ts b/app/src/services/cpk.ts index a32364c249..9304d1b4e5 100644 --- a/app/src/services/cpk.ts +++ b/app/src/services/cpk.ts @@ -138,6 +138,17 @@ interface CPKSubmitAnswerParams { setTxState: (step: TransactionStep) => void } +interface CPKAddFundingAndStakeParams { + amount: BigNumber + campaignAddress: string + collateral: Token + compoundService?: CompoundService | null + marketMaker: MarketMakerService + useBaseToken?: boolean + setTxHash: (arg0: string) => void + setTxState: (step: TransactionStep) => void +} + interface TransactionResult { hash?: string safeTxHash?: string @@ -1454,6 +1465,154 @@ class CPKService { } } + addFundingAndStake = async ({ + amount, + campaignAddress, + collateral, + compoundService, + marketMaker, + setTxHash, + setTxState, + useBaseToken, + }: CPKAddFundingAndStakeParams): Promise => { + try { + const signer = this.provider.getSigner() + const account = await signer.getAddress() + + const network = await this.provider.getNetwork() + const networkId = network.chainId + + const transactions: Transaction[] = [] + + const txOptions: TxOptions = {} + txOptions.gas = defaultGas + + let collateralSymbol = '' + let userInputCollateralSymbol: KnownToken + let userInputCollateral: Token = collateral + + let collateralAddress + + const fundingAmount = this.cpk.relay ? amount.sub(RELAY_FEE) : amount + + if (collateral.address === pseudoNativeAssetAddress) { + // ultimately WETH will be the collateral if we fund with native ether + collateralAddress = getWrapToken(networkId).address + + // we need to send the funding amount in native ether + if (!this.isSafeApp) { + txOptions.value = fundingAmount + } + + // Step 0: Wrap ether + transactions.push({ + to: collateralAddress, + value: fundingAmount.toString(), + }) + } else { + collateralAddress = collateral.address + } + const collateralService = new ERC20Service(this.provider, account, collateralAddress) + // Check if the allowance of the CPK to the market maker is enough. + const hasCPKEnoughAlowance = await collateralService.hasEnoughAllowance( + this.cpk.address, + marketMaker.address, + fundingAmount, + ) + if (!hasCPKEnoughAlowance) { + // Step 1: Approve unlimited amount to be transferred to the market maker + transactions.push({ + to: collateralAddress, + data: ERC20Service.encodeApproveUnlimited(marketMaker.address), + }) + } + let minCollateralAmount = fundingAmount + if (useBaseToken && compoundService != null) { + collateralSymbol = collateral.symbol.toLowerCase() + if (collateralSymbol === 'ceth') { + userInputCollateral = getNativeAsset(networkId) + } else { + userInputCollateralSymbol = collateralSymbol.substring(1, collateralSymbol.length) as KnownToken + userInputCollateral = getToken(networkId, userInputCollateralSymbol) + } + minCollateralAmount = compoundService.calculateBaseToCTokenExchange(userInputCollateral, fundingAmount) + } + // If we are signed in as a safe we don't need to transfer + if (!this.isSafeApp && collateral.address !== pseudoNativeAssetAddress) { + // Step 4: Transfer funding from user + if (useBaseToken) { + // If use base token then transfer the base token amount from the user + if (collateral.address !== pseudoNativeAssetAddress) { + transactions.push({ + to: userInputCollateral.address, + data: ERC20Service.encodeTransferFrom(account, this.cpk.address, fundingAmount), + }) + } + } else { + // If use collateral token then transfer the collateral token amount from the user + transactions.push({ + to: collateral.address, + data: ERC20Service.encodeTransferFrom(account, this.cpk.address, minCollateralAmount), + }) + } + } + if (useBaseToken) { + // get base token + const encodedMintFunction = CompoundService.encodeMintTokens(collateralSymbol, fundingAmount.toString()) + // Approve cToken for the cpk contract + if (userInputCollateral.address === pseudoNativeAssetAddress) { + if (!this.isSafeApp) { + txOptions.value = fundingAmount + } + transactions.push({ + to: collateral.address, + data: encodedMintFunction, + value: fundingAmount.toString(), + }) + } else { + transactions.push({ + to: userInputCollateral.address, + data: ERC20Service.encodeApproveUnlimited(collateral.address), + }) + // Mint ctokens from the underlying token + transactions.push({ + to: collateral.address, + data: encodedMintFunction, + }) + } + } + + // Step 3: Add funding to market + transactions.push({ + to: marketMaker.address, + data: MarketMakerService.encodeAddFunding(minCollateralAmount), + }) + + const omnToken = getOMNToken(networkId).address + const erc20Service = new ERC20Service(this.provider, this.cpk.address, omnToken) + const hasEnoughAllowance = await erc20Service.hasEnoughAllowance(this.cpk.address, campaignAddress, amount) + + // Step 4: Approve pool tokens to be sent to campaign address if not already done + if (!hasEnoughAllowance) { + transactions.push({ + to: marketMaker.address, + data: ERC20Service.encodeApproveUnlimited(campaignAddress), + }) + } + + // Step 5: Stake pool tokens + transactions.push({ + to: campaignAddress, + data: StakingService.encodeStakePoolTokens(amount), + }) + + return this.execTransactions(transactions, txOptions, setTxHash, setTxState) + } catch (err) { + logger.error(`There was an error adding and staking`, err.message) + throw err + } + } + withdrawStakedPoolTokens = async (amount: BigNumber, campaignAddress: string) => { try { const transactions: Transaction[] = []