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.
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`
.
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?
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.
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
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()?).
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.
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.
The Inspect
button redirects us to Solana Explorer
where we can review the transaction from there.
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.
See at https://chalda.cz/solana-tx/
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 |
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'
}
]
}
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.