Skip to main content

3. Submitting a Governance Proposal

The third step towards integrating Dual Rewards for a pool is to submit a governance proposal. This proposal must be presented to the Astral Assembly and obtain approval to move forward. The Dual Rewards Proposal carries three proposal messages:

  • The first proposal message requests the allocation of ASTRO emissions. ASTRO, Astroport's native token, forms a crucial part of the rewards system, enhancing the incentives for liquidity providers.
  • The second and third proposal messages seek to establish the connection with your generator proxy contract, facilitating the distribution of third-party tokens. This integration complements the ASTRO emissions, creating a balanced rewards ecosystem for LPs consisting of both ASTRO and third-party tokens – a combination that defines Astroport's unique 'dual rewards' model.
NOTE

Astroport's governance hub is currently hosted on Terra. As such, for deployments on other blockchains (Neutron, Injective, and Sei), you will need to submit your governance proposal to the Assembly contract on Terra. Upon approval, this proposal will trigger a cross-chain governance operation, which executes the approved proposal on the specific blockchain to which the message was addressed.

In the following sections, we will guide you through the steps of crafting and submitting a governance proposals for Dual Rewards.Sections 1-5 dive into the heart of the governance proposal process. These sections focus on crafting a proposal message dedicated to requesting ASTRO emissions. Sections 6-8 will guide you through the creation of a proposal message for connecting a generator proxy contract. The final section will bring it all together, focusing on the formulation and submission of the combined proposal message.

Steps

1. Construct send message

To initiate the submission of a proposal, a contract call needs to be executed that calls the send endpoint within the xASTRO token contract. The send operation requires three parameters:

  • contract: This refers to the address where xASTRO tokens are being dispatched. In this context, it should be the Assembly address.
  • amount: This specifies the quantity of xASTRO tokens to be sent or staked. For mainnet, it's currently 30,000 xASTRO (1000 xASTRO for testnet).
  • msg: This is a binary-encoded message that contains a contract call to the submit_proposal endpoint. This message will be discussed below.

_17
{
_17
"send": {
_17
"contract": assemblyAddress,
_17
"amount": "30000",
_17
"msg": toBase64(
_17
{
_17
"submit_proposal": {
_17
"title": "integrate proxy contract",
_17
"description": "Proposal to integrate 3rd party rewards",
_17
"link": "https://forum.astroport.fi/",
_17
"messages": [setup_pools, set_allowed_reward_proxies, move_to_proxy],
_17
"ibc_channel": "..."
_17
}
_17
}
_17
)
_17
}
_17
}

2. Construct submit_proposal message

The msg parameter in Step 1 requires a binary-encoded message, which essentially includes a contract call to the submit_proposal endpoint within the Assembly contract. You will need to structure your submit_proposal message with the following parameters:

  • title: This is the title of your proposal.
  • description: This provides a more detailed explanation of your proposal.
  • link: This should be the URL to the forum discussion for your proposal.
  • messages: This field includes your proposal messages for requesting ASTRO emissions and for connecting a generator proxy contract. These messages outline the changes to be made if your proposal is approved. The specific structure and content of these messages will be discussed in subsequent steps.
  • ibc_channel: This field is reserved for proposals that should be executed on a different chain. If your proposal is meant to be executed cross-chain, specify the governance channel for the target chain.

_17
{
_17
"send": {
_17
"contract": assemblyAddress,
_17
"amount": "30000",
_17
"msg": toBase64(
_17
{
_17
"submit_proposal": {
_17
"title": "integrate proxy contract",
_17
"description": "Proposal to integrate 3rd party rewards",
_17
"link": "https://forum.astroport.fi/",
_17
"messages": [setup_pools, set_allowed_reward_proxies, move_to_proxy],
_17
"ibc_channel": "..."
_17
}
_17
}
_17
)
_17
}
_17
}

3. Define an encoding function

Next, you'll need to encode the submit_proposal message you've constructed. As previously outlined in Step 1, the msg parameter requires a binary-encoded string. In Step 2, we prepped our submit_proposal message for encoding using a toBase64 function. Now it's time to actually perform that encoding.

Please note that the toBase64 function must be defined in your code environment to use it for encoding purposes. This function is crucial to convert the JSON string representation of our message into a base64 encoded string.

Here's an example of how to define the toBase64 function in JavaScript:


