Răsfoiți Sursa

feat: use sendTransaction for OIS (#2555)

* feat: use sendTransaction for OIS

* remove integration test

* fix type

* fix format

* fix type
Keyvan Khademi 7 luni în urmă
părinte
comite
64e9eb3ae5

+ 2 - 0
apps/staking/src/hooks/use-api.tsx

@@ -195,6 +195,7 @@ const useApiContext = (
               publicKey: wallet.publicKey,
               signAllTransactions: wallet.signAllTransactions,
               signTransaction: wallet.signTransaction,
+              sendTransaction: wallet.sendTransaction,
             },
           })
         : undefined,
@@ -202,6 +203,7 @@ const useApiContext = (
       wallet.publicKey,
       wallet.signAllTransactions,
       wallet.signTransaction,
+      wallet.sendTransaction,
       connection,
     ],
   );

+ 0 - 129
governance/pyth_staking_sdk/integration-tests/all.test.ts

@@ -1,129 +0,0 @@
-import { Wallet } from "@coral-xyz/anchor";
-import type { Connection } from "@solana/web3.js";
-import {
-  Keypair,
-  PublicKey,
-  sendAndConfirmTransaction,
-  SystemProgram,
-  Transaction,
-} from "@solana/web3.js";
-
-import type { CustomAbortController } from "./start-validator";
-import { startValidatorRaw } from "./start-validator";
-import { getCurrentEpoch } from "../src";
-import { getConfigAddress } from "../src/pdas";
-import { PythStakingClient } from "../src/pyth-staking-client";
-import type { GlobalConfig } from "../src/types";
-
-describe("Test", () => {
-  let connection: Connection;
-  let controller: CustomAbortController;
-  let wallet: Wallet;
-  let pythStakingClient: PythStakingClient;
-  let rewardProgramAuthority: Keypair;
-  let poolData: Keypair;
-  let config: GlobalConfig;
-
-  beforeAll(async () => {
-    ({ connection, controller, wallet } = await startValidatorRaw());
-    pythStakingClient = new PythStakingClient({ connection, wallet });
-    rewardProgramAuthority = Keypair.generate();
-    poolData = Keypair.generate();
-  });
-
-  afterAll(() => {
-    return controller.abort();
-  });
-
-  test("config", async () => {
-    const tmpConfig: GlobalConfig = {
-      bump: getConfigAddress()[1],
-      governanceAuthority: PublicKey.unique(),
-      pythTokenMint: PublicKey.unique(),
-      pythGovernanceRealm: PublicKey.unique(),
-      removedUnlockingDuration: 0,
-      epochDuration: 100n,
-      freeze: false,
-      pdaAuthority: PublicKey.unique(),
-      governanceProgram: PublicKey.unique(),
-      pythTokenListTime: null,
-      agreementHash: Array.from<number>({ length: 32 }).fill(0),
-      mockClockTime: 0n,
-      poolAuthority: PublicKey.unique(),
-    };
-
-    await pythStakingClient.initGlobalConfig(tmpConfig);
-
-    config = await pythStakingClient.getGlobalConfig();
-
-    expect(config).toEqual(tmpConfig);
-  });
-
-  test("initialize pool", async () => {
-    const poolDataSpace = 2 * 1024 * 1024;
-    const balance =
-      await connection.getMinimumBalanceForRentExemption(poolDataSpace);
-
-    const transaction = new Transaction().add(
-      SystemProgram.createAccount({
-        fromPubkey: wallet.publicKey,
-        newAccountPubkey: poolData.publicKey,
-        lamports: balance,
-        space: poolDataSpace,
-        programId: pythStakingClient.integrityPoolProgram.programId,
-      }),
-    );
-
-    await sendAndConfirmTransaction(connection, transaction, [
-      wallet.payer,
-      poolData,
-    ]);
-
-    await pythStakingClient.initializePool({
-      rewardProgramAuthority: rewardProgramAuthority.publicKey,
-      y: 100n,
-      poolData: poolData.publicKey,
-    });
-
-    const poolConfig = await pythStakingClient.getPoolConfigAccount();
-
-    expect(poolConfig).toEqual({
-      poolData: poolData.publicKey,
-      rewardProgramAuthority: rewardProgramAuthority.publicKey,
-      pythTokenMint: config.pythTokenMint,
-      y: 100n,
-    });
-
-    const poolDataAccount = await pythStakingClient.getPoolDataAccount();
-
-    expect(poolDataAccount).toEqual({
-      lastUpdatedEpoch: (await getCurrentEpoch(connection)) - 1n,
-      claimableRewards: 0n,
-      publishers: Array.from({ length: 1024 }).fill(PublicKey.default),
-      delState: Array.from({ length: 1024 }).fill({
-        totalDelegation: 0n,
-        deltaDelegation: 0n,
-      }),
-      selfDelState: Array.from({ length: 1024 }).fill({
-        totalDelegation: 0n,
-        deltaDelegation: 0n,
-      }),
-      publisherStakeAccounts: Array.from({ length: 1024 }).fill(
-        PublicKey.default,
-      ),
-      events: Array.from({ length: 52 }).fill({
-        epoch: 0n,
-        y: 0n,
-        extraSpace: Array.from({ length: 7 }).fill(0n),
-        eventData: Array.from({ length: 1024 }).fill({
-          selfRewardRatio: 0n,
-          otherRewardRatio: 0n,
-          delegationFee: 0n,
-        }),
-      }),
-      numEvents: 0n,
-      numSlashEvents: Array.from({ length: 1024 }).fill(0n),
-      delegationFees: Array.from({ length: 1024 }).fill(0n),
-    });
-  });
-});

