Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pyth): use the new pyth sdk #2445

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading