@@ -165,7 +165,8 @@ program
165
165
"multisig wallet secret key filepath" ,
166
166
"keys/key.json"
167
167
)
168
- . option ( "-p, --payload <hex-string>" , "payload to sign" , "0xdeadbeef" )
168
+ . option ( "-f, --file <filepath>" , "Path to a json file with instructions" )
169
+ . option ( "-p, --payload <hex-string>" , "Wormhole VAA payload" )
169
170
. option ( "-s, --skip-duplicate-check" , "Skip checking duplicates" )
170
171
. action ( async ( options ) => {
171
172
const cluster : Cluster = options . cluster ;
@@ -176,55 +177,103 @@ program
176
177
options . ledgerDerivationChange ,
177
178
options . wallet
178
179
) ;
179
- const wormholeTools = await loadWormholeTools ( cluster , squad . connection ) ;
180
180
181
- if ( ! options . skipDuplicateCheck ) {
182
- const activeProposals = await getActiveProposals (
183
- squad ,
184
- CONFIG [ cluster ] . vault
185
- ) ;
186
- const activeInstructions = await getManyProposalsInstructions (
181
+ if ( options . payload && options . file ) {
182
+ console . log ( "Only one of --payload or --file must be provided" ) ;
183
+ return ;
184
+ }
185
+
186
+ if ( options . payload ) {
187
+ const wormholeTools = await loadWormholeTools ( cluster , squad . connection ) ;
188
+
189
+ if ( ! options . skipDuplicateCheck ) {
190
+ const activeProposals = await getActiveProposals (
191
+ squad ,
192
+ CONFIG [ cluster ] . vault
193
+ ) ;
194
+ const activeInstructions = await getManyProposalsInstructions (
195
+ squad ,
196
+ activeProposals
197
+ ) ;
198
+
199
+ const msAccount = await squad . getMultisig ( CONFIG [ cluster ] . vault ) ;
200
+ const emitter = squad . getAuthorityPDA (
201
+ msAccount . publicKey ,
202
+ msAccount . authorityIndex
203
+ ) ;
204
+
205
+ for ( let i = 0 ; i < activeProposals . length ; i ++ ) {
206
+ if (
207
+ hasWormholePayload (
208
+ squad ,
209
+ emitter ,
210
+ activeProposals [ i ] . publicKey ,
211
+ options . payload ,
212
+ activeInstructions [ i ] ,
213
+ wormholeTools
214
+ )
215
+ ) {
216
+ console . log (
217
+ `❌ Skipping, payload ${ options . payload } matches instructions at ${ activeProposals [ i ] . publicKey } `
218
+ ) ;
219
+ return ;
220
+ }
221
+ }
222
+ }
223
+
224
+ await createWormholeMsgMultisigTx (
225
+ options . cluster ,
187
226
squad ,
188
- activeProposals
227
+ CONFIG [ cluster ] . vault ,
228
+ options . payload ,
229
+ wormholeTools
189
230
) ;
231
+ }
190
232
191
- const msAccount = await squad . getMultisig ( CONFIG [ cluster ] . vault ) ;
192
- const emitter = squad . getAuthorityPDA (
193
- msAccount . publicKey ,
194
- msAccount . authorityIndex
233
+ if ( options . file ) {
234
+ const instructions : SquadInstruction [ ] = loadInstructionsFromJson (
235
+ options . file
195
236
) ;
196
237
197
- for ( let i = 0 ; i < activeProposals . length ; i ++ ) {
198
- if (
199
- hasWormholePayload (
200
- squad ,
201
- emitter ,
202
- activeProposals [ i ] . publicKey ,
203
- options . payload ,
204
- activeInstructions [ i ] ,
205
- wormholeTools
206
- )
207
- ) {
208
- console . log (
209
- `❌ Skipping, payload ${ options . payload } matches instructions at ${ activeProposals [ i ] . publicKey } `
210
- ) ;
211
- return ;
238
+ if ( ! options . skipDuplicateCheck ) {
239
+ const activeProposals = await getActiveProposals (
240
+ squad ,
241
+ CONFIG [ cluster ] . vault
242
+ ) ;
243
+ const activeInstructions = await getManyProposalsInstructions (
244
+ squad ,
245
+ activeProposals
246
+ ) ;
247
+
248
+ for ( let i = 0 ; i < activeProposals . length ; i ++ ) {
249
+ if (
250
+ areEqualOnChainInstructions (
251
+ instructions . map ( ( ix ) => ix . instruction ) ,
252
+ activeInstructions [ i ]
253
+ )
254
+ ) {
255
+ console . log (
256
+ `❌ Skipping, instructions from ${ options . file } match instructions at ${ activeProposals [ i ] . publicKey } `
257
+ ) ;
258
+ return ;
259
+ }
212
260
}
213
261
}
214
- }
215
262
216
- await createWormholeMsgMultisigTx (
217
- options . cluster ,
218
- squad ,
219
- CONFIG [ cluster ] . vault ,
220
- options . payload ,
221
- wormholeTools
222
- ) ;
263
+ const txKey = await createTx ( squad , CONFIG [ cluster ] . vault ) ;
264
+ await addInstructionsToTx (
265
+ cluster ,
266
+ squad ,
267
+ CONFIG [ cluster ] . vault ,
268
+ txKey ,
269
+ instructions
270
+ ) ;
271
+ }
223
272
} ) ;
224
273
225
274
program
226
275
. command ( "verify" )
227
- . description ( "Verify given wormhole transaction has the given payload" )
276
+ . description ( "Verify given proposal matches a payload" )
228
277
. option ( "-c, --cluster <network>" , "solana cluster to use" , "devnet" )
229
278
. option ( "-l, --ledger" , "use ledger" )
230
279
. option (
@@ -240,7 +289,8 @@ program
240
289
"multisig wallet secret key filepath" ,
241
290
"keys/key.json"
242
291
)
243
- . requiredOption ( "-p, --payload <hex-string>" , "expected payload" )
292
+ . option ( "-p, --payload <hex-string>" , "expected wormhole payload" )
293
+ . option ( "-f, --file <filepath>" , "Path to a json file with instructions" )
244
294
. requiredOption ( "-t, --tx-pda <address>" , "transaction PDA" )
245
295
. action ( async ( options ) => {
246
296
const cluster : Cluster = options . cluster ;
@@ -251,6 +301,12 @@ program
251
301
options . ledgerDerivationChange ,
252
302
options . wallet
253
303
) ;
304
+
305
+ if ( options . payload && options . file ) {
306
+ console . log ( "Only one of --payload or --file must be provided" ) ;
307
+ return ;
308
+ }
309
+
254
310
const wormholeTools = await loadWormholeTools ( cluster , squad . connection ) ;
255
311
256
312
let onChainInstructions = await getProposalInstructions (
@@ -264,21 +320,42 @@ program
264
320
msAccount . authorityIndex
265
321
) ;
266
322
267
- if (
268
- hasWormholePayload (
269
- squad ,
270
- emitter ,
271
- new PublicKey ( options . txPda ) ,
272
- options . payload ,
273
- onChainInstructions ,
274
- wormholeTools
275
- )
276
- ) {
277
- console . log (
278
- "✅ This proposal is verified to be created with the given payload."
323
+ if ( options . payload ) {
324
+ if (
325
+ hasWormholePayload (
326
+ squad ,
327
+ emitter ,
328
+ new PublicKey ( options . txPda ) ,
329
+ options . payload ,
330
+ onChainInstructions ,
331
+ wormholeTools
332
+ )
333
+ ) {
334
+ console . log (
335
+ "✅ This proposal is verified to be created with the given payload."
336
+ ) ;
337
+ } else {
338
+ console . log ( "❌ This proposal does not match the given payload." ) ;
339
+ }
340
+ }
341
+
342
+ if ( options . file ) {
343
+ const instructions : SquadInstruction [ ] = loadInstructionsFromJson (
344
+ options . file
279
345
) ;
280
- } else {
281
- console . log ( "❌ This proposal does not match the given payload." ) ;
346
+
347
+ if (
348
+ areEqualOnChainInstructions (
349
+ instructions . map ( ( ix ) => ix . instruction ) ,
350
+ onChainInstructions
351
+ )
352
+ ) {
353
+ console . log (
354
+ "✅ This proposal is verified to be created with the given instructions."
355
+ ) ;
356
+ } else {
357
+ console . log ( "❌ This proposal does not match the given instructions." ) ;
358
+ }
282
359
}
283
360
} ) ;
284
361
@@ -730,6 +807,24 @@ async function createWormholeMsgMultisigTx(
730
807
) ;
731
808
}
732
809
810
+ function areEqualOnChainInstructions (
811
+ instructions : TransactionInstruction [ ] ,
812
+ onChainInstructions : InstructionAccount [ ]
813
+ ) : boolean {
814
+ if ( instructions . length != onChainInstructions . length ) {
815
+ console . debug (
816
+ `Proposals have a different number of instructions ${ instructions . length } vs ${ onChainInstructions . length } `
817
+ ) ;
818
+ return false ;
819
+ } else {
820
+ return lodash
821
+ . range ( 0 , instructions . length )
822
+ . every ( ( i ) =>
823
+ isEqualOnChainInstruction ( instructions [ i ] , onChainInstructions [ i ] )
824
+ ) ;
825
+ }
826
+ }
827
+
733
828
function hasWormholePayload (
734
829
squad : Squads ,
735
830
emitter : PublicKey ,
@@ -980,3 +1075,25 @@ async function removeMember(
980
1075
squadIxs
981
1076
) ;
982
1077
}
1078
+
1079
+ function loadInstructionsFromJson ( path : string ) : SquadInstruction [ ] {
1080
+ const inputInstructions = JSON . parse ( fs . readFileSync ( path ) . toString ( ) ) ;
1081
+ const instructions : SquadInstruction [ ] = inputInstructions . map (
1082
+ ( ix : any ) : SquadInstruction => {
1083
+ return {
1084
+ instruction : new TransactionInstruction ( {
1085
+ programId : new PublicKey ( ix . program_id ) ,
1086
+ keys : ix . accounts . map ( ( acc : any ) => {
1087
+ return {
1088
+ pubkey : new PublicKey ( acc . pubkey ) ,
1089
+ isSigner : acc . is_signer ,
1090
+ isWritable : acc . is_writable ,
1091
+ } ;
1092
+ } ) ,
1093
+ data : Buffer . from ( ix . data , "hex" ) ,
1094
+ } ) ,
1095
+ } ;
1096
+ }
1097
+ ) ;
1098
+ return instructions ;
1099
+ }
0 commit comments