All Articles

Story of a Solana multisig signature

spl governance story

A tale of an ordinary developer who receives a task, and during the process, he learns various things. This story revolves around Solana multisig configured with SPL Governance, and the creation of an arbitrary transaction that multisig members sign and execute.

SPL Governance as multisig manager

The SPL Governance is a Solana program that runs DAO management. One can create his own DAO instance in the system and use the SPL Governance for community to manage the DAO. But another possible usage is to use it as a multisig tooling. For that one creates a new DAO where no community is involved and only particular wallets has permission to proceed with signatures. This is referred as council managed. There are other offerings for multisig management like Squads protocol. While a benefit of the SPL Governance system is that’s part of the Solana Program Library and thus it’s developed by people from Solana Labs.

This is simple as go to https://app.realms.today/ (when testing the your configuration add ?cluster=devnet for devnet deployment), click at blue button Create DAO and choose the Multi-Signature Wallet. The wizard goes with you through few steps where one defines what wallet keys will be part of the multisig management (members of the multisig may be still added or removed later) and then the multisig DAO is created.

Note
For technical details about the SPL Governance system I recommend to read through the SPL Governance Deep Dive article.

For the purpose of the showcase in this blog post I have created a DAO Solana multisig signature with one member of address FwkjePeCG5CjD8uYsHh3hvj6X26KnNh2UrpZSpDiNbfh`.

one member

After the multisig DAO is created we can start to provide a transactions that should be signed by the multisig members. Any actions that the multisig should sign have to be defined as a Proposal that contains transactions. When the proposal is confirmed by multisig members then anybody may be execute. At the main page of the DAO one click at ⊕ New Proposal. When the member wallet is connected a new Proposal can be created and then multisig owners can vote for or against it. The SPL Governance system provides a list of common `Instruction`s types that is integrated to UI and easily set up. Those are things as sending funds or working with token accounts. But what if my custom contract contains some admin abilities that I need to manage? How can I create a transaction that contains an arbitrary data?

SPL Governance workflow

Before we talk about custom transactions for SPL Governance let’s briefly discuss the Proposal workflow in the SPL Governance system. The Proposal is a unit that represents "an action" to be processed. The "action" is constituted as a transaction that can be executed when acknowledged by all multisig members.

The SPL Governance Proposal workflow is first to create a Proposal with one or several instructions. Multisig members votes for the proposal. When the defined threshold of votes is exceeded then the instructions that constitute the Proposal can be executed. The threshold is defined as percentage per a Governance that represent the multisig wallet address. This fact is important from the perspective of the multisig signature and administering the admin rights in the contract. It’s the Governance Wallet address that needs to be defined as owner of the funds to be transferred or and administrator authority within your contract. It’s the Governance Wallet address that the SPL Governance uses to sign the transactions on behalf of the acknowledgment of the multisig members.

The Governance configures not only the threshold but also the time of the Proposal voting. When the time exceeds the Proposal and not enough Yes votes were collected, then the proposal is considered as failed and can’t be voted anymore.

Either, if the Proposal succeeds or not when finished can be Finalized and parked at the final state for the SPL system recognizing it as a finished matter.

SPL Governance Custom Transaction

For anybody can create a Proposal with whatever transaction content there is Instruction type Common: Execute Custom Instruction. The transaction can be created with UI for anybody having the keys for the council power at the address of the DAO https://app.realms.today/dao/2EogxMvrtTr8ccA4oQcAGfS8vKbDhYTANA12LW3fqUy3/proposal/new?cluster=devnet

custom transaction proposal

The field Governance defines the multisig signature key that the transaction will be signed with. Hold up time (days) can be defined with 0 if we don’t want to delayed the instruction execution after Proposal acknowledgment. The Instruction field has to be filled with Base64 encoded serialized Solana instruction…​

But wait, where to get it?

Basically, this can be whatever transaction that is encoded to base64 of borsh(!) serialized data that contains program id, account keys and call data

[
  InstructionData,
  {
    kind: 'struct',
    fields: [
      ['programId', 'pubkey'],
      ['accounts', [AccountMetaData]],
      ['data', ['u8']],
    ],
  }
]

When we work with Typescript we can use the Oyster SPL Governance SDK that provides the function serializeInstructionToBase64 that takes an instruction and provides the encoded form that can be copy-pasted into the field.

WARN: The SPL Governance system Oyster SDK is deprecated and the new SDK is being developed. The new SDK is based on IDL, see at https://www.npmjs.com/package/governance-idl-sdk

When we work with Rust we can use the bincode or serde to serialize the transaction data.

If we work with Rust we need to transform the Transaction structure in the required format and print it. When using bincode then one can use the way provided in Anchor CLI in function print_idl_instruction (included as part of the PR CLI anchor idl command to use dump base64 for set-buffer and close) or in case of usage serde then prepare the struct that align with the expected format. It could be for example as of struct TransactionInstruction that implements From for Instruction and can be used like base64::encode(ix.into().try_to_vec()?).

Contract example with admin authority

To show the deployment I use a simple example contract Simple admin under v2 branch (the first version of the contract v1 was used as example in the article Decoding Solana data accounts).

The contract works on the simple base where admin has to sign the PrintMessage instruction. When signed then instruction is successfully executed which prints message into the transaction log and saves it into a separate solana account. The created admin account 3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh configures the admin being the multisig governance wallet GjYBMcRv4JzkgqCfeUKQMs7KygkFvgLpGzFbQ7SihGJ8.

With that the PrintMessage instruction has to be signed with SPL governance Proposal acknowledged by members of the multisig. Obviously the Governance UI does list the simple-admin instructions in the listing and we need to provide the base64 encoded transaction by ourselves. The simple-admin contract provides a CLI command for that purpose. It can generate the base64 format of the PrintMessage transaction.

First, let’s re-check what’s setup within the contract.

git clone https://github.com/ochaloup/simple-admin -b v2
cd simple-admin

pnpm install
# check content of the simple account
pnpm cli -ud show -a 3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh
{
  programId: 'sa4ihCaRZKuYZtY4fcdnNqx9vx9Lc6KELrL2nBkzNn2',
  data: [
    {
      publicKey: '3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh',
      admin: 'GjYBMcRv4JzkgqCfeUKQMs7KygkFvgLpGzFbQ7SihGJ8',
      printCallCount: '0',
      createdAccountNextIndex: 0
    }
  ]
}

The content of the simple-account says that only signature of the governance wallet GjYBMcRv4JzkgqCfeUKQMs7KygkFvgLpGzFbQ7SihGJ8 permits to execute the operation. When we try to execute the transaction signed by the default wallet (~/.config/solana/id.json) an error is emitted.

# trying to execute the print-message with default solana wallet
pnpm cli -ud print-message  --message 'gm gm' 3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh
> [...] ERROR (377647): Admin CUuLjSEx7q3AB3sRGn3sMJBsSNTmULwowMGUh6NdsxQD is not an admin of simple account 3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh > (admin is GjYBMcRv4JzkgqCfeUKQMs7KygkFvgLpGzFbQ7SihGJ8)

That’s not what we want and thus we follow the the original plan to use the governance wallet signature.

# get base64 transaction data to be used as custom transaction
pnpm cli -ud print-message --print-only --rent-payer GjYBMcRv4JzkgqCfeUKQMs7KygkFvgLpGzFbQ7SihGJ8 --admin GjYBMcRv4JzkgqCfeUKQMs7KygkFvgLpGzFbQ7SihGJ8 --message 'gm gm' 3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh
> Instructions:
>   DPRtGY504O6TnBjrarIUv9xLcAC17A/DMHqwT2cgX78FAAAAKaTrO1Ty3tvwfW/rnwokf8rtF+EgRM3z1Nxd+oTUD5gAAenE85m/zh0eB1j8UHbJDkYK+b+mqI9psHckuV6lrgRtAQCYBSVJmeyBwE67H9wKHeiNwzR47QJ0XgYHJjS6rEJoJQAB6cTzmb/OHR4HWPxQdskORgr5v6aoj2mwdyS5XqWuBG0BAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAAAARg8IXY5SF1kFAAAAZ20gZ20=
> [...] INFO (377533): Message 'gm gm' successfully printed for account 3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh

Let’s elaborate on this action a little bit.

We used the --print-only argument that instead of execution of the transaction to cluster uses the Oyster function to print the transaction in base64 format.

An important thing to notice is that we provided the --rent-payer argument. The PrintMessage instruction creates a new account where a message is saved into. The account has to be created with deposit of the rent exempt that has to be payed by somebody. For that we need the CLI provides a way to pass a pubkey here. We generated base64 transaction data which is not signed, neither there is a blockhash encoded. The signatures are added at time of the execution. The SPL Governance UI uses the wallet signature provided by the user that clicks button Execute in browser. The browser wallet is used to pay the transaction fee. That’s an obligatory part. This signature can be used for any other purposes as well, for example for rent payment. If we create the base64 transaction with rent payer pubkey of a wallet then only that particular wallet is then able to execute transactions from the acknowledged Proposal. As rent payer we used again the address of the governance wallet. We pre-funded the governance wallet with some SOLs beforehand. As the SPL Governance system provides the transaction execution always with the signature of the governance wallet the pre-funded SOLs can be used for example as payment for the rent.

With instruction prepared in base64 format we can create a Proposal of custom transaction of this content.

multisig proposal creation

Before we create the Proposal with the transaction we can check the if transaction content is correct and can be run. With using Preview Transaction button the transaction will be simulated.

multisig proposal creation simulation

The Inspect button redirects us to Solana Explorer where we can review the transaction from there.

solana-tx tooling

When we have the base64 transaction data we can use the solana-tx tooling to decode the transaction content. It’s a simple web based app that I develop for the purpose of the transaction inspection.

The tooling allows to paste the base64 transaction data and see the content of the transaction. The tool uses ExplorerKit to decode the transaction content and if possible to decode it and to show the details.

You can try to paste the base64 transaction as generated by --print-only argument of the simple-admin CLI. Select the devnet (as the simple-admin program is deployed at Solana devnet) and the tool will show the content of the transaction after clicking on Show deserialized format.

Note
You can use the tooling to deserialize content of events as well at https://chalda.cz/solana-tx/event

Finishing the story

When we finish with having the Proposal created, the Proposal is acknowledged by multisig members (the more that threshold of voters voted Yes on the Proposal) and the Proposal transaction was executed, then a print-account is created with content of a message. The simple-admin CLI allows us to list all print-accounts belonging to a particular simple-admin account.

pnpm cli -ud show --address 3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh --print-address
{
  programId: 'sa4ihCaRZKuYZtY4fcdnNqx9vx9Lc6KELrL2nBkzNn2',
  data: [
    {
      publicKey: 'BERXDKKk7xmArJXHxDdb9rRqLdd7TRsGXJyh6tj9ZTit',
      simpleAccount: '3oZa6pjVpC5m1gaKYnXWoxahrBQt2dCsapBSiDAMBWoh',
      message: 'gm gm'
    }
  ]
}

Summary

With that we went through the SPL Governance Multisig signature management, we talked about Proposal creation and risk connected with Proposal’s transaction using signatures different to a wallet (fee-payer) and we introduced a tool to list SPL Gov.

Published Oct 8, 2024

Developer notes.