Skip to content
2 changes: 2 additions & 0 deletions governance/pyth_staking_sdk/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const ONE_WEEK_IN_SECONDS = 7n * ONE_DAY_IN_SECONDS;

export const EPOCH_DURATION = ONE_WEEK_IN_SECONDS;

export const MAX_VOTER_WEIGHT = 10_000_000_000_000_000; // 10 Billion with 6 decimals

export const FRACTION_PRECISION = 1_000_000;
export const FRACTION_PRECISION_N = 1_000_000n;

Expand Down
25 changes: 25 additions & 0 deletions governance/pyth_staking_sdk/src/pdas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,28 @@ export const getDelegationRecordAddress = (
INTEGRITY_POOL_PROGRAM_ADDRESS,
)[0];
};

export const getTargetAccountAddress = () => {
return PublicKey.findProgramAddressSync(
[Buffer.from("target"), Buffer.from("voting")],
STAKING_PROGRAM_ADDRESS,
)[0];
};

export const getVoterWeightRecordAddress = (
stakeAccountPositions: PublicKey,
) => {
return PublicKey.findProgramAddressSync(
[Buffer.from("voter_weight"), stakeAccountPositions.toBuffer()],
STAKING_PROGRAM_ADDRESS,
)[0];
};

export const getMaxVoterWeightRecordAddress = (
stakeAccountPositions: PublicKey,
) => {
return PublicKey.findProgramAddressSync(
[Buffer.from("max_voter"), stakeAccountPositions.toBuffer()],
STAKING_PROGRAM_ADDRESS,
)[0];
};
117 changes: 116 additions & 1 deletion governance/pyth_staking_sdk/src/pyth-staking-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,27 @@ import {
TransactionInstruction,
} from "@solana/web3.js";

import { GOVERNANCE_ADDRESS, POSITIONS_ACCOUNT_SIZE } from "./constants";
import {
GOVERNANCE_ADDRESS,
MAX_VOTER_WEIGHT,
POSITIONS_ACCOUNT_SIZE,
} from "./constants";
import {
getConfigAddress,
getDelegationRecordAddress,
getPoolConfigAddress,
getStakeAccountCustodyAddress,
getStakeAccountMetadataAddress,
getTargetAccountAddress,
} from "./pdas";
import {
PositionState,
type GlobalConfig,
type PoolConfig,
type PoolDataAccount,
type StakeAccountPositions,
type TargetAccount,
type VoterWeightAction,
} from "./types";
import { convertBigIntToBN, convertBNToBigInt } from "./utils/bn";
import { epochToDate, getCurrentEpoch } from "./utils/clock";
Expand Down Expand Up @@ -687,4 +694,112 @@ export class PythStakingClient {
undefined,
);
}

public async getTargetAccount(): Promise<TargetAccount> {
const targetAccount =
await this.stakingProgram.account.targetMetadata.fetch(
getTargetAccountAddress(),
);
return convertBNToBigInt(targetAccount);
}

/**
* This returns the current scaling factor between staked tokens and realms voter weight.
* The formula is n_staked_tokens = scaling_factor * n_voter_weight
*/
public async getScalingFactor(): Promise<number> {
const targetAccount = await this.getTargetAccount();
Copy link
Contributor

@guibescos guibescos Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this is only valid if someone has cranked the target account (happens whenever someone stakes to unstakes, or whenever someome slashes). I generally think this approach seems fine

return Number(targetAccount.locked) / MAX_VOTER_WEIGHT;
}

public async getRecoverAccountInstruction(
stakeAccountPositions: PublicKey,
governanceAuthority: PublicKey,
): Promise<TransactionInstruction> {
return this.stakingProgram.methods
.recoverAccount()
.accountsPartial({
stakeAccountPositions,
governanceAuthority,
})
.instruction();
}

public async getUpdatePoolAuthorityInstruction(
governanceAuthority: PublicKey,
poolAuthority: PublicKey,
): Promise<TransactionInstruction> {
return this.stakingProgram.methods
.updatePoolAuthority(poolAuthority)
.accounts({
governanceAuthority,
})
.instruction();
}

public async getUpdateVoterWeightInstruction(
stakeAccountPositions: PublicKey,
action: VoterWeightAction,
remainingAccount?: PublicKey,
) {
return this.stakingProgram.methods
.updateVoterWeight(action)
.accounts({
stakeAccountPositions,
})
.remainingAccounts(
remainingAccount
? [
{
pubkey: remainingAccount,
isWritable: false,
isSigner: false,
},
]
: [],
)
.instruction();
}

public async getMainStakeAccount() {
const stakeAccountPositions = await this.getAllStakeAccountPositions();
const stakeAccountBalances = await Promise.all(
stakeAccountPositions.map(async (position) => {
const stakeAccountCustody = await this.getStakeAccountCustody(position);
return {
stakeAccountPosition: position,
balance: stakeAccountCustody.amount,
};
}),
);

let mainAccount = stakeAccountBalances[0];

if (mainAccount === undefined) {
return;
}

for (let i = 1; i < stakeAccountBalances.length; i++) {
const currentAccount = stakeAccountBalances[i];
if (
currentAccount !== undefined &&
currentAccount.balance > mainAccount.balance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort by voterWeight not by balance

) {
mainAccount = currentAccount;
}
}

return mainAccount;
}

public async getVoterWeight() {
const mainAccount = await this.getMainStakeAccount();

if (mainAccount === undefined) {
return 0;
}

const scalingFactor = await this.getScalingFactor();
return Number(mainAccount.balance) / scalingFactor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to be the voter weight, that is the sum of all positions that have governance target and are active

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right!

}
}
5 changes: 5 additions & 0 deletions governance/pyth_staking_sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export type TargetWithParameters = IdlTypes<Staking>["targetWithParameters"];
export type VestingScheduleAnchor = IdlTypes<Staking>["vestingSchedule"];
export type VestingSchedule = ConvertBNToBigInt<VestingScheduleAnchor>;

export type TargetAccountAnchor = IdlAccounts<Staking>["targetMetadata"];
export type TargetAccount = ConvertBNToBigInt<TargetAccountAnchor>;

export type VoterWeightAction = IdlTypes<Staking>["voterWeightAction"];

export type UnlockSchedule = {
date: Date;
amount: bigint;
Expand Down
Loading