Skip to content

Commit 2aabc86

Browse files
authored
add raydium harvest fees instruction (#177)
* add raydium harvest fees instruction * reduce chunk size
1 parent b70bbfb commit 2aabc86

File tree

8 files changed

+396
-6
lines changed

8 files changed

+396
-6
lines changed

hooks/queries/digitalAssets.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { useConnection } from '@solana/wallet-adapter-react'
66
import { getNativeTreasuryAddress } from '@solana/spl-governance'
77
import queryClient from './queryClient'
88
import { getNetworkFromEndpoint } from '@utils/connection'
9+
import axios from 'axios'
10+
import { BN } from '@coral-xyz/anchor'
911

1012
type Network = 'devnet' | 'mainnet'
1113
const getHeliusEndpoint = (network: Network) => {
@@ -36,6 +38,11 @@ export const digitalAssetsQueryKeys = {
3638
'by Owner',
3739
owner.toString(),
3840
],
41+
byOwnerWithMeta: (network: Network, owner: PublicKey) => [
42+
...digitalAssetsQueryKeys.all(network),
43+
'by Owner with Meta',
44+
owner.toString(),
45+
],
3946
byRealm: (network: Network, realm: PublicKey) => [
4047
...digitalAssetsQueryKeys.all(network),
4148
'by Realm',
@@ -268,6 +275,77 @@ export const useDigitalAssetsByOwner = (owner: undefined | PublicKey) => {
268275
})
269276
}
270277

278+
export const useRaydiumAssetsByOwner = (owner: undefined | PublicKey) => {
279+
const { connection } = useConnection()
280+
const network = getNetworkFromEndpoint(connection.rpcEndpoint) as Network
281+
const enabled = owner !== undefined
282+
const raydiumCreator = new PublicKey("3f7GcQFG397GAaEnv51zR6tsTVihYRydnydDD1cXekxH")
283+
284+
return useQuery({
285+
enabled,
286+
queryKey: owner && digitalAssetsQueryKeys.byOwnerWithMeta(network, owner),
287+
queryFn: async () => {
288+
if (!enabled) throw new Error()
289+
const nfts = await dasByOwnerQueryFn(network, owner)
290+
const raydiumAssets = nfts.filter(
291+
nft =>
292+
nft.creators.length &&
293+
nft.creators[0].address === raydiumCreator.toBase58() &&
294+
nft.creators[0].verified
295+
)
296+
297+
return Promise.all(
298+
raydiumAssets.map(async(x) => {
299+
try {
300+
const uriData = await axios.get(x.content.json_uri)
301+
const mintA = uriData.data.poolInfo?.mintA?.symbol
302+
const mintB = uriData.data.poolInfo?.mintB?.symbol
303+
const poolId = uriData.data.poolInfo?.id
304+
const decimals = uriData.data.poolInfo?.lpMint?.decimals
305+
const lp = uriData.data.positionInfo?.unclaimedFee?.lp
306+
const mintARewardAmount = uriData.data.positionInfo?.unclaimedFee?.amountA
307+
const mintBRewardAmount = uriData.data.positionInfo?.unclaimedFee?.amountB
308+
309+
const lpAmount = decimals && lp ?
310+
new BN(
311+
uriData.data.positionInfo?.unclaimedFee?.lp *
312+
Math.pow(10, decimals)
313+
) :
314+
new BN(0)
315+
316+
const id = x.id
317+
const name = mintA && mintB && id ?
318+
`${mintB}-${mintA} (${id})` :
319+
undefined
320+
321+
return {
322+
name,
323+
poolId,
324+
lpAmount,
325+
mintARewardAmount,
326+
mintBRewardAmount,
327+
mintA,
328+
mintB,
329+
...x
330+
}
331+
} catch {
332+
return {
333+
name: undefined,
334+
poolId: undefined,
335+
lpAmount: new BN(0),
336+
mintARewardAmount: undefined,
337+
mintBRewardAmount: undefined,
338+
mintA: undefined,
339+
mintB: undefined,
340+
...x
341+
}
342+
}
343+
}
344+
))
345+
},
346+
})
347+
}
348+
271349
export const useRealmDigitalAssetsQuery = () => {
272350
const { connection } = useConnection()
273351
const realm = useRealmQuery().data?.result

hooks/useGovernanceAssets.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ export default function useGovernanceAssets() {
204204
name: 'Switchboard',
205205
image: '/img/switchboard.png',
206206
},
207+
[PackageEnum.Raydium]: {
208+
name: 'Raydium',
209+
image: '/img/raydium.png',
210+
},
207211
[PackageEnum.VsrPlugin]: {
208212
name: 'Vsr Plugin',
209213
isVisible:
@@ -820,6 +824,18 @@ export default function useGovernanceAssets() {
820824
packageId: PackageEnum.Switchboard,
821825
},
822826

827+
/*
828+
____ _ _
829+
| _ \ __ _ _ _ __| (_)_ _ _ __ ___
830+
| |_) / _` | | | |/ _` | | | | | '_ ` _ \
831+
| _ < (_| | |_| | (_| | | |_| | | | | | |
832+
|_| \_\__,_|\__, |\__,_|_|\__,_|_| |_| |_|
833+
|___/
834+
*/
835+
[Instructions.CollectPoolFees]: {
836+
name: 'Collect Pool Fees (CPMM)',
837+
packageId: PackageEnum.Raydium,
838+
},
823839
/*
824840
██ ██ ███████ ██████ ██████ ██ ██ ██ ██████ ██ ███ ██
825841
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"@radix-ui/react-toast": "1.0.0",
9494
"@radix-ui/react-toolbar": "1.0.0",
9595
"@radix-ui/react-tooltip": "1.0.0",
96+
"@raydium-io/raydium-sdk-v2": "0.1.127-alpha",
9697
"@realms-today/spl-governance": "0.3.30",
9798
"@sentry/nextjs": "7.81.1",
9899
"@solana-mobile/wallet-adapter-mobile": "2.0.0",
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import React, { useContext, useEffect, useState } from 'react'
2+
import useRealm from '@hooks/useRealm'
3+
import { PublicKey } from '@solana/web3.js'
4+
import {
5+
UiInstruction,
6+
} from '@utils/uiTypes/proposalCreationTypes'
7+
import { NewProposalContext } from '../../../new'
8+
import useGovernanceAssets from '@hooks/useGovernanceAssets'
9+
import { Governance, serializeInstructionToBase64 } from '@solana/spl-governance'
10+
import { ProgramAccount } from '@solana/spl-governance'
11+
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
12+
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
13+
import { DasNftObject, useRaydiumAssetsByOwner } from '@hooks/queries/digitalAssets'
14+
import GovernanceAccountSelect from '../../GovernanceAccountSelect'
15+
import { SplGovernance } from 'governance-idl-sdk'
16+
import Select from '@components/inputs/Select'
17+
import {Raydium} from '@raydium-io/raydium-sdk-v2'
18+
import { BN } from '@coral-xyz/anchor'
19+
20+
interface RaydiumDasNftObject extends DasNftObject {
21+
name: string
22+
poolId: string
23+
lpAmount: BN
24+
mintARewardAmount: string
25+
mintBRewardAmount: string
26+
mintA: string
27+
mintB: string
28+
}
29+
30+
const CollectPoolFees = ({
31+
index,
32+
governance,
33+
}: {
34+
index: number
35+
governance: ProgramAccount<Governance> | null
36+
}) => {
37+
const connection = useLegacyConnectionContext()
38+
const wallet = useWalletOnePointOh()
39+
const { governancesArray } = useGovernanceAssets()
40+
const [selectedNft, setSelectedNft] = useState<RaydiumDasNftObject | null>(null)
41+
42+
const [governedAccount, setGovernedAccount] = useState<
43+
ProgramAccount<Governance> | undefined
44+
>(undefined)
45+
46+
const splGovernance = new SplGovernance(connection.current)
47+
const nativeAddress = governedAccount?.pubkey ?
48+
splGovernance.pda.nativeTreasuryAccount({governanceAccount: governedAccount.pubkey}).publicKey :
49+
undefined
50+
51+
const {data: assets} = useRaydiumAssetsByOwner(nativeAddress)
52+
const raydiumNfts = assets?.filter((asset) => asset.name !== undefined)
53+
54+
const { handleSetInstructions } = useContext(NewProposalContext)
55+
56+
async function getInstruction(): Promise<UiInstruction> {
57+
if (
58+
governedAccount?.account &&
59+
wallet?.publicKey &&
60+
selectedNft
61+
) {
62+
const raydium = await Raydium.load({
63+
connection: connection.current,
64+
owner: nativeAddress,
65+
})
66+
67+
if (selectedNft.lpAmount.isZero()) {
68+
throw new Error('No rewards to claim.')
69+
}
70+
71+
const poolData = await raydium.cpmm.getRpcPoolInfo(selectedNft.poolId)
72+
const poolInfo = await raydium.cpmm.getCpmmPoolKeys(selectedNft.poolId)
73+
74+
const harvestLpFeesIxs = await raydium.cpmm.harvestLockLp({
75+
nftMint: new PublicKey(selectedNft.id),
76+
poolInfo: {
77+
programId: poolInfo.programId,
78+
id: poolInfo.id,
79+
mintA: poolInfo.mintA,
80+
mintB: poolInfo.mintB,
81+
rewardDefaultInfos: [],
82+
rewardDefaultPoolInfos: "Raydium",
83+
price: parseInt(poolData.poolPrice.toString()),
84+
mintAmountA: 0,
85+
mintAmountB: 0,
86+
feeRate: 0,
87+
openTime: poolInfo.openTime,
88+
tvl: 0,
89+
pooltype: [],
90+
farmUpcomingCount: 0,
91+
farmFinishedCount: 0,
92+
farmOngoingCount: 0,
93+
burnPercent: 0,
94+
day: {
95+
volume: 0,
96+
volumeQuote: 0,
97+
volumeFee: 0,
98+
apr: 0,
99+
feeApr: 0,
100+
priceMin: 0,
101+
priceMax: 0,
102+
rewardApr: []
103+
},
104+
week: {
105+
volume: 0,
106+
volumeQuote: 0,
107+
volumeFee: 0,
108+
apr: 0,
109+
feeApr: 0,
110+
priceMin: 0,
111+
priceMax: 0,
112+
rewardApr: []
113+
},
114+
month: {
115+
volume: 0,
116+
volumeQuote: 0,
117+
volumeFee: 0,
118+
apr: 0,
119+
feeApr: 0,
120+
priceMin: 0,
121+
priceMax: 0,
122+
rewardApr: []
123+
},
124+
lpAmount: 0,
125+
lpMint: poolInfo.mintLp,
126+
lpPrice: 0,
127+
type: 'Standard',
128+
config: poolInfo.config
129+
},
130+
lpFeeAmount: selectedNft.lpAmount,
131+
})
132+
133+
const additionalSerializedInstructions = harvestLpFeesIxs.transaction.instructions.map((ix) => serializeInstructionToBase64(ix))
134+
return {
135+
serializedInstruction : '',
136+
additionalSerializedInstructions,
137+
isValid: true,
138+
governance: governedAccount,
139+
chunkBy: 1,
140+
}
141+
} else {
142+
return {
143+
serializedInstruction: '',
144+
isValid: false,
145+
governance: governedAccount,
146+
chunkBy: 1,
147+
}
148+
}
149+
}
150+
151+
useEffect(() => {
152+
handleSetInstructions(
153+
{ governedAccount: governedAccount, getInstruction },
154+
index,
155+
)
156+
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree
157+
}, [selectedNft, governedAccount])
158+
159+
return (
160+
<>
161+
<GovernanceAccountSelect
162+
label="Governance"
163+
governanceAccounts={governancesArray}
164+
onChange={(value) => {
165+
setGovernedAccount(value)
166+
}}
167+
value={governedAccount}
168+
/>
169+
170+
<Select
171+
onChange={(value) => setSelectedNft(value)}
172+
label="Select Pool"
173+
placeholder="Select..."
174+
value={selectedNft?.id}
175+
>
176+
{raydiumNfts?.map((asset) => (
177+
<Select.Option key={asset.id} value={asset}>
178+
{asset.name}
179+
</Select.Option>
180+
))}
181+
</Select>
182+
{selectedNft?.mintARewardAmount && selectedNft.mintBRewardAmount ?
183+
<p>Rewards Available to Claim: {selectedNft.mintARewardAmount} {selectedNft.mintA}, {selectedNft.mintBRewardAmount} {selectedNft.mintB}
184+
</p> :
185+
null
186+
}
187+
</>
188+
)
189+
}
190+
191+
export default CollectPoolFees

pages/dao/[symbol]/proposal/new.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ import SettleToken from './components/instructions/Manifest/SettleToken'
154154
import CancelLimitOrder from './components/instructions/Manifest/CancelLimitOrder'
155155
import WithdrawFees from './components/instructions/Token2022/WithdrawFees'
156156
import SquadsV4RemoveMember from './components/instructions/Squads/SquadsV4RemoveMember'
157+
import CollectPoolFees from './components/instructions/Raydium/CollectPoolFees'
157158

158159
const TITLE_LENGTH_LIMIT = 130
159160
// the true length limit is either at the tx size level, and maybe also the total account size level (I can't remember)
@@ -632,6 +633,7 @@ const New = () => {
632633
[Instructions.SymmetryEditBasket]: SymmetryEditBasket,
633634
[Instructions.SymmetryDeposit]: SymmetryDeposit,
634635
[Instructions.SymmetryWithdraw]: SymmetryWithdraw,
636+
[Instructions.CollectPoolFees]: CollectPoolFees
635637
}),
636638
[governance?.pubkey?.toBase58()],
637639
)

public/img/raydium.png

221 KB
Loading

utils/uiTypes/proposalCreationTypes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { LockupKind } from 'VoteStakeRegistry/tools/types'
99
import { AssetAccount, StakeAccount } from '@utils/uiTypes/assets'
1010
import { RealmInfo } from '@models/registry/api'
1111
import * as PaymentStreaming from '@mean-dao/payment-streaming'
12+
import { DasNftObject } from '@hooks/queries/digitalAssets'
1213

1314
// Alphabetical order
1415
export enum PackageEnum {
@@ -29,6 +30,7 @@ export enum PackageEnum {
2930
Squads,
3031
Switchboard,
3132
VsrPlugin,
33+
Raydium
3234
}
3335

3436
export interface UiInstruction {
@@ -421,6 +423,7 @@ export enum Instructions {
421423
SymmetryDeposit,
422424
SymmetryWithdraw,
423425
TokenWithdrawFees,
426+
CollectPoolFees
424427
}
425428

426429
export interface ComponentInstructionData {

0 commit comments

Comments
 (0)