Skip to content

Commit

Permalink
[playground] display onchain stake accounts (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
yurushao authored Aug 28, 2024
1 parent bc0e2c8 commit cb1d73b
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 86 deletions.
46 changes: 46 additions & 0 deletions anchor/src/client/marinade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,52 @@ export class MarinadeClient {
return accounts.map((a) => a.pubkey);
}

async getTickets(fundPDA: PublicKey): Promise<
{
address: PublicKey;
lamports: number;
createdEpoch: number;
isDue: boolean;
}[]
> {
// TicketAccount {
// stateAddress: web3.PublicKey; // offset 8
// beneficiary: web3.PublicKey; // offset 40
// lamportsAmount: BN; // offset 72
// createdEpoch: BN;
// }
const accounts =
await this.base.provider.connection.getParsedProgramAccounts(
MARINADE_PROGRAM_ID,
{
filters: [
{
dataSize: 88,
},
{
memcmp: {
offset: 40,
bytes: this.base.getTreasuryPDA(fundPDA).toBase58(),
},
},
],
}
);
const currentEpoch = await this.base.provider.connection.getEpochInfo();
return accounts.map((a) => {
const lamports = Number((a.account.data as Buffer).readBigInt64LE(72));
const createdEpoch = Number(
(a.account.data as Buffer).readBigInt64LE(80)
);
return {
address: a.pubkey,
lamports,
createdEpoch,
isDue: currentEpoch.epoch > createdEpoch,
};
});
}

getMarinadeState(): any {
// The addresses are the same in mainnet and devnet:
// https://docs.marinade.finance/developers/contract-addresses
Expand Down
47 changes: 47 additions & 0 deletions anchor/src/client/staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SYSVAR_CLOCK_PUBKEY,
SYSVAR_STAKE_HISTORY_PUBKEY,
STAKE_CONFIG_ID,
ParsedAccountData,
} from "@solana/web3.js";

import { BaseClient, ApiTxOptions } from "./base";
Expand All @@ -23,6 +24,12 @@ interface StakePoolAccountData {
validatorList: PublicKey;
}

type StakeAccountInfo = {
address: PublicKey;
lamports: number;
state: string;
};

export class StakingClient {
public constructor(readonly base: BaseClient) {}

Expand Down Expand Up @@ -172,6 +179,46 @@ export class StakingClient {
.map((a) => a.pubkey);
}

async getStakeAccountsWithStates(
withdrawAuthority: PublicKey
): Promise<StakeAccountInfo[]> {
const STAKE_ACCOUNT_SIZE = 200;
const accounts =
await this.base.provider.connection.getParsedProgramAccounts(
StakeProgram.programId,
{
filters: [
{
dataSize: STAKE_ACCOUNT_SIZE,
},
{
memcmp: {
offset: 12,
bytes: withdrawAuthority.toBase58(),
},
},
],
}
);

const stakes = await Promise.all(
accounts.map(async (account) => {
const stakeInfo =
await this.base.provider.connection.getStakeActivation(
account.pubkey
);
return {
address: account.pubkey,
lamports: account.account.lamports,
state: stakeInfo.state,
};
})
);

// order by lamports desc
return stakes.sort((a, b) => b.lamports - a.lamports);
}

async getStakePoolAccountData(
stakePool: PublicKey
): Promise<StakePoolAccountData> {
Expand Down
2 changes: 1 addition & 1 deletion anchor/src/react/glam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function GlamProvider({
).then((res) => res.json()),
enabled: !!wallet.publicKey,
staleTime: 1000 * 60 * 5, // 5 minutes
refetchInterval: 1000 * 10, // 10 seconds
refetchInterval: 1000 * 30, // 10 seconds
});

useEffect(() => {
Expand Down
3 changes: 3 additions & 0 deletions api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ app.use((req, res, next) => {
if (process.env.GAE_SERVICE && req.hostname !== "api.glam.systems") {
req.client = devnetClient;
}
if (process.env.NODE_ENV === "development") {
console.log(`${new Date().toISOString()} ${req.method} ${req.originalUrl}`);
}
next();
});

Expand Down
10 changes: 9 additions & 1 deletion api/src/routers/fund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,19 @@ router.get("/funds/:pubkey/perf", async (req, res) => {

router.get("/funds/:pubkey/tickets", async (req, res) => {
const fund = validatePubkey(req.params.pubkey);
const tickets = await req.client.marinade.getExistingTickets(fund);
const tickets = await req.client.marinade.getTickets(fund);
res.set("content-type", "application/json");
res.send({ tickets });
});

router.get("/funds/:pubkey/stakes", async (req, res) => {
const fund = validatePubkey(req.params.pubkey);
const treasury = req.client.getTreasuryPDA(fund);
const stakes = await req.client.staking.getStakeAccountsWithStates(treasury);
res.set("content-type", "application/json");
res.send({ stakes });
});

router.get("/funds/:pubkey/metadata", async (req, res) => {
const pubkey = validatePubkey(req.params.pubkey);
if (!pubkey) {
Expand Down
31 changes: 24 additions & 7 deletions playground/src/app/stake/components/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";

import { types, statuses } from "../data/data";
import { Ticket } from "../data/ticketSchema";
import { TicketOrStake } from "../data/schema";
import { DataTableColumnHeader } from "./data-table-column-header";
import { DataTableRowActions } from "./data-table-row-actions";

import TruncateAddress from "../../../utils/TruncateAddress";

export const columns: ColumnDef<Ticket>[] = [
export const columns: ColumnDef<TicketOrStake>[] = [
{
id: "select",
header: ({ table }) => (
Expand Down Expand Up @@ -43,7 +43,21 @@ export const columns: ColumnDef<Ticket>[] = [
<DataTableColumnHeader column={column} title="Ticket" />
),
cell: ({ row }) => {
return <TruncateAddress address={row.getValue("publicKey")}/>
return <TruncateAddress address={row.getValue("publicKey")} />;
},
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "lamports",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Lamports" />
),
cell: ({ row }) => {
const formatted = new Intl.NumberFormat("en-US").format(
row.getValue("lamports")
);
return <p>{formatted}</p>;
},
enableSorting: false,
enableHiding: false,
Expand All @@ -58,9 +72,12 @@ export const columns: ColumnDef<Ticket>[] = [

return (
<div className="flex space-x-2">
{type && <Badge variant="outline" className="rounded-none">{type.label}</Badge>}
<span className="max-w-[500px] truncate font-medium">
</span>
{type && (
<Badge variant="outline" className="rounded-none">
{type.label}
</Badge>
)}
<span className="max-w-[500px] truncate font-medium"></span>
</div>
);
},
Expand All @@ -80,7 +97,7 @@ export const columns: ColumnDef<Ticket>[] = [
}

return (
<div className="flex w-[100px] items-center">
<div className="flex w-[120px] items-center">
{status.icon && (
<status.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
Expand Down
21 changes: 13 additions & 8 deletions playground/src/app/stake/components/data-table-row-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ResetIcon } from "@radix-ui/react-icons";
import { Row } from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { PublicKey } from "@solana/web3.js";
import { ticketSchema } from "../data/ticketSchema";
import { ticketOrStakeSchema } from "../data/schema";
import { useGlam } from "@glam/anchor/react";
import { testFund } from "../../testFund";
import { toast } from "@/components/ui/use-toast";
Expand All @@ -17,20 +17,25 @@ interface DataTableRowActionsProps<TData> {
export function DataTableRowActions<TData>({
row,
}: DataTableRowActionsProps<TData>) {
const ticket = ticketSchema.parse(row.original);
const isClaimable = ticket.status === "claimable";
const ticketOrStake = ticketOrStakeSchema.parse(row.original);
const isClaimable =
ticketOrStake.status === "claimable" || ticketOrStake.status === "inactive";

const { glamClient } = useGlam();
const { glamClient, fund: fundPDA } = useGlam();

const handleClaim = async () => {
if (!fundPDA) {
console.error("No fund selected");
return;
}

try {
const ticketPublicKey = new PublicKey(ticket.publicKey);
console.log("Test Claim Button");
const ticketPublicKey = new PublicKey(ticketOrStake.publicKey);
console.log("Deactivating stake account:", ticketPublicKey.toBase58());

const txId = await glamClient.marinade.claimTickets(testFund.fundPDA, [
const txId = await glamClient.staking.deactivateStakeAccounts(fundPDA, [
ticketPublicKey,
]);
console.log("Claim successful");

toast({
title: "Claim Successful",
Expand Down
6 changes: 4 additions & 2 deletions playground/src/app/stake/components/data-table-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ export function DataTableToolbar<TData>({
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
<Input
placeholder="Filter tickets..."
value={(table.getColumn("publicKey")?.getFilterValue() as string) ?? ""}
placeholder="Filter tickets and stakes ..."
value={
(table.getColumn("publicKey")?.getFilterValue() as string) ?? ""
}
onChange={(event) =>
table.getColumn("publicKey")?.setFilterValue(event.target.value)
}
Expand Down
10 changes: 9 additions & 1 deletion playground/src/app/stake/data/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ export const types = [
{
value: "liquid",
label: "Liquid",
}
},
{
value: "stake",
label: "Stake",
},
{
value: "ticket",
label: "Ticket",
},
];

export const statuses = [
Expand Down
18 changes: 18 additions & 0 deletions playground/src/app/stake/data/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { z } from "zod";

export const stakeServiceSchema = z.object({
service: z.enum(["Marinade", "Native", "Jito"]),
amountIn: z.number().nonnegative(),
amountInAsset: z.string(),
});

export const ticketOrStakeSchema = z.object({
publicKey: z.string(),
lamports: z.number().nonnegative(),
service: z.string(),
status: z.string(),
label: z.string(),
});

export type StakeService = z.infer<typeof stakeServiceSchema>;
export type TicketOrStake = z.infer<typeof ticketOrStakeSchema>;
20 changes: 0 additions & 20 deletions playground/src/app/stake/data/testTickets.tsx

This file was deleted.

10 changes: 0 additions & 10 deletions playground/src/app/stake/data/ticketSchema.ts

This file was deleted.

Loading

0 comments on commit cb1d73b

Please sign in to comment.