From b5c10153d5b15bba55156fc202298459e5a0fbea Mon Sep 17 00:00:00 2001 From: Evgenii <68502706+EvgeniiVoznyuk@users.noreply.github.com> Date: Fri, 1 Jul 2022 02:42:48 +0300 Subject: [PATCH] dev (#1) (#812) * dev --- Strategies/components/DepositModal.tsx | 9 + .../components/EverlendModalContent.tsx | 85 +++++ .../components/everlend/EverlendDeposit.tsx | 244 ++++++++++++++ .../components/everlend/EverlendWithdraw.tsx | 231 +++++++++++++ .../everlend/preparedSolDepositTx.ts | 136 ++++++++ Strategies/protocols/everlend/tools.ts | 313 ++++++++++++++++++ Strategies/store/useStrategiesStore.tsx | 4 +- .../TreasuryAccount/AccountOverview.tsx | 34 ++ package.json | 1 + public/realms/Everlend/img/logo.png | Bin 0 -> 33010 bytes yarn.lock | 47 ++- 11 files changed, 1102 insertions(+), 2 deletions(-) create mode 100644 Strategies/components/EverlendModalContent.tsx create mode 100644 Strategies/components/everlend/EverlendDeposit.tsx create mode 100644 Strategies/components/everlend/EverlendWithdraw.tsx create mode 100644 Strategies/protocols/everlend/preparedSolDepositTx.ts create mode 100644 Strategies/protocols/everlend/tools.ts create mode 100644 public/realms/Everlend/img/logo.png diff --git a/Strategies/components/DepositModal.tsx b/Strategies/components/DepositModal.tsx index 3ac535c1a1..f8c23c3da4 100644 --- a/Strategies/components/DepositModal.tsx +++ b/Strategies/components/DepositModal.tsx @@ -4,6 +4,7 @@ import SolendModalContent from './SolendModalContent' import MangoDeposit from './MangoDepositComponent' import BigNumber from 'bignumber.js' import { SolendStrategy } from 'Strategies/types/types' +import EverlendModalContent from './EverlendModalContent' const DepositModal = ({ onClose, @@ -49,6 +50,14 @@ const DepositModal = ({ createProposalFcn={createProposalFcn} > ) : null} + {protocolName === 'Everlend' ? ( + + ) : null} ) } diff --git a/Strategies/components/EverlendModalContent.tsx b/Strategies/components/EverlendModalContent.tsx new file mode 100644 index 0000000000..5bed1195ec --- /dev/null +++ b/Strategies/components/EverlendModalContent.tsx @@ -0,0 +1,85 @@ +import ButtonGroup from '@components/ButtonGroup' +import { useEffect, useState } from 'react' +import { TreasuryStrategy } from 'Strategies/types/types' +import { CreateEverlendProposal } from 'Strategies/protocols/everlend/tools' +import { AssetAccount } from '@utils/uiTypes/assets' +import EverlendDeposit from './everlend/EverlendDeposit' +import EverlendWithdraw from './everlend/EverlendWithdraw' +import { findAssociatedTokenAccount } from '@everlend/common' +import { PublicKey } from '@solana/web3.js' +import useWalletStore from 'stores/useWalletStore' + +enum Tabs { + DEPOSIT = 'Deposit', + WITHDRAW = 'Withdraw', +} + +interface IProps { + proposedInvestment: TreasuryStrategy & { poolMint: string } + handledMint: string + createProposalFcn: CreateEverlendProposal + governedTokenAccount: AssetAccount +} + +const EverlendModalContent = ({ + proposedInvestment, + handledMint, + createProposalFcn, + governedTokenAccount, +}: IProps) => { + const [selectedTab, setSelectedTab] = useState(Tabs.DEPOSIT) + const [depositedAmount, setDepositedAmount] = useState(0) + const tabs = Object.values(Tabs) + const connection = useWalletStore((s) => s.connection) + + const isSol = governedTokenAccount.isSol + const owner = isSol + ? governedTokenAccount!.pubkey + : governedTokenAccount!.extensions!.token!.account.owner + + useEffect(() => { + const loadMaxAmount = async () => { + const tokenMintATA = await findAssociatedTokenAccount( + owner, + new PublicKey(proposedInvestment.poolMint) + ) + const tokenMintATABalance = await connection.current.getTokenAccountBalance( + tokenMintATA + ) + setDepositedAmount(Number(tokenMintATABalance.value.uiAmount)) + } + loadMaxAmount() + }, [proposedInvestment, handledMint]) + + return ( +
+
+ setSelectedTab(tab)} + values={tabs} + /> +
+ {selectedTab === Tabs.DEPOSIT && ( + + )} + {selectedTab === Tabs.WITHDRAW && ( + + )} +
+ ) +} + +export default EverlendModalContent diff --git a/Strategies/components/everlend/EverlendDeposit.tsx b/Strategies/components/everlend/EverlendDeposit.tsx new file mode 100644 index 0000000000..9b1565e283 --- /dev/null +++ b/Strategies/components/everlend/EverlendDeposit.tsx @@ -0,0 +1,244 @@ +import Button, { LinkButton } from '@components/Button' +import Tooltip from '@components/Tooltip' +import Input from '@components/inputs/Input' +import { useState } from 'react' +import { useRouter } from 'next/router' +import useQueryContext from '@hooks/useQueryContext' +import useRealm from '@hooks/useRealm' +import useVotePluginsClientStore from 'stores/useVotePluginsClientStore' +import useWalletStore from 'stores/useWalletStore' +import tokenService from '@utils/services/token' +import BN from 'bn.js' +import { + fmtMintAmount, + getMintMinAmountAsDecimal, + getMintNaturalAmountFromDecimalAsBN, +} from '@tools/sdk/units' +import { RpcContext } from '@solana/spl-governance' +import { PublicKey } from '@solana/web3.js' +import { getProgramVersionForRealm } from '@models/registry/api' +import { AssetAccount } from '@utils/uiTypes/assets' +import { CreateEverlendProposal } from '../../protocols/everlend/tools' +import AdditionalProposalOptions from '@components/AdditionalProposalOptions' +import * as yup from 'yup' +import { precision } from '@utils/formatting' +import { validateInstruction } from '@utils/instructionTools' +import useGovernanceAssets from '@hooks/useGovernanceAssets' +import Loading from '@components/Loading' + +interface IProps { + proposedInvestment + handledMint: string + createProposalFcn: CreateEverlendProposal + governedTokenAccount: AssetAccount + depositedAmount: number +} + +const EverlendDeposit = ({ + proposedInvestment, + createProposalFcn, + governedTokenAccount, + depositedAmount, +}: IProps) => { + const [amount, setAmount] = useState(0) + const tokenSymbol = tokenService.getTokenInfo( + governedTokenAccount.extensions.mint!.publicKey.toBase58() + )?.symbol + + const proposalTitle = `Deposit ${amount} ${ + tokenSymbol || 'tokens' + } to the Everlend pool` + + const [proposalInfo, setProposalInfo] = useState({ + title: '', + description: '', + }) + const [formErrors, setFormErrors] = useState({}) + const [isDepositing, setIsDepositing] = useState(false) + const router = useRouter() + const { fmtUrlWithCluster } = useQueryContext() + const { + realmInfo, + realm, + mint, + councilMint, + ownVoterWeight, + symbol, + } = useRealm() + const [voteByCouncil, setVoteByCouncil] = useState(false) + const client = useVotePluginsClientStore( + (s) => s.state.currentRealmVotingClient + ) + const connection = useWalletStore((s) => s.connection) + const wallet = useWalletStore((s) => s.current) + + const { canUseTransferInstruction } = useGovernanceAssets() + + const treasuryAmount = new BN( + governedTokenAccount.isSol + ? governedTokenAccount.extensions.amount!.toNumber() + : governedTokenAccount.extensions.token!.account.amount + ) + + const mintInfo = governedTokenAccount.extensions?.mint?.account + + const mintMinAmount = mintInfo ? getMintMinAmountAsDecimal(mintInfo) : 1 + const currentPrecision = precision(mintMinAmount) + const maxAmountFormatted = fmtMintAmount(mintInfo, treasuryAmount) + + const handleDeposit = async () => { + const isValid = await validateInstruction({ + schema, + form: { amount }, + setFormErrors, + }) + if (!isValid) { + return + } + try { + setIsDepositing(true) + const rpcContext = new RpcContext( + new PublicKey(realm!.owner), + getProgramVersionForRealm(realmInfo!), + wallet!, + connection.current, + connection.endpoint + ) + const ownTokenRecord = ownVoterWeight.getTokenRecordToCreateProposal( + governedTokenAccount!.governance!.account.config, + voteByCouncil + ) + const defaultProposalMint = voteByCouncil + ? realm?.account.config.councilMint + : !mint?.supply.isZero() || + realm?.account.config.useMaxCommunityVoterWeightAddin + ? realm!.account.communityMint + : !councilMint?.supply.isZero() + ? realm!.account.config.councilMint + : undefined + + const proposalAddress = await createProposalFcn( + rpcContext, + { + title: proposalInfo.title || proposalTitle, + description: proposalInfo.description, + amountFmt: String(amount), + bnAmount: getMintNaturalAmountFromDecimalAsBN( + amount as number, + governedTokenAccount.extensions.mint!.account.decimals + ), + action: 'Deposit', + poolPubKey: proposedInvestment.poolPubKey, + tokenMint: proposedInvestment.handledMint, + poolMint: proposedInvestment.poolMint, + }, + realm!, + governedTokenAccount!, + ownTokenRecord, + defaultProposalMint!, + governedTokenAccount!.governance!.account!.proposalCount, + false, + connection, + client + ) + const url = fmtUrlWithCluster( + `/dao/${symbol}/proposal/${proposalAddress}` + ) + router.push(url) + } catch (e) { + console.error(e) + } + setIsDepositing(false) + } + + const schema = yup.object().shape({ + amount: yup + .number() + .required('Amount is required') + .max(Number(maxAmountFormatted)), + }) + + const validateAmountOnBlur = () => { + setAmount( + parseFloat( + Math.max( + Number(mintMinAmount), + Math.min(Number(Number.MAX_SAFE_INTEGER), Number(amount)) + ).toFixed(currentPrecision) + ) + ) + } + + return ( +
+
+ Amount +
+ Bal:{' '} + {Number(maxAmountFormatted)} + { + setAmount(Number(maxAmountFormatted)) + }} + className="font-bold ml-2 text-primary-light" + > + Max + +
+
+ setAmount(e.target.value)} + value={amount} + onBlur={validateAmountOnBlur} + error={formErrors['amount']} + /> + setProposalInfo((prev) => ({ ...prev, title: evt }))} + setDescription={(evt) => + setProposalInfo((prev) => ({ ...prev, description: evt })) + } + voteByCouncil={voteByCouncil} + setVoteByCouncil={setVoteByCouncil} + /> +
+
+ Current Deposits + + {depositedAmount}{' '} + {tokenSymbol} + +
+
+ Proposed Deposit + + {amount?.toLocaleString() || ( + Enter an amount + )}{' '} + + {amount && tokenSymbol} + + +
+
+
+ +
+
+ ) +} + +export default EverlendDeposit diff --git a/Strategies/components/everlend/EverlendWithdraw.tsx b/Strategies/components/everlend/EverlendWithdraw.tsx new file mode 100644 index 0000000000..9592af7875 --- /dev/null +++ b/Strategies/components/everlend/EverlendWithdraw.tsx @@ -0,0 +1,231 @@ +import { useState } from 'react' +import Button, { LinkButton } from '@components/Button' +import Input from '@components/inputs/Input' +import Tooltip from '@components/Tooltip' +import { RpcContext } from '@solana/spl-governance' +import { PublicKey } from '@solana/web3.js' +import { getProgramVersionForRealm } from '@models/registry/api' +import { + getMintMinAmountAsDecimal, + getMintNaturalAmountFromDecimalAsBN, +} from '@tools/sdk/units' +import { CreateEverlendProposal } from '../../protocols/everlend/tools' +import { AssetAccount } from '@utils/uiTypes/assets' +import useRealm from '@hooks/useRealm' +import useWalletStore from 'stores/useWalletStore' +import useVotePluginsClientStore from 'stores/useVotePluginsClientStore' +import useQueryContext from '@hooks/useQueryContext' +import { useRouter } from 'next/router' +import AdditionalProposalOptions from '@components/AdditionalProposalOptions' +import tokenService from '@utils/services/token' +import * as yup from 'yup' +import { precision } from '@utils/formatting' +import { validateInstruction } from '@utils/instructionTools' +import useGovernanceAssets from '@hooks/useGovernanceAssets' +import Loading from '@components/Loading' + +interface IProps { + proposedInvestment + handledMint: string + createProposalFcn: CreateEverlendProposal + governedTokenAccount: AssetAccount + depositedAmount: number +} + +const EverlendWithdraw = ({ + proposedInvestment, + createProposalFcn, + governedTokenAccount, + depositedAmount, +}: IProps) => { + const [amount, setAmount] = useState(0) + const [isWithdrawing, setIsWithdrawing] = useState(false) + const [proposalInfo, setProposalInfo] = useState({ + title: '', + description: '', + }) + const [formErrors, setFormErrors] = useState({}) + + const { + realmInfo, + realm, + mint, + councilMint, + ownVoterWeight, + symbol, + } = useRealm() + const { canUseTransferInstruction } = useGovernanceAssets() + const [voteByCouncil, setVoteByCouncil] = useState(false) + const client = useVotePluginsClientStore( + (s) => s.state.currentRealmVotingClient + ) + const { fmtUrlWithCluster } = useQueryContext() + const connection = useWalletStore((s) => s.connection) + const wallet = useWalletStore((s) => s.current) + const router = useRouter() + + const tokenSymbol = tokenService.getTokenInfo( + governedTokenAccount.extensions.mint!.publicKey.toBase58() + )?.symbol + + const proposalTitle = `Withdraw ${amount} ${ + tokenSymbol || 'tokens' + } from the Everlend pool` + + const mintInfo = governedTokenAccount.extensions?.mint?.account + const mintMinAmount = mintInfo ? getMintMinAmountAsDecimal(mintInfo) : 1 + const currentPrecision = precision(mintMinAmount) + + const handleWithdraw = async () => { + const isValid = await validateInstruction({ + schema, + form: { amount }, + setFormErrors, + }) + if (!isValid) { + return + } + try { + setIsWithdrawing(true) + const rpcContext = new RpcContext( + new PublicKey(realm!.owner), + getProgramVersionForRealm(realmInfo!), + wallet!, + connection.current, + connection.endpoint + ) + const ownTokenRecord = ownVoterWeight.getTokenRecordToCreateProposal( + governedTokenAccount!.governance!.account.config, + voteByCouncil + ) + const defaultProposalMint = voteByCouncil + ? realm?.account.config.councilMint + : !mint?.supply.isZero() || + realm?.account.config.useMaxCommunityVoterWeightAddin + ? realm!.account.communityMint + : !councilMint?.supply.isZero() + ? realm!.account.config.councilMint + : undefined + + const proposalAddress = await createProposalFcn( + rpcContext, + { + title: proposalInfo.title || proposalTitle, + description: proposalInfo.description, + amountFmt: String(amount), + bnAmount: getMintNaturalAmountFromDecimalAsBN( + amount as number, + governedTokenAccount.extensions.mint!.account.decimals + ), + action: 'Withdraw', + poolPubKey: proposedInvestment.poolPubKey, + tokenMint: proposedInvestment.handledMint, + poolMint: proposedInvestment.poolMint, + }, + realm!, + governedTokenAccount!, + ownTokenRecord, + defaultProposalMint!, + governedTokenAccount!.governance!.account!.proposalCount, + false, + connection, + client + ) + const url = fmtUrlWithCluster( + `/dao/${symbol}/proposal/${proposalAddress}` + ) + router.push(url) + } catch (e) { + console.error(e) + } + setIsWithdrawing(false) + } + + const schema = yup.object().shape({ + amount: yup.number().required('Amount is required').max(depositedAmount), + }) + + const validateAmountOnBlur = () => { + setAmount( + parseFloat( + Math.max( + Number(mintMinAmount), + Math.min(Number(Number.MAX_SAFE_INTEGER), Number(amount)) + ).toFixed(currentPrecision) + ) + ) + } + + return ( +
+
+ Amount +
+ Bal: {depositedAmount} + setAmount(depositedAmount)} + className="font-bold ml-2 text-primary-light" + > + Max + +
+
+ + setAmount(e.target.value)} + value={amount} + onBlur={validateAmountOnBlur} + error={formErrors['amount']} + /> + + setProposalInfo((prev) => ({ ...prev, title: evt }))} + setDescription={(evt) => + setProposalInfo((prev) => ({ ...prev, description: evt })) + } + voteByCouncil={voteByCouncil} + setVoteByCouncil={setVoteByCouncil} + /> +
+
+ Current Deposits + + {depositedAmount}{' '} + {tokenSymbol} + +
+
+ Proposed Deposit + + {amount?.toLocaleString() || ( + Enter an amount + )}{' '} + + {amount && tokenSymbol} + + +
+
+ +
+ +
+
+ ) +} + +export default EverlendWithdraw diff --git a/Strategies/protocols/everlend/preparedSolDepositTx.ts b/Strategies/protocols/everlend/preparedSolDepositTx.ts new file mode 100644 index 0000000000..7b32b72973 --- /dev/null +++ b/Strategies/protocols/everlend/preparedSolDepositTx.ts @@ -0,0 +1,136 @@ +import { + Connection, + Keypair, + PublicKey, + SystemProgram, + Transaction, +} from '@solana/web3.js' +import BN from 'bn.js' +import { DepositTx, Pool } from '@everlend/general-pool' +import { GeneralPoolsProgram } from '@everlend/general-pool' +import { + CreateAssociatedTokenAccount, + findAssociatedTokenAccount, + findRegistryPoolConfigAccount, +} from '@everlend/common' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + NATIVE_MINT, + Token, + TOKEN_PROGRAM_ID, +} from '@solana/spl-token' +import { syncNative } from '@solendprotocol/solend-sdk' + +export type ActionOptions = { + /** the JSON RPC connection instance. */ + connection: Connection + /** the fee payer public key, can be user's SOL address (owner address). */ + payerPublicKey: PublicKey +} + +export type ActionResult = { + /** the prepared transaction, ready for signing and sending. */ + tx: Transaction + /** the additional key pairs which may be needed for signing and sending transactions. */ + keypairs?: Record +} + +export const prepareSolDepositTx = async ( + { connection, payerPublicKey }: ActionOptions, + pool: PublicKey, + registry: PublicKey, + amount: BN, + source: PublicKey, + destination: PublicKey +): Promise => { + const { + data: { poolMarket, tokenAccount, poolMint, tokenMint }, + } = await Pool.load(connection, pool) + + const poolMarketAuthority = await GeneralPoolsProgram.findProgramAddress([ + poolMarket.toBuffer(), + ]) + + const tx = new Transaction() + const registryPoolConfig = await findRegistryPoolConfigAccount(registry, pool) + + console.log('source (ctoken)', source.toString()) + console.log('dest (liquidity)', destination.toString()) + + // Wrapping SOL + const depositAccountInfo = await connection.getAccountInfo(source) + console.log({ depositAccountInfo }) + if (!depositAccountInfo) { + // generate the instruction for creating the ATA + const createAtaInst = Token.createAssociatedTokenAccountInstruction( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + new PublicKey(tokenMint), + source, + payerPublicKey, + payerPublicKey + ) + tx.add(createAtaInst) + } + + const userWSOLAccountInfo = await connection.getAccountInfo(destination) + + const rentExempt = await Token.getMinBalanceRentForExemptAccount(connection) + + const transferLamportsIx = SystemProgram.transfer({ + fromPubkey: payerPublicKey, + toPubkey: source, + lamports: (userWSOLAccountInfo ? 0 : rentExempt) + amount.toNumber(), + }) + + tx.add(transferLamportsIx) + + if (!userWSOLAccountInfo) { + const createUserWSOLAccountIx = Token.createAssociatedTokenAccountInstruction( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + NATIVE_MINT, + source, + payerPublicKey, + payerPublicKey + ) + tx.add(createUserWSOLAccountIx) + } else { + const syncIx = syncNative(source) + tx.add(syncIx) + } + + // Create destination account for pool mint if doesn't exist + destination = + destination ?? (await findAssociatedTokenAccount(payerPublicKey, poolMint)) + !(await connection.getAccountInfo(destination)) && + tx.add( + new CreateAssociatedTokenAccount( + { feePayer: payerPublicKey }, + { + associatedTokenAddress: destination, + tokenMint: poolMint, + } + ) + ) + + tx.add( + new DepositTx( + { feePayer: payerPublicKey }, + { + registryPoolConfig, + registry, + poolMarket, + pool, + source, + destination, + tokenAccount, + poolMint, + poolMarketAuthority, + amount, + } + ) + ) + + return { tx } +} diff --git a/Strategies/protocols/everlend/tools.ts b/Strategies/protocols/everlend/tools.ts new file mode 100644 index 0000000000..cf64c7254e --- /dev/null +++ b/Strategies/protocols/everlend/tools.ts @@ -0,0 +1,313 @@ +import { PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js' +import { + getInstructionDataFromBase64, + ProgramAccount, + Realm, + RpcContext, + serializeInstructionToBase64, + TokenOwnerRecord, +} from '@solana/spl-governance' +import { BN } from '@project-serum/anchor' +import { AssetAccount } from '@utils/uiTypes/assets' +import { ConnectionContext } from '@utils/connection' +import { VotingClient } from '@utils/uiTypes/VotePlugin' +import { + createProposal, + InstructionDataWithHoldUpTime, +} from 'actions/createProposal' +import tokenService from '@utils/services/token' +import { + prepareDepositTx, + prepareWithdrawalRequestTx, + Pool, +} from '@everlend/general-pool' +import axios from 'axios' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + Token, + TOKEN_PROGRAM_ID, +} from '@solana/spl-token' +import { prepareSolDepositTx } from './preparedSolDepositTx' + +const MARKET_MAIN = 'DzGDoJHdzUANM7P7V25t5nxqbvzRcHDmdhY51V6WNiXC' +const MARKET_DEV = '4yC3cUWXQmoyyybfnENpxo33hiNxUNa1YAmmuxz93WAJ' +const REGISTRY_DEV = '6KCHtgSGR2WDE3aqrqSJppHRGVPgy9fHDX5XD8VZgb61' +const REGISTRY_MAIN = 'UaqUGgMvVzUZLthLHC9uuuBzgw5Ldesich94Wu5pMJg' +const ENDPOINT_MAIN = 'https://api.everlend.finance/api/v1/' +const ENDPOINT_DEV = 'https://dev-api.everlend.finance/api/v1/' +export const EVERLEND = 'Everlend' + +async function getAPYs(isDev = false) { + const api = axios.create({ + baseURL: isDev ? ENDPOINT_DEV : ENDPOINT_MAIN, + timeout: 30000, + }) + + return api.get('apy') +} + +async function getStrategies(connection: ConnectionContext) { + const isDev = connection.cluster === 'devnet' + const POOL_MARKET_PUBKEY = new PublicKey(isDev ? MARKET_DEV : MARKET_MAIN) + + try { + const response = await Pool.findMany(connection.current, { + poolMarket: POOL_MARKET_PUBKEY, + }) + + const apys = await getAPYs(isDev) + + const strategies = response.map((pool) => { + const { tokenMint, poolMint } = pool.data + const tokenInfo = tokenService.getTokenInfo(tokenMint.toString()) + const apy = + apys.data.find((apy) => apy.token === tokenInfo?.symbol)?.supply_apy * + 100 ?? 0 + return { + handledMint: tokenMint.toString(), + createProposalFcn: handleEverlendAction, + protocolLogoSrc: '/realms/Everlend/img/logo.png', + protocolName: 'Everlend', + protocolSymbol: 'evd', + isGenericItem: false, + poolMint: poolMint.toString(), + poolPubKey: pool.publicKey.toString(), + strategyDescription: '', + strategyName: 'Deposit', + handledTokenSymbol: tokenInfo?.symbol, + handledTokenImgSrc: tokenInfo?.logoURI, + apy: apy.toFixed(2).concat('%'), + } + }) + + return strategies + } catch (e) { + console.error(e) + } +} + +export async function handleEverlendAction( + rpcContext: RpcContext, + form: { + action: 'Deposit' | 'Withdraw' + title: string + description: string + bnAmount: BN + poolPubKey: string + tokenMint: string + poolMint: string + }, + realm: ProgramAccount, + matchedTreasury: AssetAccount, + tokenOwnerRecord: ProgramAccount, + governingTokenMint: PublicKey, + proposalIndex: number, + isDraft: boolean, + connection: ConnectionContext, + client?: VotingClient +) { + const isSol = matchedTreasury.isSol + const insts: InstructionDataWithHoldUpTime[] = [] + const owner = isSol + ? matchedTreasury!.pubkey + : matchedTreasury!.extensions!.token!.account.owner + const REGISTRY = new PublicKey( + connection.cluster === 'mainnet' ? REGISTRY_MAIN : REGISTRY_DEV + ) + + const ctokenATA = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + new PublicKey(form.tokenMint), + owner, + true + ) + + const liquidityATA = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + new PublicKey(form.poolMint), + owner, + true + ) + + const setupInsts: InstructionDataWithHoldUpTime[] = [] + const cleanupInsts: InstructionDataWithHoldUpTime[] = [] + + if (form.action === 'Deposit') { + const actionTx = await handleEverlendDeposit( + Boolean(isSol), + connection, + owner, + REGISTRY, + form.poolPubKey, + form.bnAmount, + ctokenATA, + liquidityATA + ) + actionTx.instructions.map((instruction) => { + insts.push({ + data: getInstructionDataFromBase64( + serializeInstructionToBase64(instruction) + ), + holdUpTime: matchedTreasury.governance!.account!.config + .minInstructionHoldUpTime, + prerequisiteInstructions: [], + }) + }) + } else if (form.action === 'Withdraw') { + const { withdrawTx, closeIx } = await handleEverlendWithdraw( + Boolean(isSol), + connection, + owner, + REGISTRY, + form.poolPubKey, + form.bnAmount, + liquidityATA, + ctokenATA + ) + + withdrawTx.instructions.map((instruction) => { + insts.push({ + data: getInstructionDataFromBase64( + serializeInstructionToBase64(instruction) + ), + holdUpTime: matchedTreasury.governance!.account!.config + .minInstructionHoldUpTime, + prerequisiteInstructions: [], + chunkSplitByDefault: true, + }) + }) + + if (closeIx) { + cleanupInsts.push({ + data: getInstructionDataFromBase64( + serializeInstructionToBase64(closeIx) + ), + holdUpTime: matchedTreasury.governance!.account!.config + .minInstructionHoldUpTime, + prerequisiteInstructions: [], + chunkSplitByDefault: true, + }) + } + } + + const proposalAddress = await createProposal( + rpcContext, + realm, + matchedTreasury.governance!.pubkey, + tokenOwnerRecord, + form.title, + form.description, + governingTokenMint, + proposalIndex, + [...setupInsts, ...insts, ...cleanupInsts], + isDraft, + client + ) + return proposalAddress +} + +async function handleEverlendDeposit( + isSol: boolean, + connection: ConnectionContext, + owner: PublicKey, + REGISTRY: PublicKey, + poolPubKey: string, + amount: BN, + source: PublicKey, + destination: PublicKey +) { + let actionTx: Transaction + if (isSol) { + const { tx: depositTx } = await prepareSolDepositTx( + { connection: connection.current, payerPublicKey: owner }, + new PublicKey(poolPubKey), + REGISTRY, + amount, + source, + destination + ) + actionTx = depositTx + } else { + const { tx: depositTx } = await prepareDepositTx( + { connection: connection.current, payerPublicKey: owner }, + new PublicKey(poolPubKey), + REGISTRY, + amount, + source + ) + actionTx = depositTx + } + return actionTx +} + +async function handleEverlendWithdraw( + isSol: boolean, + connection: ConnectionContext, + owner: PublicKey, + REGISTRY: PublicKey, + poolPubKey: string, + amount: BN, + source: PublicKey, + destination: PublicKey +) { + const { tx: withdrawslTx } = await prepareWithdrawalRequestTx( + { + connection: connection.current, + payerPublicKey: owner, + }, + new PublicKey(poolPubKey), + REGISTRY, + amount, + source, + isSol ? owner : undefined + ) + const withdrawTx = withdrawslTx + let closeIx: TransactionInstruction | undefined + if (isSol) { + const closeWSOLAccountIx = Token.createCloseAccountInstruction( + TOKEN_PROGRAM_ID, + destination, + owner, + owner, + [] + ) + closeIx = closeWSOLAccountIx + } + + return { + withdrawTx, + closeIx: closeIx ?? null, + } +} + +export async function getEverlendStrategies( + connection: ConnectionContext +): Promise { + const strategies = await getStrategies(connection) + + return strategies +} + +export type CreateEverlendProposal = ( + rpcContext: RpcContext, + form: { + action: 'Deposit' | 'Withdraw' + title: string + description: string + bnAmount: BN + amountFmt: string + poolPubKey: string + tokenMint: string + poolMint: string + }, + realm: ProgramAccount, + matchedTreasury: AssetAccount, + tokenOwnerRecord: ProgramAccount, + governingTokenMint: PublicKey, + proposalIndex: number, + isDraft: boolean, + connection: ConnectionContext, + client?: VotingClient +) => Promise diff --git a/Strategies/store/useStrategiesStore.tsx b/Strategies/store/useStrategiesStore.tsx index cb466bf3df..2e408b0d4d 100644 --- a/Strategies/store/useStrategiesStore.tsx +++ b/Strategies/store/useStrategiesStore.tsx @@ -4,6 +4,7 @@ import { tvl } from 'Strategies/protocols/mango/tools' import { getSolendStrategies } from 'Strategies/protocols/solend' import { TreasuryStrategy } from 'Strategies/types/types' import create, { State } from 'zustand' +import { getEverlendStrategies } from '../protocols/everlend/tools' interface StrategiesStore extends State { strategies: TreasuryStrategy[] @@ -21,9 +22,10 @@ const useStrategiesStore = create((set, _get) => ({ try { const mango = await tvl(Date.now() / 1000, connection) const solend = await getSolendStrategies() + const everlend = await getEverlendStrategies(connection) //add fetch functions for your protocol in promise.all - const strategies: TreasuryStrategy[] = [...solend, ...mango] + const strategies: TreasuryStrategy[] = [...solend, ...mango, ...everlend] set((s) => { s.strategies = strategies diff --git a/components/TreasuryAccount/AccountOverview.tsx b/components/TreasuryAccount/AccountOverview.tsx index 3c683e6870..4790e22546 100644 --- a/components/TreasuryAccount/AccountOverview.tsx +++ b/components/TreasuryAccount/AccountOverview.tsx @@ -43,6 +43,8 @@ import { SOLEND, } from 'Strategies/protocols/solend' import tokenService from '@utils/services/token' +import { EVERLEND } from '../../Strategies/protocols/everlend/tools' +import { findAssociatedTokenAccount } from '@everlend/common' type InvestmentType = TreasuryStrategy & { investedAmount: number @@ -179,6 +181,32 @@ const AccountOverview = () => { return [] } + const handleEverlendAccounts = async (): Promise => { + const everlendStrategy = visibleInvestments.filter( + (strat) => strat.protocolName === EVERLEND + )[0] + + const tokenMintATA = await findAssociatedTokenAccount( + isSol + ? currentAccount!.pubkey + : currentAccount!.extensions!.token!.account.owner, + + new PublicKey( + (everlendStrategy as TreasuryStrategy & { poolMint: string }).poolMint + ) + ) + const tokenMintATABalance = await connection.current.getTokenAccountBalance( + tokenMintATA + ) + + return [ + { + ...everlendStrategy, + investedAmount: Number(tokenMintATABalance.value.uiAmount), + }, + ].filter((strat) => strat.investedAmount !== 0) + } + const loadData = async () => { const requests = [] as Array>> if (visibleInvestments.filter((x) => x.protocolName === MANGO).length) { @@ -188,6 +216,12 @@ const AccountOverview = () => { requests.push(getSlndCTokens()) } + if ( + visibleInvestments.filter((x) => x.protocolName === EVERLEND).length + ) { + requests.push(handleEverlendAccounts()) + } + const results = await Promise.all(requests) setLoading(false) diff --git a/package.json b/package.json index 2f5ed27ebf..5045550d78 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@dialectlabs/react-ui": "0.8.2", "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", + "@everlend/general-pool": "^0.0.19", "@foresight-tmp/foresight-sdk": "^0.1.46", "@friktion-labs/friktion-sdk": "^1.1.118", "@headlessui/react": "^1.6.4", diff --git a/public/realms/Everlend/img/logo.png b/public/realms/Everlend/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2c9dd216e22a645844cfb6f5c781cbac2cf5e06e GIT binary patch literal 33010 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∈169!}EeJ&nEbu{&? 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 Ri9Pst?W+2f>`P`({y!+zRBZqN literal 0 HcmV?d00001 diff --git a/yarn.lock b/yarn.lock index 9ad20efeff..c6c69ceb71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -972,6 +972,29 @@ "@ethersproject/logger" "^5.5.0" hash.js "1.1.7" +"@everlend/common@workspace:^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@everlend/common/-/common-0.0.3.tgz#b18d7e025592a6f863dc84abf563ca2b25df0ac2" + integrity sha512-hj729mHUw250kJNKgdhfK/ZLiUJOSze2IquGyNs6qooMe856Au9E/vi3RlkuCzDMe3nkvvC8mpRzjzRmkwpJWw== + dependencies: + "@solana/spl-token" "^0.1.5" + "@solana/web3.js" "^1.16.1" + borsh "^0.6.0" + bs58 "^4.0.1" + buffer "^6.0.3" + +"@everlend/general-pool@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@everlend/general-pool/-/general-pool-0.0.19.tgz#35c449cee0dfd9f9d3c49f1f3dbbd7cc59927634" + integrity sha512-GoEntJ9fchb4QUd/cLF1unpovlaWZEcEMcMiavClQfM49HR9U3VglUYGhQJ/6uRABnr43qH3YfMTccnZ7y/lvw== + dependencies: + "@everlend/common" "workspace:^0.0.3" + "@solana/spl-token" "^0.1.5" + "@solana/web3.js" "^1.16.1" + borsh "^0.6.0" + bs58 "^4.0.1" + buffer "^6.0.3" + "@fast-csv/format@4.3.5": version "4.3.5" resolved "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz" @@ -2784,7 +2807,7 @@ buffer-layout "^1.2.0" dotenv "10.0.0" -"@solana/spl-token@0.1.8", "@solana/spl-token@^0.1.6", "@solana/spl-token@^0.1.8": +"@solana/spl-token@0.1.8", "@solana/spl-token@^0.1.5", "@solana/spl-token@^0.1.6", "@solana/spl-token@^0.1.8": version "0.1.8" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.1.8.tgz#f06e746341ef8d04165e21fc7f555492a2a0faa6" integrity sha512-LZmYCKcPQDtJgecvWOgT/cnoIQPWjdH+QVyzPcFvyDUiT0DiRjZaam4aqNUyvchLFhzgunv3d9xOoyE34ofdoQ== @@ -2977,6 +3000,28 @@ superstruct "^0.14.2" tweetnacl "^1.0.0" +"@solana/web3.js@^1.16.1": + version "1.44.2" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.44.2.tgz#5303efd94a7f2d6054a1483a4b4db4a26eb2a392" + integrity sha512-DvrJMoKonLuaX0/KyyJXcP/+w+9q8mve4gN3hC2Ptg51K/Gi1/cx6oQN2lbRZb4wYPBd2s2GDAJAJUAwZGsEug== + dependencies: + "@babel/runtime" "^7.12.5" + "@ethersproject/sha2" "^5.5.0" + "@solana/buffer-layout" "^4.0.0" + bigint-buffer "^1.1.5" + bn.js "^5.0.0" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.1" + fast-stable-stringify "^1.0.0" + jayson "^3.4.4" + js-sha3 "^0.8.0" + node-fetch "2" + rpc-websockets "^7.4.2" + secp256k1 "^4.0.2" + superstruct "^0.14.2" + tweetnacl "^1.0.0" + "@solana/web3.js@^1.32.0": version "1.41.4" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.41.4.tgz#595aa29a4a61c181b8c8f5cf0bbef80b4739cfab"