+ 0 - 6
governance/pyth_staking_sdk/integration-tests/keypairs/alice.json

@@ -1,6 +0,0 @@
-[
-  183, 145, 202, 243, 78, 73, 124, 200, 67, 201, 221, 160, 44, 228, 7, 237, 159,
-  162, 229, 154, 26, 254, 210, 186, 13, 234, 77, 153, 59, 16, 132, 85, 188, 161,
-  67, 10, 200, 220, 80, 95, 43, 11, 222, 172, 144, 237, 178, 104, 39, 96, 83,
-  131, 107, 198, 0, 181, 234, 6, 113, 123, 228, 84, 138, 58
-]

+ 0 - 6
governance/pyth_staking_sdk/integration-tests/keypairs/bob.json

@@ -1,6 +0,0 @@
-[
-  37, 161, 177, 49, 106, 56, 211, 117, 100, 37, 65, 105, 161, 118, 33, 36, 69,
-  3, 100, 111, 197, 40, 110, 39, 164, 106, 6, 28, 31, 0, 202, 107, 129, 118,
-  108, 33, 108, 0, 183, 136, 14, 121, 250, 232, 104, 229, 12, 174, 235, 3, 172,
-  223, 71, 113, 5, 23, 142, 62, 184, 88, 46, 19, 61, 248
-]

+ 0 - 6
governance/pyth_staking_sdk/integration-tests/keypairs/localnet-authority.json

@@ -1,6 +0,0 @@
-[
-  89, 248, 82, 140, 72, 162, 248, 43, 112, 211, 27, 53, 65, 180, 206, 43, 89,
-  31, 133, 170, 209, 177, 168, 196, 143, 175, 0, 14, 45, 162, 73, 1, 33, 253, 6,
-  66, 85, 211, 55, 70, 173, 117, 34, 169, 193, 205, 183, 194, 252, 226, 147,
-  131, 186, 70, 0, 221, 227, 108, 167, 83, 156, 31, 208, 120
-]

+ 0 - 6
governance/pyth_staking_sdk/integration-tests/keypairs/pyth-mint.json

@@ -1,6 +0,0 @@
-[
-  215, 90, 118, 135, 142, 32, 217, 201, 81, 8, 215, 67, 40, 207, 247, 75, 131,
-  220, 66, 3, 46, 103, 79, 15, 176, 124, 112, 99, 239, 152, 200, 2, 237, 180,
-  74, 77, 106, 199, 181, 38, 238, 187, 114, 59, 13, 75, 239, 220, 101, 239, 96,
-  232, 237, 229, 82, 151, 219, 17, 7, 195, 165, 152, 132, 24
-]

+ 0 - 9
governance/pyth_staking_sdk/integration-tests/keys.ts

