Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions xc-admin/packages/xc-admin-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./multisig";
export * from "./propose";
export * from "./governance_payload";
176 changes: 176 additions & 0 deletions xc-admin/packages/xc-admin-common/src/propose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import Squads, { getIxAuthorityPDA, getTxPDA } from "@sqds/mesh";
import {
PublicKey,
Transaction,
TransactionInstruction,
} from "@solana/web3.js";
import { BN } from "bn.js";
import { AnchorProvider } from "@project-serum/anchor";
import {
createWormholeProgramInterface,
getPostMessageAccounts,
} from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
import { encodeExecutePostedVaa } from "./governance_payload/ExecutePostedVaa";

type SquadInstruction = {
instruction: TransactionInstruction;
authorityIndex?: number;
authorityBump?: number;
authorityType?: string;
};

/**
* Propose an array of `TransactionInstructions` as a proposal
* @param squad Squads client
* @param vault vault public key (the id of the multisig where these instructions should be proposed)
* @param instructions instructions that will be proposed
* @param remote whether the instructions should be executed in the chain of the multisig or remotely on Pythnet
* @returns the newly created proposal's pubkey
*/
export async function proposeInstructions(
squad: Squads,
vault: PublicKey,
instructions: TransactionInstruction[],
remote: boolean,
wormholeAddress?: PublicKey
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make a class like MultisigWrapper to encapsulate some of the common arguments (squads, wormholeAddress, etc)

): Promise<PublicKey> {
const msAccount = await squad.getMultisig(vault);
let txToSend: Transaction[] = [];

const createProposal = new Transaction().add(
await squad.buildCreateTransaction(
msAccount.publicKey,
msAccount.authorityIndex,
msAccount.transactionIndex + 1
)
);
const newProposalAddress = getTxPDA(
vault,
new BN(msAccount.transactionIndex + 1),
squad.multisigProgramId
)[0];
txToSend.push(createProposal);

if (remote) {
if (!wormholeAddress) {
throw new Error("Need wormhole address");
}
for (let i = 0; i < instructions.length; i++) {
const squadIx = await wrapAsRemoteInstruction(
squad,
vault,
newProposalAddress,
instructions[i],
i,
wormholeAddress
);
txToSend.push(
new Transaction().add(
await squad.buildAddInstruction(
vault,
newProposalAddress,
squadIx.instruction,
i,
squadIx.authorityIndex,
squadIx.authorityBump,
squadIx.authorityType
)
)
);
}
} else {
for (let i = 0; i < instructions.length; i++) {
txToSend.push(
new Transaction().add(
await squad.buildAddInstruction(
vault,
newProposalAddress,
instructions[i],
i
)
)
);
}
}

txToSend.push(
new Transaction().add(
await squad.buildActivateTransaction(vault, newProposalAddress)
)
);
txToSend.push(
new Transaction().add(
await squad.buildApproveTransaction(vault, newProposalAddress)
)
);

await new AnchorProvider(
squad.connection,
squad.wallet,
AnchorProvider.defaultOptions()
).sendAll(
txToSend.map((tx) => {
return { tx, signers: [] };
})
);
return newProposalAddress;
}

/**
* Wrap `instruction` in a Wormhole message for remote execution
* @param squad Squads client
* @param vault vault public key (the id of the multisig where these instructions should be proposed)
* @param proposalAddress address of the proposal
* @param instruction instruction to be wrapped in a Wormhole message
* @param instructionIndex index of the instruction within the proposal
* @param wormholeAddress address of the Wormhole bridge
* @returns an instruction to be proposed
*/
export async function wrapAsRemoteInstruction(
squad: Squads,
vault: PublicKey,
proposalAddress: PublicKey,
instruction: TransactionInstruction,
instructionIndex: number,
wormholeAddress: PublicKey
): Promise<SquadInstruction> {
const emitter = squad.getAuthorityPDA(vault, 0);

const [messagePDA, messagePdaBump] = getIxAuthorityPDA(
proposalAddress,
new BN(instructionIndex),
squad.multisigProgramId
);

const provider = new AnchorProvider(
squad.connection,
squad.wallet,
AnchorProvider.defaultOptions()
);
const wormholeProgram = createWormholeProgramInterface(
wormholeAddress,
provider
);

const buffer = encodeExecutePostedVaa({
targetChainId: "pythnet",
instructions: [instruction],
});

const accounts = getPostMessageAccounts(
wormholeAddress,
emitter,
emitter,
messagePDA
);

return {
instruction: await wormholeProgram.methods
.postMessage(0, buffer, 0)
.accounts(accounts)
.instruction(),
authorityIndex: instructionIndex,
authorityBump: messagePdaBump,
authorityType: "custom",
};
}