Skip to main content

Governance interaction

Introduction

The interaction with the governance system smart contract is done through correctly formatted transactions to submit actions and through the usage of the vm-query REST API calls for reading the proposal(s) status.

Creating a proposal

The proposal creation transaction has the following parameters:

GovernanceProposalTransaction {
Sender: <account address of the wallet that creates the proposal>
Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla
Value: 500 EGLD
GasLimit: 51000000
Data: "proposal" +
"@<identifier>" +
"@<starting epoch in hex format>" +
"@<ending epoch in hex format>"
}

The value parameter represents the mandatory proposal submission deposit of 500 EGLD.

The proposal identifier is a hex string containing exactly 40 characters. This is the git commit hash on which the proposal is made.

The starting & ending epochs should be an even-length hex string containing the starting epoch and the ending epoch. During this time frame, lasting at most 10 days, the votes can be cast.

Note: When providing the starting epoch, it should be taken into consideration that the governance contract enforcing a configured maximum gap of 30 epochs between the current epoch and the proposal’s starting epoch.

After issuing the proposal, there is a log event generated having the proposal identifier that will contain the following encoded topics:

  • nonce as encoded integer which uniquely identifies the proposals
  • identifier is the provided 40 hex characters long identifier
  • start epoch as encoded integer for the starting epoch
  • end epoch as encoded integer for the ending epoch

Voting a proposal using the direct staked or delegation-system amount

Any wallet that has staked EGLD (either direct staked or through the delegation sub-system) can cast a vote for a proposal.

GovernanceVoteTransaction {
Sender: <account address of the wallet that will vote the proposal>
Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla
Value: 0 EGLD
GasLimit: 6000000
Data: "vote" +
"@<nonce>" +
"@<vote_type>"
}

The value parameter for the voting transaction must be set to 0, since the function is non-payable.

The nonce is the hex encoded value of the proposal's unique nonce and the vote_type can be one of the following values:

  • for Yes: 796573;
  • for No: 6e6f;
  • for Abstain: 6162737461696e;
  • for Veto: 7665746f.

The vote value for the account that will vote a proposal is the sum of all staked values along with the sum of all delegated values in the delegation sub-system.

After issuing the vote, a log event is generated containing the proposal identifier and the following encoded topics:

  • nonce as encoded integer which uniquely identifies the proposals
  • vote_type as encoded string representing the vote
  • total_stake total staked EGLD for the sender address
  • total_voting_power total available voting power for the sender address

Example response:

{
"returnData": [
"WQ==", (nonce: hex = 59 -> decimal = 89)
"eWVz", (vote_type: hex = "79657" -> "yes")
"BxRA1dqcrTxyQw==", (total_stake: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD)
"BxRA1dqcrTxyQw==", (total_voting_power: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD)
]
}

Voting a proposal through smart contracts (delegation voting)

Whenever we deal with a smart contract that delegated through the delegation sub-system or owns staked nodes it is the out of scope of the metachain's governance contract to track each address that sent EGLD how much is really staked (if any EGLD is staked).

That is why we offered an endpoint to the governance smart contact that can be called only by a shard smart contract and the governance contract will record the address provided, the vote type and vote value.

This is very useful whenever implementing liquid-staking-like smart contracts. The liquid-staking contract knows the balance for each user, so it will delegate the call to the governance contract providing the corresponding voting power.

important

The maximum value for the staked value & the voting power for the liquid-staking contract is known by the governance contract, so it can not counterfeit the voting. If, due to a bug the liquid-staking contract allows, for example, for a user to vote with a larger quota, the liquid-staking contract will deplete its maximum allowance making other voting transactions (on his behalf) to fail.

The user that delegated through a liquid-staking-like contract in order to vote on a proposal, sends a transaction to the liquid-staking-like contract. The smart contract then creates the GovernanceVoteThroughDelegationTransaction based on the user’s transaction inputs, forwarding the vote and the computed voting power to the governance contract.