@@ -1,9 +0,0 @@
-import fs from "node:fs";
-
-import { Keypair } from "@solana/web3.js";
-
-export function loadKeypair(path: string) {
-  return Keypair.fromSecretKey(
-    new Uint8Array(JSON.parse(fs.readFileSync(path, "utf8")) as number[]),
-  );
-}

BIN
governance/pyth_staking_sdk/integration-tests/programs/integrity_pool.so


BIN
governance/pyth_staking_sdk/integration-tests/programs/publisher_caps.so


BIN
governance/pyth_staking_sdk/integration-tests/programs/staking.so


+ 0 - 107
governance/pyth_staking_sdk/integration-tests/start-validator.ts

@@ -1,107 +0,0 @@
-/* eslint-disable @typescript-eslint/restrict-template-expressions */
-/* eslint-disable unicorn/catch-error-name */
-/* eslint-disable @typescript-eslint/no-unnecessary-condition */
-/* eslint-disable @typescript-eslint/no-base-to-string */
-/* eslint-disable no-console */
-import { exec } from "node:child_process";
-import { mkdtemp } from "node:fs/promises";
-import os from "node:os";
-import path from "node:path";
-
-import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
-import { Connection } from "@solana/web3.js";
-
-import { loadKeypair } from "./keys";
-import {
-  INTEGRITY_POOL_PROGRAM_ADDRESS,
-  PUBLISHER_CAPS_PROGRAM_ADDRESS,
-  STAKING_PROGRAM_ADDRESS,
-} from "../src/constants";
-
-export function getConnection(): Connection {
-  return new Connection(
-    `http://127.0.0.1:8899`,
-    AnchorProvider.defaultOptions().commitment,
-  );
-}
-
-/**
- * If we abort immediately, the web-sockets are still subscribed, and they give a ton of errors.
- * Waiting a few seconds is enough to let the sockets close.
- */
-export class CustomAbortController {
-  abortController: AbortController;
-  constructor(abortController: AbortController) {
-    this.abortController = abortController;
-  }
-  abort() {
-    return new Promise((resolve) => {
-      setTimeout(() => {
-        this.abortController.abort();
-        resolve(undefined);
-      }, 5000);
-    });
-  }
-}
-
-/* Starts a validator at port portNumber with the command line arguments specified after a few basic ones
- *
- * returns a `{ controller, connection }` struct. Users of this method have to terminate the
- * validator by calling :
- * ```controller.abort()```
- */
-export async function startValidatorRaw() {
-  const connection: Connection = getConnection();
-  const ledgerDir = await mkdtemp(path.join(os.tmpdir(), "ledger-"));
-
-  const internalController: AbortController = new AbortController();
-  const { signal } = internalController;
-
-  const user = loadKeypair(
-    "integration-tests/keypairs/localnet-authority.json",
-  );
-
-  const command = `solana-test-validator \
-    --ledger ${ledgerDir} \
-    --reset \
-    --mint ${user.publicKey.toBase58()} \
-    --bpf-program ${STAKING_PROGRAM_ADDRESS.toBase58()} integration-tests/programs/staking.so \
-    --bpf-program ${INTEGRITY_POOL_PROGRAM_ADDRESS.toBase58()} integration-tests/programs/integrity_pool.so \
-    --bpf-program ${PUBLISHER_CAPS_PROGRAM_ADDRESS.toBase58()} integration-tests/programs/publisher_caps.so \
-    `;
-
-  exec(command, { signal }, (error, stdout, stderr) => {
-    if (error?.name.includes("AbortError")) {
-      // Test complete, this is expected.
-      return;
-    }
-    if (error) {
-      console.error(`exec error: ${error}`);
-      return;
-    }
-    console.log(`stdout: ${stdout}`);
-    console.error(`stderr: ${stderr}`);
-  });
-  const controller = new CustomAbortController(internalController);
-
-  let numRetries = 0;
-  while (true) {
-    try {
-      await new Promise((resolve) => setTimeout(resolve, 1000));
-      await connection.getSlot();
-      break;
-    } catch (e) {
-      // Bound the number of retries so the tests don't hang if there's some problem blocking
-      // the connection to the validator.
-      if (numRetries == 30) {
-        console.log(
-          `Failed to start validator or connect to running validator. Caught exception: ${e}`,
-        );
-        throw e;
-      }
-      numRetries += 1;
-    }
-  }
-
-  return { controller, connection, wallet: new Wallet(user) };
-}

