Skip to content

Commit

Permalink
feat(pyth): use the new pyth sdk (#2445)
Browse files Browse the repository at this point in the history
  • Loading branch information
keyvankhademi authored Sep 25, 2024
1 parent ceea061 commit 9290275
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 100 deletions.
109 changes: 66 additions & 43 deletions VoterWeightPlugins/clients/PythVoterWeightPluginClient.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {Client} from "@solana/governance-program-library";
import {PublicKey, TransactionInstruction} from "@solana/web3.js";
import BN from "bn.js";
import {PythClient, StakeAccount, StakeConnection} from "@pythnetwork/staking";
import {Provider, Wallet} from "@coral-xyz/anchor";
import {Program, Provider, Wallet} from "@coral-xyz/anchor";
import {VoterWeightAction} from "@solana/spl-governance";
import {convertVoterWeightActionToType} from "../lib/utils";
import queryClient from "@hooks/queries/queryClient";
import { getMaxVoterWeightRecordAddress, getVoterWeightRecordAddress, PythStakingClient, StakeAccountPositions } from "@pythnetwork/staking-sdk";

// A wrapper for the PythClient from @pythnetwork/staking, that implements the generic plugin client interface
// A wrapper for the PythClient from @pythnetwork/staking-sdk, that implements the generic plugin client interface
export class PythVoterWeightPluginClient extends Client<any> {
readonly requiresInputVoterWeight = false;
// The pyth plugin does not have a registrar account
Expand All @@ -16,23 +16,36 @@ export class PythVoterWeightPluginClient extends Client<any> {
}

async getMaxVoterWeightRecordPDA() {
const maxVoterWeightPk = (await this.client.program.methods.updateMaxVoterWeight().pubkeys()).maxVoterRecord

if (!maxVoterWeightPk) return null;
const [address, bump] = getMaxVoterWeightRecordAddress();

return {
maxVoterWeightPk,
maxVoterWeightRecordBump: 0 // This is wrong for Pyth - but it doesn't matter as it is not used
maxVoterWeightPk: address,
maxVoterWeightRecordBump: bump,
}
}

async getMaxVoterWeightRecord(realm: PublicKey, mint: PublicKey) {
const {maxVoterWeightPk} = await this.getMaxVoterWeightRecordPDA();
return this.client.stakingProgram.account.maxVoterWeightRecord.fetch(
maxVoterWeightPk,
);
}

async getVoterWeightRecordPDA(realm: PublicKey, mint: PublicKey, voter: PublicKey) {
const { voterWeightAccount } = await this.getUpdateVoterWeightPks([], voter, VoterWeightAction.CastVote, PublicKey.default);
const stakeAccount = await this.getStakeAccount(voter)
const [address, bump] = getVoterWeightRecordAddress(stakeAccount);

return {
voterWeightPk: voterWeightAccount,
voterWeightRecordBump: 0 // This is wrong for Pyth - but it doesn't matter as it is not used
};
voterWeightPk: address,
voterWeightRecordBump: bump,
}
}

async getVoterWeightRecord(realm: PublicKey, mint: PublicKey, walletPk: PublicKey) {
const {voterWeightPk} = await this.getVoterWeightRecordPDA(realm, mint, walletPk);
return this.client.stakingProgram.account.voterWeightRecord.fetch(
voterWeightPk,
);
}

// NO-OP Pyth records are created through the Pyth dApp.
Expand All @@ -45,54 +58,64 @@ export class PythVoterWeightPluginClient extends Client<any> {
return null;
}

private async getStakeAccount(voter: PublicKey): Promise<StakeAccount> {
private async getStakeAccount(voter: PublicKey): Promise<PublicKey> {
return queryClient.fetchQuery({
queryKey: ['pyth getStakeAccount', voter],
queryFn: () => this.client.getMainAccount(voter),
queryKey: ['pyth getStakeAccount', voter.toBase58()],
queryFn: () => this.client.getMainStakeAccount(voter).then(x => x?.stakeAccountPosition),
})
}

private async getUpdateVoterWeightPks(instructions: TransactionInstruction[], voter: PublicKey, action: VoterWeightAction, target?: PublicKey) {
async updateVoterWeightRecord(
voter: PublicKey,
realm: PublicKey,
mint: PublicKey,
action: VoterWeightAction,
inputRecordCallback?: () => Promise<PublicKey>,
target?: PublicKey
) {
const stakeAccount = await this.getStakeAccount(voter)

if (!stakeAccount) throw new Error("Stake account not found for voter " + voter.toString());
return this.client.withUpdateVoterWeight(
instructions,
const ix = await this.client.getUpdateVoterWeightInstruction(
stakeAccount,
{ [convertVoterWeightActionToType(action)]: {} } as any,
target
);
}

async updateVoterWeightRecord(voter: PublicKey, realm: PublicKey, mint: PublicKey, action: VoterWeightAction, inputRecordCallback?: () => Promise<PublicKey>, target?: PublicKey) {
const instructions: TransactionInstruction[] = [];
await this.getUpdateVoterWeightPks(instructions, voter, action, target);

return { pre: instructions };
target,
)

return { pre: [ix] };
}
// NO-OP
async updateMaxVoterWeightRecord(): Promise<TransactionInstruction | null> {
return null;
}
async calculateVoterWeight(voter: PublicKey): Promise<BN | null> {
const stakeAccount = await this.getStakeAccount(voter)

if (stakeAccount) {
return stakeAccount.getVoterWeight(await this.client.getTime()).toBN()
} else {
return new BN(0)
}
const voterWeight = await this.client.getVoterWeight(voter);
return new BN(voterWeight.toString());
}
constructor(program: typeof PythClient.prototype.program, private client: StakeConnection, devnet:boolean) {
super(program, devnet);

constructor(
program: Program<any>,
private client: PythStakingClient
) {
super(program);
}

static async connect(provider: Provider, devnet = false, wallet: Wallet): Promise<PythVoterWeightPluginClient> {
const pythClient = await PythClient.connect(
provider.connection,
wallet
)
static async connect(provider: Provider, programId: PublicKey, wallet: Wallet): Promise<PythVoterWeightPluginClient> {
const pythClient = new PythStakingClient({
connection: provider.connection,
wallet,
})

const dummyProgram = new Program(
{
version: "",
name: 'unrecognised',
accounts: [],
instructions: []
},
programId,
provider
);

return new PythVoterWeightPluginClient(pythClient.program, pythClient, devnet);
return new PythVoterWeightPluginClient(dummyProgram, pythClient);
}
}
2 changes: 1 addition & 1 deletion VoterWeightPlugins/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const loadClient = (
case 'gateway':
return GatewayClient.connect(provider)
case 'pyth':
return PythVoterWeightPluginClient.connect(provider, undefined, signer)
return PythVoterWeightPluginClient.connect(provider, programId, signer)
case 'VSR':
return VsrClient.connect(provider, programId)
case 'HeliumVSR':
Expand Down
6 changes: 4 additions & 2 deletions hooks/PythNetwork/useScalingFactor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { determineVotingPowerType } from "@hooks/queries/governancePower";
import useSelectedRealmPubkey from "@hooks/selectedRealm/useSelectedRealmPubkey";
import { PythClient } from "@pythnetwork/staking";
import { useConnection } from "@solana/wallet-adapter-react";
import { useAsync } from "react-async-hook";
import { useQuery } from "@tanstack/react-query";
import { PythStakingClient } from "@pythnetwork/staking-sdk";

/**
* Returns undefined for everything except the Pyth DAO
Expand All @@ -20,7 +20,9 @@ export default function usePythScalingFactor(): number | undefined {

const { data: scalingFactor } = useQuery(["pyth-scaling-factor"],
async (): Promise<number> => {
const pythClient = await PythClient.connect(connection, {} as NodeWallet)
const pythClient = new PythStakingClient({
connection,
})
return pythClient.getScalingFactor()
}, { enabled: plugin == "pyth" })

Expand Down
15 changes: 5 additions & 10 deletions hooks/queries/governancePower.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { StakeConnection } from "@parcl-oss/staking"
import {
LegacyVoterWeightAdapter,
} from '@models/voteWeights'
import { PythClient } from '@pythnetwork/staking'
import { PythStakingClient } from "@pythnetwork/staking-sdk";
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { findPluginName } from '@constants/plugins'
import { useRealmVoterWeightPlugins } from '@hooks/useRealmVoterWeightPlugins'
Expand Down Expand Up @@ -102,17 +102,12 @@ export const getPythGovPower = async (
): Promise<BN> => {
if (!user) return new BN(0)

const pythClient = await PythClient.connect(
const pythClient = new PythStakingClient({
connection,
new NodeWallet(new Keypair())
)
const stakeAccount = await pythClient.getMainAccount(user)
})
const voterWeight = await pythClient.getVoterWeight(user)

if (stakeAccount) {
return stakeAccount.getVoterWeight(await pythClient.getTime()).toBN()
} else {
return new BN(0)
}
return new BN(voterWeight.toString())
}

export const getParclGovPower = async (
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
"@project-serum/serum": "0.13.65",
"@project-serum/sol-wallet-adapter": "0.2.6",
"@pythnetwork/client": "2.17.0",
"@pythnetwork/staking": "2.3.1",
"@pythnetwork/staking-sdk": "0.0.2",
"@pythnetwork/staking-wasm": "0.3.5",
"@radix-ui/react-accordion": "1.0.0",
"@radix-ui/react-aspect-ratio": "1.0.0",
"@radix-ui/react-dialog": "1.0.0",
Expand Down Expand Up @@ -228,6 +229,7 @@
"@types/bn.js": "5.1.0",
"@project-serum/sol-wallet-adapter": "0.2.6",
"@project-serum/serum": "0.13.65",
"@pythnetwork/staking-sdk/@coral-xyz/anchor": "0.30.1",
"@coral-xyz/anchor": "0.29.0",
"@coral-xyz/borsh": "0.27.0",
"bignumber.js": "9.0.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { NewProposalContext } from '../../../new'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import { PublicKey } from '@solana/web3.js'
import { AssetAccount } from '@utils/uiTypes/assets'
import { StakeConnection, STAKING_ADDRESS } from '@pythnetwork/staking'
import { Wallet } from '@coral-xyz/anchor'
import { PythStakingClient } from '@pythnetwork/staking-sdk'

export interface PythRecoverAccountForm {
governedAccount: AssetAccount | null
Expand Down Expand Up @@ -52,14 +51,12 @@ const PythRecoverAccount = ({
form.governedAccount?.governance?.account &&
wallet?.publicKey
) {
const stakeConnection = await StakeConnection.createStakeConnection(
connection.current,
{} as Wallet,
STAKING_ADDRESS
)
const pythClient = new PythStakingClient({
connection: connection.current,
})

const stakeAccountPublicKey = new PublicKey(form.stakeAccount)
const instruction = await stakeConnection.buildRecoverAccountInstruction(
const instruction = await pythClient.getRecoverAccountInstruction(
stakeAccountPublicKey,
form.governedAccount.governance.pubkey
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { NewProposalContext } from '../../../new'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import { AssetAccount } from '@utils/uiTypes/assets'
import { StakeConnection, STAKING_ADDRESS } from '@pythnetwork/staking'
import { Wallet } from '@coral-xyz/anchor'
import { PythStakingClient, getConfigAddress } from '@pythnetwork/staking-sdk'

export interface PythUpdatePoolAuthorityForm {
governedAccount: AssetAccount | null
Expand Down Expand Up @@ -54,19 +53,19 @@ const PythUpdatePoolAuthority = ({
form.governedAccount?.governance?.account &&
wallet?.publicKey
) {
const stakeConnection = await StakeConnection.createStakeConnection(
connection.current,
{} as Wallet,
STAKING_ADDRESS
)
const pythClient = new PythStakingClient({
connection: connection.current,
})

const [configAddress, _] = getConfigAddress();

const poolAuthorityPublicKey = new PublicKey(form.poolAuthority)
const instruction : TransactionInstruction = {
keys: [
{pubkey : form.governedAccount.governance.pubkey, isSigner: true, isWritable: false},
{pubkey : stakeConnection.configAddress, isSigner: false, isWritable: true},
{pubkey : configAddress, isSigner: false, isWritable: true},
],
programId : stakeConnection.program.programId,
programId : pythClient.stakingProgram.programId,
data : Buffer.concat([INSTRUCTION_DISCRIMINATOR, poolAuthorityPublicKey.toBuffer()])
}

Expand Down
Loading

0 comments on commit 9290275

Please sign in to comment.