Skip to content

Commit

Permalink
Locked accounts API (#284)
Browse files Browse the repository at this point in the history
* Continue

* bump solana

* Fix

* Next

* Please work

* Vesting-api

* Seems to work

* Fix

* Please work

* Add env variable

* Rename

* Rename

* Comment
  • Loading branch information
guibescos authored Nov 23, 2023
1 parent 13d22b7 commit a4b350c
Show file tree
Hide file tree
Showing 4 changed files with 511 additions and 252 deletions.
1 change: 1 addition & 0 deletions frontend/.env.sample
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ENDPOINT="https://api.devnet.solana.com"
BACKEND_ENDPOINT="https://api.devnet.solana.com"
CLUSTER="devnet"
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@
"start": "next start"
},
"dependencies": {
"@coral-xyz/anchor": "^0.29.0",
"@coral-xyz/spl-token": "^0.29.0",
"@headlessui/react": "^1.6.0",
"@heroicons/react": "^1.0.6",
"@coral-xyz/anchor": "^0.29.0",
"@pythnetwork/staking": "*",
"@solana/spl-token": "^0.1.8",
"@solana/wallet-adapter-base": "^0.9.22",
"@solana/wallet-adapter-react": "^0.15.32",
"@solana/wallet-adapter-react-ui": "^0.9.31",
"@solana/wallet-adapter-wallets": "=0.19.16",
"@solana/wallet-adapter-wallets": "^0.19.16",
"@solana/web3.js": "^1.87.5",
"@tippyjs/react": "^4.2.6",
"dotenv": "^16.0.0",
Expand Down
159 changes: 159 additions & 0 deletions frontend/pages/api/v1/locked_accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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 { 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 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'

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 handlerLockedAccounts(
req: NextApiRequest,
res: NextApiResponse
) {
const { owner } = req.query

if (owner == undefined || owner instanceof Array) {
res.status(400).json({
error: "Must provide the 'owner' query parameters",
})
} else {
const stakeAccounts = await geStakeAccounts(
connection,
new PublicKey(owner)
)
res.status(200).json(
await Promise.all(
stakeAccounts.map((account) => {
return getStakeAccountDetails(account)
})
)
)
}
}

async function getStakeAccountDetails(positionAccountAddress: PublicKey) {
const configAccountAddress = PublicKey.findProgramAddressSync(
[Buffer.from('config')],
STAKING_ADDRESS
)[0]
const configAccountData = await stakingProgram.account.globalConfig.fetch(
configAccountAddress
)

const metadataAccountAddress = PublicKey.findProgramAddressSync(
[Buffer.from('stake_metadata'), positionAccountAddress.toBuffer()],
STAKING_ADDRESS
)[0]
const metadataAccountData =
await stakingProgram.account.stakeAccountMetadataV2.fetch(
metadataAccountAddress
)

const lock = metadataAccountData.lock

const custodyAccountAddress = PublicKey.findProgramAddressSync(
[Buffer.from('custody'), positionAccountAddress.toBuffer()],
STAKING_ADDRESS
)[0]

const custodyAccountData = await tokenProgram.account.account.fetch(
custodyAccountAddress
)

return {
custodyAccount: custodyAccountAddress.toBase58(),
actualAmount: new PythBalance(custodyAccountData.amount).toString(),
lock: getLockSummary(lock, configAccountData.pythTokenListTime),
}
}

async function geStakeAccounts(connection: Connection, owner: PublicKey) {
const response = await connection.getProgramAccounts(STAKING_ADDRESS, {
encoding: 'base64',
filters: [
{
memcmp: {
offset: 0,
bytes: bs58.encode(Buffer.from('55c3f14f7cc04f0b', 'hex')), // Positions account discriminator
},
},
{
memcmp: {
offset: 8,
bytes: owner.toBase58(),
},
},
],
})
return response.map((account) => {
return account.pubkey
})
}

export function getLockSummary(lock: any, listTime: BN | null) {
if (lock.fullyVested) {
return { type: 'fullyUnlocked' }
} else if (lock.periodicVestingAfterListing) {
return {
type: 'periodicUnlockingAfterListing',
schedule: getUnlockEvents(
listTime,
lock.periodicVestingAfterListing.periodDuration,
lock.periodicVestingAfterListing.numPeriods,
lock.periodicVestingAfterListing.initialBalance
),
}
} else if (lock.periodicVesting) {
return {
type: 'periodicUnlocking',
schedule: getUnlockEvents(
lock.periodicVesting.startDate,
lock.periodicVesting.periodDuration,
lock.periodicVesting.numPeriods,
lock.periodicVesting.initialBalance
),
}
}
}

export function getUnlockEvents(
startData: BN | null,
periodDuration: BN,
numberOfPeriods: BN,
initialBalance: BN
) {
if (startData) {
return Array(numberOfPeriods.toNumber())
.fill(0)
.map((_, i) => {
return {
date: startData.add(periodDuration.muln(i + 1)).toString(),
amount: new PythBalance(
initialBalance.divn(numberOfPeriods.toNumber())
).toString(),
}
})
}
return []
}
Loading

2 comments on commit a4b350c

@vercel
Copy link

@vercel vercel bot commented on a4b350c Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

staking-devnet – ./

governance-nu.vercel.app
staking-devnet-pyth-web.vercel.app
staking-devnet-git-main-pyth-web.vercel.app

@vercel
Copy link

@vercel vercel bot commented on a4b350c Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.