Skip to content

Commit 2a961d5

Browse files
authored
Xc admin/propose instructions (#463)
* Add encoder * Checkpoint * Refactor * Cleanup * Add comment * Cleanup
1 parent 097943f commit 2a961d5

File tree

2 files changed

+177
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)