Executing

Transactions that are in the ExecuteReady state can be executed by a multisig member. In order to execute the transaction, all of the accounts specified by the attached transactions instructions must be provided, and the parameter for the execute must include an array of accounts that map appropriately. For a more detailed explanation refer to the README in the repository. Accounts should be ordered but also unique - this helps maximize the number of instructions that can be attached to a transaction.

The account array parameter provided should be a list that correlates to the attached accounts referenced in the execute method. The list should follow the format of [ix1Account, ix1Program, ix1Account1, ix1Account2...] where each array item is the index of the accounts given to the execute method.

To use our previous example of transferring out of the vault, we first fetch the transaction account and retrieve all the attached instructions.

// retrieve the transaction we wish to execute
const transactionAccount = await squadsProgram.account.msTransaction.fetch(transaction);

// retrieve all of the attached instructions
const instructions = await Promise.all([...new Array(transactionAccount.instructionIndex)].map(async (a, i) => {
        const ixIndex = new anchor.BN(i + 1);
        const [ix] = await getIxPDA(transaction, ixIndex, squadsProgram.programId);
        const ixAccount = await squadsProgram.account.msInstruction.fetch(ix);
        return {pubkey: ix, ixAccountData};
}));

Now that we have all of the instruction accounts needed, we can put together our array list and specify the accounts needed for the execute method:

// map all of the instructions and their keys into the following pattern
// [ix, program, ...remaining_accounts]
const ixKeys = instructions.map(({pubkey, ixAccountData}) => {
        const formattedKeys = ixAccount.keys.map((ixKey) => {
            return {
                pubkey: ixKey.pubkey,
                isSigner: false,
                isWritable: ixKey.isWritable
            };
        });

        return [
            {pubkey, isSigner: false, isWritable: false},
            {pubkey: ixAccountData.programId, isSigner: false, isWritable: false},
            ...formattedKeys
        ];
        
    //squash them into a single list
    }).reduce((p, c) => p.concat(c), [])

Now we can remove duplicate keys and create the array parameter needed for the execute method:

// these will be the accounts specified for the execute method
//  [ix ix_account, ix program_id, key1, key2 ...]
const keysUnique = ixKeys.reduce((prev, curr) => {
    const inList = prev.findIndex(a => a.pubkey.toBase58() === curr.pubkey.toBase58());
    // if its already in the list, and has same write flag
    if (inList >= 0 && prev[inList].isWritable === curr.isWritable) {
        return prev;
    } else {
        prev.push({pubkey: curr.pubkey, isWritable: curr.isWritable, isSigner: curr.isSigner});
        return prev;
    }
}, []);

// this will be the parameter for the execute
const keyIndexArray = ixKeys.map(a => {
    return keysUnique.findIndex(k => {
        if (k.pubkey.toBase58() === a.pubkey.toBase58() && k.isWritable === a.isWritable) {
            return true;
        }
        return false;
    });
});

Now you can call the execute method with the proper arguments:

const executeIx = await program.methods.executeTransaction(Buffer.from(keyIndexArray))
        .accounts({multisig, transaction, member: wallet.publicKey})
        .instruction();

// attache the formatted unique keys
executeIx.keys = executeIx.keys.concat(keysUnique);

// execute the transaction
const execute = new anchor.web3.Transaction();
execute.add(executeIx);
wallet.sendAndConfirm(execute);

Last updated