Skip to content

Commit

Permalink
feat(staking): add governance ui methods (#1937)
Browse files Browse the repository at this point in the history
* feat(staking): add scaling factor

* add

* fix

* fix

* add more

* fix

* wip

* fix

* fix
  • Loading branch information
keyvankhademi authored Sep 24, 2024
1 parent 9a6e7d1 commit c987d49
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 12 deletions.
File renamed without changes.
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();
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
}
}

0 comments on commit c987d49

Please sign in to comment.