GovernanceVoteThroughDelegationTransaction {
Sender: <smart contract address>
Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla
Value: 0 EGLD
GasLimit: 51000000
Data: "delegateVote" +
"@<nonce>" +
"@<vote_type>" +
"@<account address handled by the smart contract>" +
"@<vote_balance>"
}

The value parameter for the voting transaction must be set to 0, since the function is non-payable.

The nonce is the hex encoded value of the proposal's unique nonce and the vote_type can be one of the following values:

  • for Yes: 796573;
  • for No: 6e6f;
  • for Abstain: 6162737461696e;
  • for Veto: 7665746f.

The account address handled by the smart contract is the address handled by the smart contract that will delegate the vote towards the governance smart contract. This address will be recorded for casting the vote.

The vote_balance is the amount of stake the address has in the smart contract. The governance contract will "believe" that this is the right amount as it impossible to verify the information. The balance will diminish the total voting power the smart contract has.

After issuing the vote, a log event is generated containing the proposal identifier and the following encoded topics:

  • nonce as encoded integer which uniquely identifies the proposals
  • vote_type as encoded string representing the vote
  • voter account address handled by the smart contract
  • total_stake total staked EGLD for the sender address
  • total_voting_power total available voting power for the sender address

Example response:

{
"returnData": [
"WQ==", (nonce: hex = 59 -> decimal = 89)
"eWVz", (vote_type: hex = "79657" -> "yes")
"ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE=", (voter: hex = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" -> erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th)
"BxRA1dqcrTxyQw==", (total_stake: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD)
"BxRA1dqcrTxyQw==", (total_voting_power: hex = "071440d5da9cad3c7243" -> 33430172142116630458947 -> ~33430 EGLD)
]
}

Closing a proposal

A proposal can be closed by the issuer before the voting period starts but with an early closing fee equal to LostProposalFee. A proposal can also be closed by anyone in an epoch that is strictly higher than the end epoch value provided when the proposal was opened.

CloseProposalTransaction {
Sender: <account address>
Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla
Value: 0 EGLD
GasLimit: 51000000
Data: "closeProposal" +
"@<nonce>"
}

Rules for closing

  • The proposal can be closed by the issuer before the voting starts or by anyone after voting ended.
  • If the proposal passes → the full proposal fee is refunded.
  • If the proposal fails or is vetoed → the refund is reduced by the LostProposalFee.
  • Once a proposal is closed, it cannot be reopened.
  • Closing also finalizes the vote tally (the proposal is marked as Passed or not, based on the results).

Querying the status of a proposal

The status of any proposal can be queried at any time through vm-values/query REST API endpoints provided by the gateway/API.

https://<gateway>.multiversx.com/vm-values/query
{
"scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla",
"funcName": "viewProposal",
"args": ["<nonce-hex>"]
}
  • The argument nonce is the proposal nonce in hex format.
  • The response will contain the following json definition where all fields are base64-encoded:
{
"returnData": [
"<proposal_cost>", (amount locked by proposer)
"<commit_hash>", (unique identifier of the proposal)
"<nonce>", (proposal number)
"<issuer_address>", (address of the proposer)
"<start_epoch>", (epoch when voting starts)
"<end_epoch>", (epoch when voting ends)
"<quorum_stake>", (current quorum stake: sum of stake that participated)
"<yes_votes>", (total stake voting YES)
"<no_votes>", (total stake voting NO)
"<veto_votes>", (total stake vetoing the proposal)
"<abstain_votes>", (total stake abstaining)
"<proposal_closed true|false>",
"<proposal_passed true|false>"
]
}

Example response:

{
"returnData": [
"Gxrk1uLvUAAA", (proposal locked amount: 500 EGLD denominated = 500 * 10^18)
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABg==", (commit hash: 0x0000...006)
"AQ==", (nonce: 1)
"aj88GtqHy9ibm5ePPQlG4aqLhpgqsQWygoTppckLa4M=", (proposer address)
"bQ==", (starting epoch: 109)
"bg==", (ending epoch: 110)
"ntGU2xmyOMAAAA==", (quorum: 750000 EGLD denominated)
"", (yes votes: 0)
"", (no votes: 0)
"ntGU2xmyOMAAAA==", (veto votes: 750000 EGLD denominated)
"", (abstain votes: 0)
"dHJ1ZQ==", (proposal closed: true)
"ZmFsc2U=" (proposal passed: false)
]
}

Querying an address voting status (direct staking or system delegation)

The voting status of a certain address can be queried at any time by using the vm-values/query REST API endpoints provided by the gateway/API.

https://gateway.multiversx.com/vm-values/query

With the following payload:

{
"scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla",
"funcName": "viewDelegatedVoteInfo",
"args": ["<proposal_nonce-hex>", "<address>"]
}
  • proposal_nonce → the proposal identifier (nonce, hex-encoded).
  • address → the bech32 address of the account to check.

Note: The older function viewUserVoteHistory (which returned lists of proposal nonces) is now considered legacy. Use viewDelegatedVoteInfo for detailed voting power and stake information.

The response will contain the following json definition where all fields are base64-encoded:

{
"returnData": [
"<used_power>", (voting power used by this address on the proposal)
"<used_stake>", (stake associated with the used power)
"<total_power>", (total available voting power for the address)
"<total_stake>" (total stake considered in governance for the address)
]
}

Example for an address that voted:

{
"returnData": [
"Cg==", (used power: 100)
"ZAA=", (used stake: 100)
"A+g=", (total power: 1000)
"Gg4M=", (total stake: 1000)
]
}

In this example, the queried address voted on this proposal with 100 stake, which translated into 100 voting power. The proposal overall had 1000 total stake and 1000 total voting power recorded.

Example for an address that did not vote:

{
"returnData": [
"AA==", (used power: 0)
"AA==", (used stake: 0)
"A+g=", (total power: 1000)
"A+g=", (total stake: 1000)
]
}

Querying the voting power of an address

The voting power of a certain address can be queried at any time by using the vm-values/query REST API endpoints provided by the gateway/API.

https://gateway.multiversx.com/vm-values/query

With the following payload:

{
"scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla",
"funcName": "viewVotingPower",
"args": ["<hex-encoded address>"]
}

Here's an example of the response:

{
"returnData": [
"Q8M8GTdWSAAA" (1250000000000000000000 -> 1250 EGLD)
]
}

Querying the configuration of the Governance contract

The configuration of the Governance System Smart Contract can be queried at any time by using the vm-values/query REST API endpoints provided by the gateway/API.

https://gateway.multiversx.com/vm-values/query

With the following payload:

{
"scAddress": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrlllsrujgla",
"funcName": "viewConfig",
"args": []
}

The response will contain the following json definition where all fields are base64-encoded:

{
"returnData": [
"<proposal_fee>", (the amount of EGLD required to issue a new proposal)
"<lost_proposal_fee>", (the amount of EGLD that the issuer loses if the proposal fails)
"<min_quorum>", (the minimum percentage of total voting power required for a proposal to be considered valid)
"<min_veto_threshold>", (the minimum percentage of veto votes needed to reject a proposal and slash its fee)
"<min_pass_threshold>", (the minimum percentage of YES votes required for a proposal to pass)
"<last_proposal_nonce>" (the nonce of the last created proposal)
]
}

Here's how a response might look like:

{
"returnData": [
"NTAwMDAwMDAwMDAwMDAwMDAwMDAw", ("500000000000000000000" -> 500 EGLD)
"MTAwMDAwMDAwMDAwMDAwMDAwMDA=", ("10000000000000000000" -> 10 EGLD)
"MC4yMDAw", ("0.2000" -> 20%)
"MC4zMzAw", ("0.3300" -> 33%)
"MC42NjY3", ("0.6667" -> 66.67%)
"MA==", ("0" -> 0)
],
}