registry.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import BN from "bn.js";
  2. import fetch from "cross-fetch";
  3. import * as borsh from "@project-serum/borsh";
  4. import { Connection, PublicKey } from "@solana/web3.js";
  5. /**
  6. * Returns a verified build from the anchor registry. null if no such
  7. * verified build exists, e.g., if the program has been upgraded since the
  8. * last verified build.
  9. */
  10. export async function verifiedBuild(
  11. connection: Connection,
  12. programId: PublicKey,
  13. limit: number = 5
  14. ): Promise<Build | null> {
  15. const url = `https://anchor.projectserum.com/api/v0/program/${programId.toString()}/latest?limit=${limit}`;
  16. const [programData, latestBuildsResp] = await Promise.all([
  17. fetchData(connection, programId),
  18. fetch(url),
  19. ]);
  20. // Filter out all non successful builds.
  21. const latestBuilds = (await latestBuildsResp.json()).filter(
  22. (b: Build) => !b.aborted && b.state === "Built" && b.verified === "Verified"
  23. );
  24. if (latestBuilds.length === 0) {
  25. return null;
  26. }
  27. // Get the latest build.
  28. const build = latestBuilds[0];
  29. // Has the program been upgraded since the last build?
  30. if (programData.slot.toNumber() !== build.verified_slot) {
  31. return null;
  32. }
  33. // Success.
  34. return build;
  35. }
  36. /**
  37. * Returns the program data account for this program, containing the
  38. * metadata for this program, e.g., the upgrade authority.
  39. */
  40. export async function fetchData(
  41. connection: Connection,
  42. programId: PublicKey
  43. ): Promise<ProgramData> {
  44. const accountInfo = await connection.getAccountInfo(programId);
  45. if (accountInfo === null) {
  46. throw new Error("program account not found");
  47. }
  48. const { program } = decodeUpgradeableLoaderState(accountInfo.data);
  49. const programdataAccountInfo = await connection.getAccountInfo(
  50. program.programdataAddress
  51. );
  52. if (programdataAccountInfo === null) {
  53. throw new Error("program data account not found");
  54. }
  55. const { programData } = decodeUpgradeableLoaderState(
  56. programdataAccountInfo.data
  57. );
  58. return programData;
  59. }
  60. const UPGRADEABLE_LOADER_STATE_LAYOUT = borsh.rustEnum(
  61. [
  62. borsh.struct([], "uninitialized"),
  63. borsh.struct(
  64. [borsh.option(borsh.publicKey(), "authorityAddress")],
  65. "buffer"
  66. ),
  67. borsh.struct([borsh.publicKey("programdataAddress")], "program"),
  68. borsh.struct(
  69. [
  70. borsh.u64("slot"),
  71. borsh.option(borsh.publicKey(), "upgradeAuthorityAddress"),
  72. ],
  73. "programData"
  74. ),
  75. ],
  76. undefined,
  77. borsh.u32()
  78. );
  79. export function decodeUpgradeableLoaderState(data: Buffer): any {
  80. return UPGRADEABLE_LOADER_STATE_LAYOUT.decode(data);
  81. }
  82. export type ProgramData = {
  83. slot: BN;
  84. upgradeAuthorityAddress: PublicKey | null;
  85. };
  86. export type Build = {
  87. aborted: boolean;
  88. address: string;
  89. created_at: string;
  90. updated_at: string;
  91. descriptor: string[];
  92. docker: string;
  93. id: number;
  94. name: string;
  95. sha256: string;
  96. upgrade_authority: string;
  97. verified: string;
  98. verified_slot: number;
  99. state: string;
  100. };