Generate Safe UI compatible multisig batch JSON to set veNFT votes. Use when the user wants to vote on gauges, allocate voting power to pools, or prepare vote transactions for a multisig wallet.
Generate Safe Transaction Builder compatible JSON for setting veNFT votes on the Lithos protocol.
.env file must contain RPC_URL for Plasma mainnet0x2AF460a511849A7aA37Ac964074475b0E6249c690x2Eff716Caa7F9EB441861340998B0952AF0566869745| Pool Name | Symbol | Pool Address |
|---|---|---|
| XPL/USDT (CL) | aWXPL-USDT | 0x19F4eBc0a1744b93A355C2320899276aE7F79Ee5 |
| USDT/USDe | sAMM-USDe/USDT0 | 0x01b968C1b663C3921Da5BE3C99Ee3c9B89a40B54 |
| WETH/USDT (CL) | aWETH-USDT | 0xca8759814695516C34168BBedd86290964D37adA |
| WETH/WXPL | vAMM-WXPL/WETH | 0x15DF11A0b0917956fEa2b0D6382E5BA100B312df |
| LITH/XPL | vAMM-WXPL/LITH | 0x7dAB98CC51835Beae6dEE43BbdA84cDb96896fb5 |
| MSUSD/USDT0 | sAMM-msUSD/USDT0 | 0xaa1605fbd9c2Cd3854337dB654471a45B2276c12 |
| MSUSD/USDE | sAMM-msUSD/USDe | 0x7257bEC1613d056eD1295721B5f731C05d1302fb |
| XAUT0/USDT0 | vAMM-XAUt0/USDT0 | 0xB1F2724482D8DcCbDCc5480A70622F93d0A66ae8 |
| WXPL/USDT0 (V) | vAMM-WXPL/USDT0 | 0xA0926801A2abC718822a60d8Fa1bc2A51Fa09F1e |
| WETH/weETH (V) | vAMM-WETH/weETH | 0x7483eD877a1423f34Dc5e46CF463ea4A0783d165 |
| WETH/weETH (S) | sAMM-WETH/weETH | 0x82862237F37e8495D88287d72A4C0073250487E0 |
| FBOMB/USDT | vAMM-fBOMB/USDT0 | 0xa82d9DfC3e92907aa4D092f18d89F1C0E129B8AC |
| EBUSD/USDT0 | sAMM-USDT0/ebUSD | 0x4388DcA346165FdC6cbC9e1e388779c66C026d27 |
To get the full list of pools dynamically:
# Get number of pools
source .env && cast call 0x2AF460a511849A7aA37Ac964074475b0E6249c69 "length()(uint256)" --rpc-url $RPC_URL
# Get pool at index
source .env && cast call 0x2AF460a511849A7aA37Ac964074475b0E6249c69 "pools(uint256)(address)" <INDEX> --rpc-url $RPC_URL
# Get pool symbol
source .env && cast call <POOL_ADDRESS> "symbol()(string)" --rpc-url $RPC_URL
source .env && cast call 0x2Eff716Caa7F9EB441861340998B0952AF056686 "balanceOf(address)(uint256)" <OWNER_ADDRESS> --rpc-url $RPC_URL
For each index from 0 to (balance - 1):
# Get token ID
source .env && cast call 0x2Eff716Caa7F9EB441861340998B0952AF056686 "tokenOfOwnerByIndex(address,uint256)(uint256)" <OWNER_ADDRESS> <INDEX> --rpc-url $RPC_URL
# Get voting power for token
source .env && cast call 0x2Eff716Caa7F9EB441861340998B0952AF056686 "balanceOfNFT(uint256)(uint256)" <TOKEN_ID> --rpc-url $RPC_URL
Key insight: When voting with a veNFT, its entire voting power is distributed proportionally across the pools based on weight ratios. You cannot partially vote - all power gets used.
Compare each veNFT's voting power against the total target votes:
Recommended strategy: Prefer overshooting over undershooting. Select the veNFT whose voting power is closest to (but ideally above) the target total.
Calculate the scale factor and show the user the projected actual votes:
scale_factor = veNFT_voting_power / total_target_votes
actual_votes_per_pool = target_weight * scale_factor
Multi-veNFT handling: If the multisig owns multiple veNFTs:
source .env && cast call 0x2Eff716Caa7F9EB441861340998B0952AF056686 "voted(uint256)(bool)" <TOKEN_ID> --rpc-url $RPC_URL
Check ALL veNFTs, not just the one you plan to vote with. If any veNFT has voted=true, include a reset transaction for it in the batch.
For any veNFT that needs to be reset (either already voted, or not being used for voting):
cast calldata "reset(uint256)" <TOKEN_ID>
The function selector for reset(uint256) is 0x310bd74b.
IMPORTANT: The vote() function takes POOL addresses, not gauge addresses!
cast calldata "vote(uint256,address[],uint256[])" <TOKEN_ID> "[<POOL1>,<POOL2>,...]" "[<WEIGHT1>,<WEIGHT2>,...]"
The function selector for vote(uint256,address[],uint256[]) is 0x7ac09bf7.
Generate a JSON file with this structure. Include reset transactions BEFORE the vote transaction in the transactions array:
{
"version": "1.0",
"chainId": "9745",
"createdAt": <UNIX_TIMESTAMP>,
"meta": {
"name": "Reset #<IDs> & Vote with #<TOKEN_ID>",
"description": "Reset votes for veNFT #<RESET_IDs>, then cast votes with veNFT #<TOKEN_ID> (<VOTING_POWER> voting power) across <NUM_POOLS> pools.\n\nVote Distribution:\n<POOL_BREAKDOWN_WITH_ACTUAL_VOTES>",
"txBuilderVersion": "1.16.5"
},
"transactions": [
{
"to": "0x2AF460a511849A7aA37Ac964074475b0E6249c69",
"value": "0",
"data": "<RESET_CALLDATA>",
"contractMethod": {
"inputs": [
{
"internalType": "uint256",
"name": "_tokenId",
"type": "uint256"
}
],
"name": "reset",
"payable": false
},
"contractInputsValues": {
"_tokenId": "<RESET_TOKEN_ID>"
}
},
{
"to": "0x2AF460a511849A7aA37Ac964074475b0E6249c69",
"value": "0",
"data": "<VOTE_CALLDATA>",
"contractMethod": {
"inputs": [
{
"internalType": "uint256",
"name": "_tokenId",
"type": "uint256"
},
{
"internalType": "address[]",
"name": "_poolVote",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "_weights",
"type": "uint256[]"
}
],
"name": "vote",
"payable": false
},
"contractInputsValues": {
"_tokenId": "<TOKEN_ID>",
"_poolVote": "[<POOL_ADDRESSES>]",
"_weights": "[<WEIGHTS>]"
}
}
]
}
Save the JSON to safe-txs/safe.vote-<TOKEN_ID>.json.
After the transaction is executed, verify the vote was successful:
# Check voted status
source .env && cast call 0x2Eff716Caa7F9EB441861340998B0952AF056686 "voted(uint256)(bool)" <TOKEN_ID> --rpc-url $RPC_URL
# Check vote amount on a specific pool
source .env && cast call 0x2AF460a511849A7aA37Ac964074475b0E6249c69 "votes(uint256,address)(uint256)" <TOKEN_ID> <POOL_ADDRESS> --rpc-url $RPC_URL
User: "Set votes for veNFTs owned by 0x495a98fd059551385Fc9bAbBcFD88878Da3A1b78 with these targets:
Workflow:
Using gauge addresses instead of pool addresses - The vote() function takes pool addresses. Use the Pool Address Reference table above.
Forgetting to check voted status - If the veNFT has already voted this epoch, the transaction will fail. Reset first.
Including pools with 0 weight - Only include pools with non-zero weights in the arrays.
Not resetting unused veNFTs - If the multisig owns multiple veNFTs but only votes with one, the others' votes from previous epochs may still be active. Always reset veNFTs not being used.
Assuming target weights = actual votes - The veNFT's entire voting power is distributed proportionally. Actual votes = target × (veNFT_power / total_target). Always show the user the projected actual votes.
Voting with all veNFTs when user wants specific totals - If user provides target vote amounts, they likely want TOTAL votes across all veNFTs to approximate those targets. Ask whether to use one or all veNFTs.
The primary multisig that owns veNFTs: 0x495a98fd059551385Fc9bAbBcFD88878Da3A1b78
Current veNFTs owned (as of last update):