🤖Custom Proposal

The Custom Proposal workflow allows you to submit a JSON bundle of contract calls as part of your proposal, and have them be used when submitting your proposal on-chain.

An example of where this might be useful would be MIP-2, which makes a number of adjustments to the protocol's risk parameters, as well as pays out 100,000 WELL to the proposal submitter. https://gov.moonwell.fi/proposal/2

JSON Parameters

Each proposal needs to populate an array of values - targets, values, signatures, and callDatas. Proposal steps are combined based on index, so the first call will be targets[0], values[0], signatures[0], and callDatas[0], the second call will be targets[1], values[1], signatures[1], callDatas[1], etc.

Key NameUsage

targets

An array of contract addresses to interact with - Ex: ["0x091608f4e4a15335145be0A279483C0f8E4c7955", "0xffffffff1fcacbd218edc0eba20fc2308c778080"]

values

An array of NATIVE (GLMR/MOVR) values to send along with the request. Usually is set to 0 - Ex: [0, 0]

signatures

An array of function signatures for a target and callData. Ex: ["transferFrom(address,address,uint256)", "transferFrom(address,address,uint256)"]

callDatas

The ABI encoded callData for a function signature - Ex: ["0x0000000000000000000000000000000000000000000375b83deb52b6eb740000", "0x0000000000000000000000000000000000000000000375b83deb52b6eb740000"]

An example blob would look something like this, which is a single transferFrom request that will send 4,182,693 WELL from 6972f25ab3fc425eaf719721f0ebd1cdb58ee451 to 7793e08eb4525309c46c9ba394ce33361a167ba4:

{
  targets: [
    "0x511aB53F793683763E5a8829738301368a2411E3"
  ],
  values: [
    0
  ],
  signatures: [
    "transferFrom(address,address,uint256)"
  ],
  callDatas: [
    "0x0000000000000000000000006972f25ab3fc425eaf719721f0ebd1cdb58ee4510000000000000000000000007793e08eb4525309c46c9ba394ce33361a167ba40000000000000000000000000000000000000000000375b83deb52b6eb740000"
  ]
}

In order to generate a payload like this, you can use the following example for how to format things:

const ethers = require('ethers');
const moonwellJS = require('@moonwell-fi/moonwell.js');

const WELL_RECIPIENTS = {
    '0xa320c032A8e682675cc6020E3c87B9314f122077': 100_000,
    '0x8b61D581f826F91854Fd3dCfdFd1408A7c59d249': 250_000,
}

// Generate a payload to send the specified amounts of WELL to the desired recipients (WELL_RECIPIENTS) above
;(async () => {
    // JSON payload
    const payload = {
        targets: [], signatures: [], values: [], callDatas: [],
    }
    // 18 digit mantissa
    const mantissa = ethers.BigNumber.from(10).pow(18)

    const wellContract = new ethers.Contract(
        // Get WELL token address from moonwell.js
        moonwellJS.moonwellContracts.moonbeam.GOV_TOKEN,
        // Fill this in with a full ABI from moonscan for other functions!
        ["function transferFrom(address from, address to, uint256 value) returns (bool)"],
    )

    for (const [wallet, amount] of Object.entries(WELL_RECIPIENTS)){
        // Use `populateTransaction` to just return an encoded transaction without sending it
        const populatedTx = await wellContract.populateTransaction.transferFrom(
            '0xF130e4946F862F2c6CA3d007D51C21688908e006', // DEV GRANT Multisig
            wallet,
            ethers.BigNumber.from(amount).mul(mantissa)
        )

        // Target == WELL token
        payload.targets.push(wellContract.address)
        // Values = 0 GLMR sent
        payload.values.push(0)
        // Signatures = Get signature for `transferFrom` function
        payload.signatures.push(
            wellContract.interface.getFunction('transferFrom').format()
        )
        // CallDatas = Get call data for arguments.
        // IMPORTANT: You need to slice off the function selector from your call args
        payload.callDatas.push('0x' + populatedTx.data.slice(10))
    }

    // Log formatted proposal JSON
    console.log( JSON.stringify(payload, null, 2) )
})();

Proposal Details

Once you've generated and added your proposal JSON, you'll be taken to the editor to wordsmith your proposal. The editor accepts markdown and will give you an immediate preview on what things will look like when submitted.

If you need more space, click the "Pop Out Editor" for a full-screen markdown editor.

Once things look good, the last step is to preview your proposal, which will give you a detailed breakdown of the calls made and a formatted view of your proposal.

If things look good there, you can click "Submit Proposal For Voting", which will make a metamask request and broadcast your custom proposal to the DAO for a vote! 🗳

Last updated