_3
let toBase64 = (obj) => {
_3
return Buffer.from(JSON.stringify(obj)).toString("base64");
_3
};

4. Constructing the setup_pools message

NOTE

In the scenario where you're executing a proposal via a script, it's advisable to compartmentalize each proposal message into distinct variables. These variables then get placed into the message vector of our proposal. This approach enhances the code modularity and maintainability.

setup_pools is our first proposal message. It calls the setup_pools endpoint in the Generator contract and expects a parameter named pools. This parameter contains a vector of LP token addresses and their corresponding allocation points, which is also stored in a binary-encoded format using the toBase64 function we defined earlier.

It's crucial to avoid two common mistakes while working with the setup_pools function:

  • The function expects LP token addresses, not pair addresses.
  • The function expects a vector of LP token addresses. If a single address is specified, it overwrites all active pools with the new list. Therefore, you should query the generator contract to incorporate previous LP token addresses and allocation points. This step will be discussed next.

_17
let setup_pools = {
_17
{
_17
"wasm": {
_17
"execute": {
_17
"contract_addr": generatorAddress,
_17
"msg": toBase64(
_17
{
_17
"setup_pools": {
_17
"pools": config.active_pools
_17
}
_17
}
_17
),
_17
"funds": []
_17
}
_17
}
_17
}
_17
}

5. Query config.active_pools

The previous step presented the setup_pools proposal message, calling the setup_pools endpoint in the Generator contract. This message carries an array of liquidity pool tokens and their associated allocation points.

When you specify a single liquidity pool token address in the setup_pools message, it replaces all active pools with the one specified in the message. To preserve existing pools while introducing new ones, you need to query the Generator contract to fetch the currently active pool addresses and their allocation points.

The code snippet below demonstrates this process. It begins by querying the Generator contract to obtain its current configuration. It then appends a new liquidity pool token address and allocation points to the active_pools list. This updated list will then be utilized in our setup_pools proposal message.

NOTE

In cases where the community desires to reduce allocation points for specific pools, proposal submitters may need to modify the config.active_pools list accordingly.


_10
let config;
_10
_10
const query = await lcd.wasm.contractQuery(
_10
generatorAddress,
_10
{
_10
"config": {}
_10
}
_10
).then(result => { config = result });
_10
_10
config.active_pools.push("new_lp_token", "new_alloc_points")

6. Construct set_allowed_reward_proxies message

set_allowed_reward_proxies is our second proposal message, calling the set_allowed_reward_proxies endpoint within the Generator contract. It carries an array of proxy contracts, wrapped within a binary-encoded function.

NOTE

In this code snippet, generatorAddress should be replaced with the address the Generator contract. config.allowed_reward_proxies should hold the array of proxy contract addresses you intend to allow. This variable will be defined in the next step. toBase64 is the function defined earlier that encodes our message for submission.


_17
let set_allowed_reward_proxies = {
_17
{
_17
"wasm": {
_17
"execute": {
_17
"contract_addr": generatorAddress,
_17
"msg": toBase64(
_17
{
_17
"set_allowed_reward_proxies": {
_17
"proxies": config.allowed_reward_proxies
_17
}
_17
}
_17
),
_17
"funds": []
_17
}
_17
}
_17
}
_17
}

7. Query config.allowed_reward_proxies

In the previous step, we introduced set_allowed_reward_proxies, a proposal message that calls the set_allowed_reward_proxies endpoint in the Generator contract. This message carries an array of proxy contracts.

When you specify a single address in the set_allowed_reward_proxies message, it overwrites all active proxy contracts with the ones included in the message. To maintain existing proxies while adding new ones, you need to query the Generator contract and include the previously active proxy contract addresses.

The following code demonstrates this process. It first queries the Generator contract to retrieve its configuration and then appends a new proxy address to the list of allowed proxies. This updated list will then be used in our set_allowed_reward_proxies proposal message.


_10
let config;
_10
_10
const query = await lcd.wasm.contractQuery(
_10
generatorAddress,
_10
{
_10
"config": {}
_10
}
_10
).then(result => { config = result });
_10
_10
config.allowed_reward_proxies.push(exProxyAddress);

8. Construct move_to_proxy message

move_to_proxy is our third proposal message. This message calls the move_to_proxy endpoint in the Generator contract to change the current dual rewards proxy for a specific LP token.

