diff --git a/apps/staking/src/api.ts b/apps/staking/src/api.ts index 5ba2a9bc25..62682ae380 100644 --- a/apps/staking/src/api.ts +++ b/apps/staking/src/api.ts @@ -106,6 +106,7 @@ export const loadData = async ( pythnetClient: PythnetClient, hermesClient: HermesClient, stakeAccount?: PublicKey | undefined, + simulationPayer?: PublicKey, ): Promise => stakeAccount === undefined ? loadDataNoStakeAccount(client, pythnetClient, hermesClient) @@ -114,6 +115,7 @@ export const loadData = async ( pythnetClient, hermesClient, stakeAccount, + simulationPayer, ); const loadDataNoStakeAccount = async ( @@ -149,6 +151,7 @@ const loadDataForStakeAccount = async ( pythnetClient: PythnetClient, hermesClient: HermesClient, stakeAccount: PublicKey, + simulationPayer?: PublicKey, ): Promise => { const [ { publishers, ...baseInfo }, @@ -160,7 +163,7 @@ const loadDataForStakeAccount = async ( loadBaseInfo(client, pythnetClient, hermesClient), client.getStakeAccountCustody(stakeAccount), client.getUnlockSchedule(stakeAccount), - client.getClaimableRewards(stakeAccount), + client.getClaimableRewards(stakeAccount, simulationPayer), client.getStakeAccountPositions(stakeAccount), ]); diff --git a/apps/staking/src/components/Root/index.tsx b/apps/staking/src/components/Root/index.tsx index 53f5459d5e..ae1b9d9f72 100644 --- a/apps/staking/src/components/Root/index.tsx +++ b/apps/staking/src/components/Root/index.tsx @@ -14,6 +14,7 @@ import { MAINNET_RPC, HERMES_URL, PYTHNET_RPC, + SIMULATION_PAYER_ADDRESS, } from "../../config/server"; import { ApiProvider } from "../../hooks/use-api"; import { LoggerProvider } from "../../hooks/use-logger"; @@ -82,7 +83,11 @@ const HtmlWithProviders = ({ lang, ...props }: HTMLProps) => ( walletConnectProjectId={WALLETCONNECT_PROJECT_ID} mainnetRpc={MAINNET_RPC} > - + diff --git a/apps/staking/src/config/server.ts b/apps/staking/src/config/server.ts index 0e4c566cc0..25846b4016 100644 --- a/apps/staking/src/config/server.ts +++ b/apps/staking/src/config/server.ts @@ -74,7 +74,12 @@ export const GOVERNANCE_ONLY_REGIONS = transformOr( [], ); export const PROXYCHECK_API_KEY = demandInProduction("PROXYCHECK_API_KEY"); - +// This needs to be a public key that has SOL in it all the time, it will be used as a payer in the transaction simulation to compute the claimable rewards +// such simulation fails when the payer has no funds. +export const SIMULATION_PAYER_ADDRESS = getOr( + "SIMULATION_PAYER_ADDRESS", + "E5KR7yfb9UyVB6ZhmhQki1rM1eBcxHvyGKFZakAC5uc", +); class MissingEnvironmentError extends Error { constructor(name: string) { super(`Missing environment variable: ${name}!`); diff --git a/apps/staking/src/hooks/use-api.tsx b/apps/staking/src/hooks/use-api.tsx index 7d9b22f7cf..d66451b69f 100644 --- a/apps/staking/src/hooks/use-api.tsx +++ b/apps/staking/src/hooks/use-api.tsx @@ -4,7 +4,7 @@ import { HermesClient } from "@pythnetwork/hermes-client"; import { PythnetClient, PythStakingClient } from "@pythnetwork/staking-sdk"; import { useLocalStorageValue } from "@react-hookz/web"; import { useConnection, useWallet } from "@solana/wallet-adapter-react"; -import { Connection, type PublicKey } from "@solana/web3.js"; +import { Connection, PublicKey } from "@solana/web3.js"; import { type ComponentProps, createContext, useContext, useMemo } from "react"; import { useSWRConfig } from "swr"; @@ -65,6 +65,7 @@ const State = { pythnetClient: PythnetClient, hermesClient: HermesClient, account: PublicKey, + simulationPayer: PublicKey, allAccounts: [PublicKey, ...PublicKey[]], selectAccount: (account: PublicKey) => void, mutate: ReturnType["mutate"], @@ -95,7 +96,13 @@ const State = { dashboardDataCacheKey, loadData: () => - api.loadData(client, pythnetClient, hermesClient, account), + api.loadData( + client, + pythnetClient, + hermesClient, + account, + simulationPayer, + ), claim: bindApi(api.claim), deposit: bindApi(api.deposit), @@ -131,19 +138,25 @@ type ApiProviderProps = Omit< > & { pythnetRpcUrl: string; hermesUrl: string; + simulationPayerAddress: string; }; export const ApiProvider = ({ hermesUrl, pythnetRpcUrl, + simulationPayerAddress, ...props }: ApiProviderProps) => { - const state = useApiContext(hermesUrl, pythnetRpcUrl); + const state = useApiContext(hermesUrl, pythnetRpcUrl, simulationPayerAddress); return ; }; -const useApiContext = (hermesUrl: string, pythnetRpcUrl: string) => { +const useApiContext = ( + hermesUrl: string, + pythnetRpcUrl: string, + simulationPayerAddress: string, +) => { const wallet = useWallet(); const { connection } = useConnection(); const { isMainnet } = useNetwork(); @@ -153,6 +166,10 @@ const useApiContext = (hermesUrl: string, pythnetRpcUrl: string) => { () => new PythnetClient(new Connection(pythnetRpcUrl)), [pythnetRpcUrl], ); + const simulationPayer = useMemo( + () => new PublicKey(simulationPayerAddress), + [simulationPayerAddress], + ); const pythStakingClient = useMemo( () => wallet.publicKey && wallet.signAllTransactions && wallet.signTransaction @@ -235,6 +252,7 @@ const useApiContext = (hermesUrl: string, pythnetRpcUrl: string) => { pythnetClient, hermesClient, selectedAccount ?? firstAccount, + simulationPayer, [firstAccount, ...otherAccounts], (account: PublicKey) => { localStorageValue.set(account.toBase58()); diff --git a/governance/pyth_staking_sdk/src/pyth-staking-client.ts b/governance/pyth_staking_sdk/src/pyth-staking-client.ts index fa94f94849..d56e485604 100644 --- a/governance/pyth_staking_sdk/src/pyth-staking-client.ts +++ b/governance/pyth_staking_sdk/src/pyth-staking-client.ts @@ -688,6 +688,7 @@ export class PythStakingClient { async getAdvanceDelegationRecordInstructions( stakeAccountPositions: PublicKey, + payer?: PublicKey, ) { const poolData = await this.getPoolDataAccount(); const stakeAccountPositionsData = await this.getStakeAccountPositions( @@ -745,7 +746,7 @@ export class PythStakingClient { this.integrityPoolProgram.methods .advanceDelegationRecord() .accountsPartial({ - payer: this.wallet.publicKey, + payer: payer ?? this.wallet.publicKey, publisher: pubkey, publisherStakeAccountPositions: stakeAccount, publisherStakeAccountCustody: stakeAccount @@ -795,16 +796,20 @@ export class PythStakingClient { ); } - public async getClaimableRewards(stakeAccountPositions: PublicKey) { + public async getClaimableRewards( + stakeAccountPositions: PublicKey, + simulationPayer?: PublicKey, + ) { const instructions = await this.getAdvanceDelegationRecordInstructions( stakeAccountPositions, + simulationPayer, ); let totalRewards = 0n; for (const instruction of instructions.advanceDelegationRecordInstructions) { const tx = new Transaction().add(instruction); - tx.feePayer = this.wallet.publicKey; + tx.feePayer = simulationPayer ?? this.wallet.publicKey; const res = await this.connection.simulateTransaction(tx); const val = res.value.returnData?.data[0]; if (val === undefined) {