Skip to content

Commit

Permalink
Add supply
Browse files Browse the repository at this point in the history
  • Loading branch information
guibescos committed Dec 18, 2023
1 parent c674b26 commit 4ad6531
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 14 deletions.
2 changes: 1 addition & 1 deletion frontend/pages/api/v1/all_locked_accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function hasStandardLockup(
)
)
}
async function getAllStakeAccounts(connection: Connection) {
export async function getAllStakeAccounts(connection: Connection) {
const response = await connection.getProgramAccounts(STAKING_ADDRESS, {
encoding: 'base64',
filters: [
Expand Down
150 changes: 150 additions & 0 deletions frontend/pages/api/v1/cmc/supply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { PythBalance } from '@pythnetwork/staking/app/pythBalance'
import BN from 'bn.js'
import { STAKING_ADDRESS } from '@pythnetwork/staking/app/constants'
import { PYTH_TOKEN } from '@pythnetwork/staking/app/deploy/mainnet_beta'
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import { Program, AnchorProvider, IdlAccounts } from '@coral-xyz/anchor'
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { Staking } from '@pythnetwork/staking/lib/target/types/staking'
import idl from '@pythnetwork/staking/target/idl/staking.json'
import { splTokenProgram } from '@coral-xyz/spl-token'
import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
import {
getConfig,
getCustodyAccountAddress,
getMetadataAccountAddress,
} from './../locked_accounts'
import { getAllStakeAccounts } from '../all_locked_accounts'

const connection = new Connection(process.env.BACKEND_ENDPOINT!)
const provider = new AnchorProvider(
connection,
new NodeWallet(new Keypair()),
{}
)
const stakingProgram = new Program<Staking>(
idl as Staking,
STAKING_ADDRESS,
provider
)
const tokenProgram = splTokenProgram({
programId: TOKEN_PROGRAM_ID,
provider: provider as any,
})

export default async function handlerSupply(
req: NextApiRequest,
res: NextApiResponse
) {
const { q } = req.query

if (q === 'totalSupply') {
res.setHeader('Cache-Control', 'max-age=0, s-maxage=3600')
res.status(200).send((await getTotalSupply(tokenProgram)).toString(false))
} else if (q === 'circulatingSupply') {
const configAccountData = await getConfig(stakingProgram)
const allStakeAccounts = await getAllStakeAccounts(connection)

const allMetadataAccountAddresses = allStakeAccounts.map((account) =>
getMetadataAccountAddress(account)
)
const allCustodyAccountAddresses = allStakeAccounts.map((account) =>
getCustodyAccountAddress(account)
)

const allMetadataAccounts =
await stakingProgram.account.stakeAccountMetadataV2.fetchMultiple(
allMetadataAccountAddresses
)
const allCustodyAccounts = await tokenProgram.account.account.fetchMultiple(
allCustodyAccountAddresses
)

const lockedCustodyAccounts = allCustodyAccounts.map((data, index) => {
return { lock: allMetadataAccounts[index], amount: data?.amount }
})

const totalLockedAmount = lockedCustodyAccounts.reduce((total, account) => {
return total.add(
account.amount && account.lock
? new PythBalance(account.amount).min(
getCurrentlyLockedAmount(account.lock, configAccountData)
)
: PythBalance.zero()
)
}, PythBalance.zero())

res.setHeader('Cache-Control', 'max-age=0, s-maxage=3600')
res
.status(200)
.send(
(await getTotalSupply(tokenProgram))
.sub(totalLockedAmount)
.toString(false)
)
} else {
res.status(400).send({
error:
"The 'q' query parameter must be one of 'totalSupply' or 'circulatingSupply'.",
})
}
}

function getCurrentlyLockedAmount(
metadataAccountData: IdlAccounts<Staking>['stakeAccountMetadataV2'],
configAccountData: IdlAccounts<Staking>['globalConfig']
): PythBalance {
const lock = metadataAccountData.lock
const listTime = configAccountData.pythTokenListTime
if (lock.fullyVested) {
return PythBalance.zero()
} else if (lock.periodicVestingAfterListing) {
if (!listTime) {
return new PythBalance(lock.periodicVestingAfterListing.initialBalance)
} else {
return getCurrentlyLockedAmountPeriodic(
listTime,
lock.periodicVestingAfterListing.periodDuration,
lock.periodicVestingAfterListing.numPeriods,
lock.periodicVestingAfterListing.initialBalance
)
}
} else if (lock.periodicVesting) {
return getCurrentlyLockedAmountPeriodic(
lock.periodicVesting.startDate,
lock.periodicVesting.periodDuration,
lock.periodicVesting.numPeriods,
lock.periodicVesting.initialBalance
)
} else {
throw new Error('Should be unreachable')
}
}

function getCurrentlyLockedAmountPeriodic(
startDate: BN,
periodDuration: BN,
numPeriods: BN,
initialBalance: BN
): PythBalance {
const currentTimestamp = new BN(Math.floor(Date.now() / 1000))
if (currentTimestamp.lte(startDate)) {
return new PythBalance(initialBalance)
} else {
const periodsElapsed = startDate.sub(currentTimestamp).div(periodDuration)
if (periodsElapsed.gte(numPeriods)) {
return PythBalance.zero()
} else {
const remainingPeriods = numPeriods.sub(periodsElapsed)
return new PythBalance(
remainingPeriods.mul(initialBalance).div(numPeriods)
)
}
}
}

async function getTotalSupply(tokenProgram: any): Promise<PythBalance> {
const pythTokenMintData = await tokenProgram.account.mint.fetch(PYTH_TOKEN)
return new PythBalance(pythTokenMintData.supply)
}
14 changes: 9 additions & 5 deletions frontend/pages/api/v1/locked_accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import BN from 'bn.js'
import { STAKING_ADDRESS } from '@pythnetwork/staking/app/constants'
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes'
import { Program, AnchorProvider } from '@coral-xyz/anchor'
import { Program, AnchorProvider, IdlAccounts } from '@coral-xyz/anchor'
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { Staking } from '@pythnetwork/staking/lib/target/types/staking'
import idl from '@pythnetwork/staking/target/idl/staking.json'
Expand Down Expand Up @@ -51,14 +51,18 @@ export default async function handlerLockedAccounts(
}
}

async function getStakeAccountDetails(positionAccountAddress: PublicKey) {
export async function getConfig(
stakingProgram: Program<Staking>
): Promise<IdlAccounts<Staking>['globalConfig']> {
const configAccountAddress = PublicKey.findProgramAddressSync(
[Buffer.from('config')],
STAKING_ADDRESS
)[0]
const configAccountData = await stakingProgram.account.globalConfig.fetch(
configAccountAddress
)
return await stakingProgram.account.globalConfig.fetch(configAccountAddress)
}

async function getStakeAccountDetails(positionAccountAddress: PublicKey) {
const configAccountData = await getConfig(stakingProgram)

const metadataAccountAddress = getMetadataAccountAddress(
positionAccountAddress
Expand Down
25 changes: 17 additions & 8 deletions staking/app/pythBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,19 @@ export class PythBalance {
}
}

toString(): string {
toString(commas = true): string {
const padded = this.toBN()
.toString()
.padStart(PYTH_DECIMALS + 1, "0");

return (
addCommas(padded.slice(0, padded.length - PYTH_DECIMALS)) +
("." + padded.slice(padded.length - PYTH_DECIMALS)).replace(
TRAILING_ZEROS,
""
)
);
const integerPart = padded.slice(0, padded.length - PYTH_DECIMALS);
return commas
? addCommas(integerPart)
: integerPart +
("." + padded.slice(padded.length - PYTH_DECIMALS)).replace(
TRAILING_ZEROS,
""
);
}

toBN() {
Expand Down Expand Up @@ -91,9 +92,17 @@ export class PythBalance {
return new PythBalance(other.toBN().add(this.toBN()));
}

sub(other: PythBalance): PythBalance {
return new PythBalance(this.toBN().sub(other.toBN()));
}

isZero(): boolean {
return this.eq(PythBalance.zero());
}

min(other: PythBalance): PythBalance {
return this.lt(other) ? this : other;
}
}

const addCommas = (x: string) => {
Expand Down
3 changes: 3 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"functions": {
"pages/api/v1/all_locked_accounts.ts": {
"maxDuration": 30
},
"pages/api/v1/cmc/supply.ts": {
"maxDuration": 30
}
}
}

0 comments on commit 4ad6531

Please sign in to comment.