diff --git a/anchor/src/client/marinade.ts b/anchor/src/client/marinade.ts index aa309d4a..acd613e3 100644 --- a/anchor/src/client/marinade.ts +++ b/anchor/src/client/marinade.ts @@ -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 diff --git a/api/src/routers/fund.ts b/api/src/routers/fund.ts index 58ce681b..2e835482 100644 --- a/api/src/routers/fund.ts +++ b/api/src/routers/fund.ts @@ -149,7 +149,7 @@ 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 }); }); diff --git a/playground/src/app/stake/components/columns.tsx b/playground/src/app/stake/components/columns.tsx index 1321425f..8930117e 100644 --- a/playground/src/app/stake/components/columns.tsx +++ b/playground/src/app/stake/components/columns.tsx @@ -48,6 +48,20 @@ export const columns: ColumnDef[] = [ enableSorting: false, enableHiding: false, }, + { + accessorKey: "lamports", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const formatted = new Intl.NumberFormat("en-US").format( + row.getValue("lamports") + ); + return

{formatted}

; + }, + enableSorting: false, + enableHiding: false, + }, { accessorKey: "label", header: ({ column }) => ( @@ -83,7 +97,7 @@ export const columns: ColumnDef[] = [ } return ( -
+
{status.icon && ( )} diff --git a/playground/src/app/stake/components/data-table-row-actions.tsx b/playground/src/app/stake/components/data-table-row-actions.tsx index bd38fbda..bcded24e 100644 --- a/playground/src/app/stake/components/data-table-row-actions.tsx +++ b/playground/src/app/stake/components/data-table-row-actions.tsx @@ -17,8 +17,9 @@ interface DataTableRowActionsProps { export function DataTableRowActions({ row, }: DataTableRowActionsProps) { - const ticket = ticketOrStakeSchema.parse(row.original); - const isClaimable = ticket.status === "claimable"; + const ticketOrStake = ticketOrStakeSchema.parse(row.original); + const isClaimable = + ticketOrStake.status === "claimable" || ticketOrStake.status === "inactive"; const { glamClient, fund: fundPDA } = useGlam(); @@ -29,7 +30,7 @@ export function DataTableRowActions({ } try { - const ticketPublicKey = new PublicKey(ticket.publicKey); + const ticketPublicKey = new PublicKey(ticketOrStake.publicKey); console.log("Deactivating stake account:", ticketPublicKey.toBase58()); const txId = await glamClient.staking.deactivateStakeAccounts(fundPDA, [ diff --git a/playground/src/app/stake/components/data-table-toolbar.tsx b/playground/src/app/stake/components/data-table-toolbar.tsx index c2d89216..e79ba291 100644 --- a/playground/src/app/stake/components/data-table-toolbar.tsx +++ b/playground/src/app/stake/components/data-table-toolbar.tsx @@ -23,8 +23,10 @@ export function DataTableToolbar({
table.getColumn("publicKey")?.setFilterValue(event.target.value) } diff --git a/playground/src/app/stake/data/schema.ts b/playground/src/app/stake/data/schema.ts index 5e6bd137..d2093cb3 100644 --- a/playground/src/app/stake/data/schema.ts +++ b/playground/src/app/stake/data/schema.ts @@ -8,6 +8,7 @@ export const stakeServiceSchema = z.object({ export const ticketOrStakeSchema = z.object({ publicKey: z.string(), + lamports: z.number().nonnegative(), service: z.string(), status: z.string(), label: z.string(), diff --git a/playground/src/app/stake/page.tsx b/playground/src/app/stake/page.tsx index 3fae709d..26c7710f 100644 --- a/playground/src/app/stake/page.tsx +++ b/playground/src/app/stake/page.tsx @@ -66,16 +66,18 @@ export default function Stake() { ); const transformedStakes = stakes.map((stakeAccount) => ({ publicKey: stakeAccount.address.toBase58(), + lamports: stakeAccount.lamports, service: "native", status: stakeAccount.state, label: "stake", })); - const tickets = await glamClient.marinade.getExistingTickets(fundPDA); + const tickets = await glamClient.marinade.getTickets(fundPDA); const transformedTickets = tickets.map((ticket) => ({ - publicKey: ticket.toBase58(), + publicKey: ticket.address.toBase58(), + lamports: ticket.lamports, service: "marinade", - status: "pending", + status: ticket.isDue ? "claimable" : "pending", label: "ticket", }));