Skip to content
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
10 changes: 4 additions & 6 deletions governance/pyth_staking_sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
{
"name": "@pythnetwork/staking-sdk",
"version": "0.0.0",
"version": "0.0.1",
"description": "Pyth staking SDK",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"main": "src/index.ts",
"types": "src/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc && node scripts/update-package-json.js",
"build": "tsc && node scripts/update-package-json.mjs",
"test": "pnpm run test:format && pnpm run test:lint && pnpm run test:integration && pnpm run test:types",
"fix": "pnpm fix:lint && pnpm fix:format",
"fix:format": "prettier --write .",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ const distPackageJsonPath = path.join(__dirname, "..", "dist", "package.json");

const packageJson = JSON.parse(fs.readFileSync(distPackageJsonPath, "utf8"));

packageJson.exports = {
".": "./src/index.js",
};
packageJson.main = "src/index.js";

fs.writeFileSync(distPackageJsonPath, JSON.stringify(packageJson, null, 2));
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 @@ -9,6 +9,8 @@ export const ONE_YEAR_IN_SECONDS = 365n * ONE_DAY_IN_SECONDS;

export const EPOCH_DURATION = ONE_WEEK_IN_SECONDS;

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

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

Expand Down
23 changes: 23 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,26 @@ 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,
);
};

export const getMaxVoterWeightRecordAddress = () => {
return PublicKey.findProgramAddressSync(
[Buffer.from("max_voter")],
STAKING_PROGRAM_ADDRESS,
);
};
122 changes: 121 additions & 1 deletion governance/pyth_staking_sdk/src/pyth-staking-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import {
} from "@solana/web3.js";

import {
FRACTION_PRECISION_N,
GOVERNANCE_ADDRESS,
MAX_VOTER_WEIGHT,
FRACTION_PRECISION_N,
ONE_YEAR_IN_SECONDS,
POSITIONS_ACCOUNT_SIZE,
} from "./constants";
Expand All @@ -36,13 +37,16 @@ import {
getPoolConfigAddress,
getStakeAccountCustodyAddress,
getStakeAccountMetadataAddress,
getTargetAccountAddress,
} from "./pdas";
import {
PositionState,
type GlobalConfig,
type PoolConfig,
type PoolDataAccount,
type StakeAccountPositions,
type TargetAccount,
type VoterWeightAction,
type VestingSchedule,
} from "./types";
import { convertBigIntToBN, convertBNToBigInt } from "./utils/bn";
Expand All @@ -51,6 +55,7 @@ import { extractPublisherData } from "./utils/pool";
import {
deserializeStakeAccountPositions,
getPositionState,
getVotingTokenAmount,
} from "./utils/position";
import { sendTransaction } from "./utils/transaction";
import { getUnlockSchedule } from "./utils/vesting";
Expand Down Expand Up @@ -750,6 +755,121 @@ export class PythStakingClient {
);
}

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) / Number(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(owner?: PublicKey) {
const stakeAccountPositions = await this.getAllStakeAccountPositions(owner);
const currentEpoch = await getCurrentEpoch(this.connection);

const stakeAccountVotingTokens = await Promise.all(
stakeAccountPositions.map(async (position) => {
const stakeAccountPositionsData =
await this.getStakeAccountPositions(position);
return {
stakeAccountPosition: position,
votingTokens: getVotingTokenAmount(
stakeAccountPositionsData,
currentEpoch,
),
};
}),
);

let mainAccount = stakeAccountVotingTokens[0];

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

for (let i = 1; i < stakeAccountVotingTokens.length; i++) {
const currentAccount = stakeAccountVotingTokens[i];
if (
currentAccount !== undefined &&
currentAccount.votingTokens > mainAccount.votingTokens
) {
mainAccount = currentAccount;
}
}

return mainAccount;
}

public async getVoterWeight(owner?: PublicKey) {
const mainAccount = await this.getMainStakeAccount(owner);

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

const targetAccount = await this.getTargetAccount();

return (mainAccount.votingTokens * MAX_VOTER_WEIGHT) / targetAccount.locked;
}

public async getPythTokenMint(): Promise<Mint> {
const globalConfig = await this.getGlobalConfig();
return getMint(this.connection, globalConfig.pythTokenMint);
Expand Down
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 = {
type: "fullyUnlocked" | "periodicUnlockingAfterListing" | "periodicUnlocking";
schedule: {
Expand Down
19 changes: 19 additions & 0 deletions governance/pyth_staking_sdk/src/utils/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,22 @@ export const deserializeStakeAccountPositions = (
},
};
};

export const getVotingTokenAmount = (
stakeAccountPositions: StakeAccountPositions,
epoch: bigint,
) => {
const positions = stakeAccountPositions.data.positions;
const votingPositions = positions
.filter((p) => p.targetWithParameters.voting)
.filter((p) =>
[PositionState.LOCKED, PositionState.PREUNLOCKING].includes(
getPositionState(p, epoch),
),
);
const totalVotingTokenAmount = votingPositions.reduce(
(sum, p) => sum + p.amount,
0n,
);
return totalVotingTokenAmount;
};
5 changes: 3 additions & 2 deletions governance/pyth_staking_sdk/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
"baseUrl": "./",
"noEmit": false,
"target": "ESNext",
"module": "ESNext",
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": true,
"composite": true,
"declarationMap": true,
"esModuleInterop": true
"esModuleInterop": true,
"verbatimModuleSyntax": false
}
}
Loading