The move_to_proxy function takes two arguments:

  • lp_token: This refers to the address of the LP token contract.
  • proxy: This is the address of the newly deployed proxy contract.
NOTE

Replace generatorAddress with the address of your Generator contract, exLPToken with your LP token contract address, and exProxyAddress with the address of your deployed proxy contract. toBase64 is the function defined earlier that encodes our message for submission.


_18
let move_to_proxy = {
_18
{
_18
"wasm": {
_18
"execute": {
_18
"contract_addr": generatorAddress,
_18
"msg": toBase64(
_18
{
_18
"move_to_proxy": {
_18
"lp_token": exLPToken,
_18
"proxy": exProxyAddress
_18
}
_18
}
_18
),
_18
"funds": []
_18
}
_18
}
_18
}
_18
}

9. Complete proposal

This section brings all the individual pieces together to form our complete proposal submission. This involves organizing our proposal messages and executing the final submit_proposal call.

NOTE

This example does not include the initial Terra LCD setup. To learn more, visit the feather.js docs.

Additionally, make sure to adjust all placeholders (e.g., generatorAddress, exProxyAddress, exLPToken, new_lp_token, new_alloc_points) to your specific use case.


_121
_121
// terra lcd setup ...
_121
_121
// encoder function
_121
_121
let toBase64 = (obj) => {
_121
return Buffer.from(JSON.stringify(obj)).toString("base64");
_121
};
_121
_121
// config query
_121
_121
let config;
_121
_121
const query = await lcd.wasm.contractQuery(
_121
generatorAddress,
_121
{
_121
"config": {}
_121
}
_121
).then(result => { config = result });
_121
_121
config.active_pools.push("new_lp_token", "new_alloc_points")
_121
config.allowed_reward_proxies.push(exProxyAddress);
_121
_121
// first proposal message
_121
_121
let setup_pools = {
_121
{
_121
"wasm": {
_121
"execute": {
_121
"contract_addr": generatorAddress,
_121
"msg": toBase64(
_121
{
_121
"setup_pools": {
_121
"pools": config.active_pools
_121
}
_121
}
_121
),
_121
"funds": []
_121
}
_121
}
_121
}
_121
}
_121
_121
// second proposal message
_121
_121
let set_allowed_reward_proxies = {
_121
{
_121
"wasm": {
_121
"execute": {
_121
"contract_addr": generatorAddress,
_121
"msg": toBase64(
_121
{
_121
"set_allowed_reward_proxies": {
_121
"proxies": config.allowed_reward_proxies
_121
}
_121
}
_121
),
_121
"funds": []
_121
}
_121
}
_121
}
_121
}
_121
_121
// third proposal message
_121
_121
let move_to_proxy = {
_121
{
_121
"wasm": {
_121
"execute": {
_121
"contract_addr": generatorAddress,
_121
"msg": toBase64(
_121
{
_121
"move_to_proxy": {
_121
"lp_token": exLPToken,
_121
"proxy": exProxyAddress
_121
}
_121
}
_121
),
_121
"funds": []
_121
}
_121
}
_121
}
_121
}
_121
_121
// send tx
_121
_121
const integrateDualRewards = new MsgExecuteContract(
_121
wallet.key.accAddress('terra'),
_121
xastroAddress,
_121
{
_121
"send": {
_121
"contract": assemblyAddress,
_121
"amount": "30000",
_121
"msg": toBase64(
_121
{
_121
"submit_proposal": {
_121
"title": "integrate proxy contract",
_121
"description": "Proposal to integrate 3rd party rewards",
_121
"link": "https://forum.astroport.fi/",
_121
"messages": [setup_pools, set_allowed_reward_proxies, move_to_proxy],
_121
"ibc_channel": "..."
_121
}
_121
}
_121
)
_121
}
_121
}
_121
)
_121
_121
// broadcast tx
_121
_121
let fee = new Fee(2000000, { uluna: 100000 });
_121
_121
let tx = await wallet.createAndSignTx({
_121
msgs: [integrateDualRewards],
_121
fee,
_121
chainID: 'phoenix-1',
_121
});
_121
_121
let result = await lcd.tx.broadcast(tx, 'phoenix-1');
_121
_121
console.log(result);

After finalizing your code, you're ready to submit your proposal and play a significant part in the decision-making process of Astroport!