+ 4 - 4
governance/pyth_staking_sdk/src/pyth-staking-client.ts

@@ -14,7 +14,6 @@ import {
   getAssociatedTokenAddressSync,
   getMint,
 } from "@solana/spl-token";
-import type { AnchorWallet } from "@solana/wallet-adapter-react";
 import {
   Connection,
   PublicKey,
@@ -48,6 +47,7 @@ import type {
   VestingSchedule,
 } from "./types";
 import { PositionState } from "./types";
+import type { Staking } from "../types/staking";
 import { bigintMax, bigintMin } from "./utils/bigint";
 import { convertBigIntToBN, convertBNToBigInt } from "./utils/bn";
 import { epochToDate, getCurrentEpoch } from "./utils/clock";
@@ -59,22 +59,22 @@ import {
 } from "./utils/position";
 import { sendTransaction } from "./utils/transaction";
 import { getUnlockSchedule } from "./utils/vesting";
+import type { PythStakingWallet } from "./utils/wallet";
 import { DummyWallet } from "./utils/wallet";
 import * as IntegrityPoolIdl from "../idl/integrity-pool.json";
 import * as PublisherCapsIdl from "../idl/publisher-caps.json";
 import * as StakingIdl from "../idl/staking.json";
 import type { IntegrityPool } from "../types/integrity-pool";
 import type { PublisherCaps } from "../types/publisher-caps";
-import type { Staking } from "../types/staking";
 
 export type PythStakingClientConfig = {
   connection: Connection;
-  wallet?: AnchorWallet;
+  wallet?: PythStakingWallet;
 };
 
 export class PythStakingClient {
   connection: Connection;
-  wallet: AnchorWallet;
+  wallet: PythStakingWallet;
   provider: AnchorProvider;
   stakingProgram: Program<Staking>;
   integrityPoolProgram: Program<IntegrityPool>;

+ 7 - 7
governance/pyth_staking_sdk/src/utils/transaction.ts

@@ -1,14 +1,12 @@
-import {
-  sendTransactions,
-  TransactionBuilder,
-} from "@pythnetwork/solana-utils";
-import type { AnchorWallet } from "@solana/wallet-adapter-react";
+import { TransactionBuilder } from "@pythnetwork/solana-utils";
 import { Connection, TransactionInstruction } from "@solana/web3.js";
 
+import type { PythStakingWallet } from "./wallet";
+
 export const sendTransaction = async (
   instructions: TransactionInstruction[],
   connection: Connection,
-  wallet: AnchorWallet,
+  wallet: PythStakingWallet,
 ) => {
   const transactions = await TransactionBuilder.batchIntoVersionedTransactions(
     wallet.publicKey,
@@ -20,5 +18,7 @@ export const sendTransaction = async (
     {},
   );
 
-  return sendTransactions(transactions, connection, wallet);
+  for (const transaction of transactions) {
+    await wallet.sendTransaction(transaction.tx, connection);
+  }
 };

+ 18 - 2
governance/pyth_staking_sdk/src/utils/wallet.ts

@@ -1,7 +1,20 @@
 import type { AnchorWallet } from "@solana/wallet-adapter-react";
-import { PublicKey } from "@solana/web3.js";
+import type { TransactionSignature } from "@solana/web3.js";
+import {
+  Connection,
+  PublicKey,
+  Transaction,
+  VersionedTransaction,
+} from "@solana/web3.js";
 
-export const DummyWallet: AnchorWallet = {
+export type PythStakingWallet = AnchorWallet & {
+  sendTransaction: (
+    tx: Transaction | VersionedTransaction,
+    connection: Connection,
+  ) => Promise<TransactionSignature>;
+};
+
+export const DummyWallet: PythStakingWallet = {
   publicKey: PublicKey.default,
   signTransaction: () => {
     throw new Error("Cannot sign transaction without a wallet");
@@ -9,4 +22,7 @@ export const DummyWallet: AnchorWallet = {
   signAllTransactions: () => {
     throw new Error("Cannot sign transactions without a wallet");
   },
+  sendTransaction: () => {
+    throw new Error("Cannot send transaction without a wallet");
+  },
 };