Skip to content

Commit c987d49

Browse files
feat(staking): add governance ui methods (#1937)
* feat(staking): add scaling factor * add * fix * fix * add more * fix * wip * fix * fix
1 parent 9a6e7d1 commit c987d49

File tree

11 files changed

+178
-12
lines changed

11 files changed

+178
-12
lines changed

governance/pyth_staking_sdk/package.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
{
22
"name": "@pythnetwork/staking-sdk",
3-
"version": "0.0.0",
3+
"version": "0.0.1",
44
"description": "Pyth staking SDK",
5-
"type": "module",
6-
"exports": {
7-
".": "./src/index.ts"
8-
},
5+
"main": "src/index.ts",
6+
"types": "src/index.d.ts",
97
"publishConfig": {
108
"access": "public"
119
},
1210
"scripts": {
13-
"build": "tsc && node scripts/update-package-json.js",
11+
"build": "tsc && node scripts/update-package-json.mjs",
1412
"test": "pnpm run test:format && pnpm run test:lint && pnpm run test:integration && pnpm run test:types",
1513
"fix": "pnpm fix:lint && pnpm fix:format",
1614
"fix:format": "prettier --write .",

governance/pyth_staking_sdk/scripts/update-package-json.js renamed to governance/pyth_staking_sdk/scripts/update-package-json.mjs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ const distPackageJsonPath = path.join(__dirname, "..", "dist", "package.json");
1515

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

18-
packageJson.exports = {
19-
".": "./src/index.js",
20-
};
18+
packageJson.main = "src/index.js";
2119

2220
fs.writeFileSync(distPackageJsonPath, JSON.stringify(packageJson, null, 2));

governance/pyth_staking_sdk/src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export const ONE_YEAR_IN_SECONDS = 365n * ONE_DAY_IN_SECONDS;
99

1010
export const EPOCH_DURATION = ONE_WEEK_IN_SECONDS;
1111

12+
export const MAX_VOTER_WEIGHT = 10_000_000_000_000_000n; // 10 Billion with 6 decimals
13+
1214
export const FRACTION_PRECISION = 1_000_000;
1315
export const FRACTION_PRECISION_N = 1_000_000n;
1416

governance/pyth_staking_sdk/src/pdas.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,26 @@ export const getDelegationRecordAddress = (
5050
INTEGRITY_POOL_PROGRAM_ADDRESS,
5151
)[0];
5252
};
53+
54+
export const getTargetAccountAddress = () => {
55+
return PublicKey.findProgramAddressSync(
56+
[Buffer.from("target"), Buffer.from("voting")],
57+
STAKING_PROGRAM_ADDRESS,
58+
)[0];
59+
};
60+
61+
export const getVoterWeightRecordAddress = (
62+
stakeAccountPositions: PublicKey,
63+
) => {
64+
return PublicKey.findProgramAddressSync(
65+
[Buffer.from("voter_weight"), stakeAccountPositions.toBuffer()],
66+
STAKING_PROGRAM_ADDRESS,
67+
);
68+
};
69+
70+
export const getMaxVoterWeightRecordAddress = () => {
71+
return PublicKey.findProgramAddressSync(
72+
[Buffer.from("max_voter")],
73+
STAKING_PROGRAM_ADDRESS,
74+
);
75+
};

governance/pyth_staking_sdk/src/pyth-staking-client.ts

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import {
2525
} from "@solana/web3.js";
2626

2727
import {
28-
FRACTION_PRECISION_N,
2928
GOVERNANCE_ADDRESS,
29+
MAX_VOTER_WEIGHT,
30+
FRACTION_PRECISION_N,
3031
ONE_YEAR_IN_SECONDS,
3132
POSITIONS_ACCOUNT_SIZE,
3233
} from "./constants";
@@ -36,13 +37,16 @@ import {
3637
getPoolConfigAddress,
3738
getStakeAccountCustodyAddress,
3839
getStakeAccountMetadataAddress,
40+
getTargetAccountAddress,
3941
} from "./pdas";
4042
import {
4143
PositionState,
4244
type GlobalConfig,
4345
type PoolConfig,
4446
type PoolDataAccount,
4547
type StakeAccountPositions,
48+
type TargetAccount,
49+
type VoterWeightAction,
4650
type VestingSchedule,
4751
} from "./types";
4852
import { convertBigIntToBN, convertBNToBigInt } from "./utils/bn";
@@ -51,6 +55,7 @@ import { extractPublisherData } from "./utils/pool";
5155
import {
5256
deserializeStakeAccountPositions,
5357
getPositionState,
58+
getVotingTokenAmount,
5459
} from "./utils/position";
5560
import { sendTransaction } from "./utils/transaction";
5661
import { getUnlockSchedule } from "./utils/vesting";
@@ -750,6 +755,121 @@ export class PythStakingClient {
750755
);
751756
}
752757

758+
public async getTargetAccount(): Promise<TargetAccount> {
759+
const targetAccount =
760+
await this.stakingProgram.account.targetMetadata.fetch(
761+
getTargetAccountAddress(),
762+
);
763+
return convertBNToBigInt(targetAccount);
764+
}
765+
766+
/**
767+
* This returns the current scaling factor between staked tokens and realms voter weight.
768+
* The formula is n_staked_tokens = scaling_factor * n_voter_weight
769+
*/
770+
public async getScalingFactor(): Promise<number> {
771+
const targetAccount = await this.getTargetAccount();
772+
return Number(targetAccount.locked) / Number(MAX_VOTER_WEIGHT);
773+
}
774+
775+
public async getRecoverAccountInstruction(
776+
stakeAccountPositions: PublicKey,
777+
governanceAuthority: PublicKey,
778+
): Promise<TransactionInstruction> {
779+
return this.stakingProgram.methods
780+
.recoverAccount()
781+
.accountsPartial({
782+
stakeAccountPositions,
783+
governanceAuthority,
784+
})
785+
.instruction();
786+
}
787+
788+
public async getUpdatePoolAuthorityInstruction(
789+
governanceAuthority: PublicKey,
790+
poolAuthority: PublicKey,
791+
): Promise<TransactionInstruction> {
792+
return this.stakingProgram.methods
793+
.updatePoolAuthority(poolAuthority)
794+
.accounts({
795+
governanceAuthority,
796+
})
797+
.instruction();
798+
}
799+
800+
public async getUpdateVoterWeightInstruction(
801+
stakeAccountPositions: PublicKey,
802+
action: VoterWeightAction,
803+
remainingAccount?: PublicKey,
804+
) {
805+
return this.stakingProgram.methods
806+
.updateVoterWeight(action)
807+
.accounts({
808+
stakeAccountPositions,
809+
})
810+
.remainingAccounts(
811+
remainingAccount
812+
? [
813+
{
814+
pubkey: remainingAccount,
815+
isWritable: false,
816+
isSigner: false,
817+
},
818+
]
819+
: [],
820+
)
821+
.instruction();
822+
}
823+
824+
public async getMainStakeAccount(owner?: PublicKey) {
825+
const stakeAccountPositions = await this.getAllStakeAccountPositions(owner);
826+
const currentEpoch = await getCurrentEpoch(this.connection);
827+
828+
const stakeAccountVotingTokens = await Promise.all(
829+
stakeAccountPositions.map(async (position) => {
830+
const stakeAccountPositionsData =
831+
await this.getStakeAccountPositions(position);
832+
return {
833+
stakeAccountPosition: position,
834+
votingTokens: getVotingTokenAmount(
835+
stakeAccountPositionsData,
836+
currentEpoch,
837+
),
838+
};
839+
}),
840+
);
841+
842+
let mainAccount = stakeAccountVotingTokens[0];
843+
844+
if (mainAccount === undefined) {
845+
return;
846+
}
847+
848+
for (let i = 1; i < stakeAccountVotingTokens.length; i++) {
849+
const currentAccount = stakeAccountVotingTokens[i];
850+
if (
851+
currentAccount !== undefined &&
852+
currentAccount.votingTokens > mainAccount.votingTokens
853+
) {
854+
mainAccount = currentAccount;
855+
}
856+
}
857+
858+
return mainAccount;
859+
}
860+
861+
public async getVoterWeight(owner?: PublicKey) {
862+
const mainAccount = await this.getMainStakeAccount(owner);
863+
864+
if (mainAccount === undefined) {
865+
return 0;
866+
}
867+
868+
const targetAccount = await this.getTargetAccount();
869+
870+
return (mainAccount.votingTokens * MAX_VOTER_WEIGHT) / targetAccount.locked;
871+
}
872+
753873
public async getPythTokenMint(): Promise<Mint> {
754874
const globalConfig = await this.getGlobalConfig();
755875
return getMint(this.connection, globalConfig.pythTokenMint);

governance/pyth_staking_sdk/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export type TargetWithParameters = IdlTypes<Staking>["targetWithParameters"];
3535
export type VestingScheduleAnchor = IdlTypes<Staking>["vestingSchedule"];
3636
export type VestingSchedule = ConvertBNToBigInt<VestingScheduleAnchor>;
3737

38+
export type TargetAccountAnchor = IdlAccounts<Staking>["targetMetadata"];
39+
export type TargetAccount = ConvertBNToBigInt<TargetAccountAnchor>;
40+
41+
export type VoterWeightAction = IdlTypes<Staking>["voterWeightAction"];
42+
3843
export type UnlockSchedule = {
3944
type: "fullyUnlocked" | "periodicUnlockingAfterListing" | "periodicUnlocking";
4045
schedule: {

governance/pyth_staking_sdk/src/utils/position.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,22 @@ export const deserializeStakeAccountPositions = (
9292
},
9393
};
9494
};
95+
96+
export const getVotingTokenAmount = (
97+
stakeAccountPositions: StakeAccountPositions,
98+
epoch: bigint,
99+
) => {
100+
const positions = stakeAccountPositions.data.positions;
101+
const votingPositions = positions
102+
.filter((p) => p.targetWithParameters.voting)
103+
.filter((p) =>
104+
[PositionState.LOCKED, PositionState.PREUNLOCKING].includes(
105+
getPositionState(p, epoch),
106+
),
107+
);
108+
const totalVotingTokenAmount = votingPositions.reduce(
109+
(sum, p) => sum + p.amount,
110+
0n,
111+
);
112+
return totalVotingTokenAmount;
113+
};

governance/pyth_staking_sdk/tsconfig.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
"baseUrl": "./",
88
"noEmit": false,
99
"target": "ESNext",
10-
"module": "ESNext",
10+
"module": "CommonJS",
1111
"moduleResolution": "Node",
1212
"declaration": true,
1313
"composite": true,
1414
"declarationMap": true,
15-
"esModuleInterop": true
15+
"esModuleInterop": true,
16+
"verbatimModuleSyntax": false
1617
}
1718
}

0 commit comments

Comments
 (0)