propose.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import Squads, { getIxAuthorityPDA, getTxPDA } from "@sqds/mesh";
  2. import {
  3. PublicKey,
  4. Transaction,
  5. TransactionInstruction,
  6. } from "@solana/web3.js";
  7. import { BN } from "bn.js";
  8. import { AnchorProvider } from "@project-serum/anchor";
  9. import {
  10. createWormholeProgramInterface,
  11. getPostMessageAccounts,
  12. } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
  13. import { ExecutePostedVaa } from "./governance_payload/ExecutePostedVaa";
  14. type SquadInstruction = {
  15. instruction: TransactionInstruction;
  16. authorityIndex?: number;
  17. authorityBump?: number;
  18. authorityType?: string;
  19. };
  20. /**
  21. * Propose an array of `TransactionInstructions` as a proposal
  22. * @param squad Squads client
  23. * @param vault vault public key (the id of the multisig where these instructions should be proposed)
  24. * @param instructions instructions that will be proposed
  25. * @param remote whether the instructions should be executed in the chain of the multisig or remotely on Pythnet
  26. * @returns the newly created proposal's pubkey
  27. */
  28. export async function proposeInstructions(
  29. squad: Squads,
  30. vault: PublicKey,
  31. instructions: TransactionInstruction[],
  32. remote: boolean,
  33. wormholeAddress?: PublicKey
  34. ): Promise<PublicKey> {
  35. const msAccount = await squad.getMultisig(vault);
  36. let txToSend: Transaction[] = [];
  37. const createProposal = new Transaction().add(
  38. await squad.buildCreateTransaction(
  39. msAccount.publicKey,
  40. msAccount.authorityIndex,
  41. msAccount.transactionIndex + 1
  42. )
  43. );
  44. const newProposalAddress = getTxPDA(
  45. vault,
  46. new BN(msAccount.transactionIndex + 1),
  47. squad.multisigProgramId
  48. )[0];
  49. txToSend.push(createProposal);
  50. if (remote) {
  51. if (!wormholeAddress) {
  52. throw new Error("Need wormhole address");
  53. }
  54. for (let i = 0; i < instructions.length; i++) {
  55. const squadIx = await wrapAsRemoteInstruction(
  56. squad,
  57. vault,
  58. newProposalAddress,
  59. instructions[i],
  60. i,
  61. wormholeAddress
  62. );
  63. txToSend.push(
  64. new Transaction().add(
  65. await squad.buildAddInstruction(
  66. vault,
  67. newProposalAddress,
  68. squadIx.instruction,
  69. i + 1,
  70. squadIx.authorityIndex,
  71. squadIx.authorityBump,
  72. squadIx.authorityType
  73. )
  74. )
  75. );
  76. }
  77. } else {
  78. for (let i = 0; i < instructions.length; i++) {
  79. txToSend.push(
  80. new Transaction().add(
  81. await squad.buildAddInstruction(
  82. vault,
  83. newProposalAddress,
  84. instructions[i],
  85. i + 1
  86. )
  87. )
  88. );
  89. }
  90. }
  91. txToSend.push(
  92. new Transaction().add(
  93. await squad.buildActivateTransaction(vault, newProposalAddress)
  94. )
  95. );
  96. txToSend.push(
  97. new Transaction().add(
  98. await squad.buildApproveTransaction(vault, newProposalAddress)
  99. )
  100. );
  101. await new AnchorProvider(
  102. squad.connection,
  103. squad.wallet,
  104. AnchorProvider.defaultOptions()
  105. ).sendAll(
  106. txToSend.map((tx) => {
  107. return { tx, signers: [] };
  108. })
  109. );
  110. return newProposalAddress;
  111. }
  112. /**
  113. * Wrap `instruction` in a Wormhole message for remote execution
  114. * @param squad Squads client
  115. * @param vault vault public key (the id of the multisig where these instructions should be proposed)
  116. * @param proposalAddress address of the proposal
  117. * @param instruction instruction to be wrapped in a Wormhole message
  118. * @param instructionIndex index of the instruction within the proposal
  119. * @param wormholeAddress address of the Wormhole bridge
  120. * @returns an instruction to be proposed
  121. */
  122. export async function wrapAsRemoteInstruction(
  123. squad: Squads,
  124. vault: PublicKey,
  125. proposalAddress: PublicKey,
  126. instruction: TransactionInstruction,
  127. instructionIndex: number,
  128. wormholeAddress: PublicKey
  129. ): Promise<SquadInstruction> {
  130. const emitter = squad.getAuthorityPDA(vault, 0);
  131. const [messagePDA, messagePdaBump] = getIxAuthorityPDA(
  132. proposalAddress,
  133. new BN(instructionIndex),
  134. squad.multisigProgramId
  135. );
  136. const provider = new AnchorProvider(
  137. squad.connection,
  138. squad.wallet,
  139. AnchorProvider.defaultOptions()
  140. );
  141. const wormholeProgram = createWormholeProgramInterface(
  142. wormholeAddress,
  143. provider
  144. );
  145. const buffer = new ExecutePostedVaa("pythnet", [instruction]);
  146. const accounts = getPostMessageAccounts(
  147. wormholeAddress,
  148. emitter,
  149. emitter,
  150. messagePDA
  151. );
  152. return {
  153. instruction: await wormholeProgram.methods
  154. .postMessage(0, buffer, 0)
  155. .accounts(accounts)
  156. .instruction(),
  157. authorityIndex: instructionIndex,
  158. authorityBump: messagePdaBump,
  159. authorityType: "custom",
  160. };
  161. }