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

Publish #1963

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
13 changes: 7 additions & 6 deletions governance/pyth_staking_sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
{
"name": "@pythnetwork/staking-sdk",
"version": "0.0.0",
"version": "0.0.1",
"description": "Pyth staking SDK",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"publishConfig": {
"access": "public"
},
"files": [
"dist/**/*"
],
"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
}
}
Loading