[Proposal] Polygon: Authorize Batch Relayer V2 for USD+ Boosted Pool

This proposal aims to authorize an updated version of the Batch Relayer which adds (un)wrapping capabilities for ERC-4626 tokens and UnbuttonTokens. For reference, the original Batch Relayer enabled atomic sequences of swaps, joins, and exits, as well as (un)wrapping of stETH and Aave aTokens. Batch Relayer V2 retains all of those features from V1.

This proposal seeks authorization for Batch Relayer V2 only on Polygon, as it is anticipated that a Batch Relayer V3 will soon be available on all networks. This intermediate release is targeted specifically at a new USD+ Boosted Pool on Polygon.

The relayer would be granted authority to perform the following tasks on behalf of users who have opted into using it:

  • Leverage the user’s existing Vault allowances and internal balances to avoid duplicate approvals.
  • Swap on the user’s behalf.
  • Add and remove liquidity on the user’s behalf.


The team at Overnight, who created the USD+ yield-bearing stable coin, played a significant role in the development of the new ERC4626LinearPool. This pool supports tokens implementing the recently finalized EIP-4626 Tokenized Vault Standard, and the Balancer Integrations Team believes it has the potential to open up Boosted Pools to a plethora of yield-bearing tokens. We thank Overnight for their patience throughout the integrations process and would like to add relayer support for their new pool as soon as possible.

The relayer will enable trades involving the rebasing USD+ which is incompatible with the Balancer Vault. It does this by (un)wrapping from/to the StaticUsdPlus token, which is an ERC-4626 token wrapper that removes the rebasing component. This will effectively create direct markets for USD+ on Balancer, not only via DAI, USDC, and USDT, but also via multihop to any other connected token within the Vault. For example, the relayer would enable the following atomic DAI->USD+ trade:

  1. perform a multihop batch swap from DAI to StaticUsdPlus through the USD+ Boosted Pool;
  2. unwrap the StaticUsdPlus tokens from step 1, receiving plain USD+ tokens;


This proposal would only grant the required roles to the BalancerRelayer contract. Each user would still be required to opt into the relayer by submitting an approval transaction or signing a message.

The code is a simple wrapper over existing Balancer pool interactions, and it has been thoroughly tested and reviewed by the Balancer Labs team.

The addresses of the BalancerRelayer and BatchRelayerLibrary mentioned in this proposal can be found in the official repo.


The Balancer governance multisig on Polygon would submit a transaction to the Authorizer in order to grant the following roles to the BalancerRelayer:

  1. manageUserBalance : Utilize existing Vault allowances and internal balances so that a user does not have to re-approve the new relayer for each token.
  2. joinPool : Add liquidity to a pool on the user’s behalf.
  3. exitPool : Remove liquidity from a pool on the user’s behalf.
  4. swap : Trade within a single pool on the user’s behalf.
  5. batchSwap : Make a multihop trade or source liquidity from multiple pools.
  6. setRelayerApproval : Approve the relayer on the user’s behalf (user must still provide a signed message).

The Gnosis Safe at 0xd2bD536ADB0198f74D5f4f2Bd4Fe68Bae1e1Ba80 would send a transaction to the Vault Authorizer at 0xA331D84eC860Bf466b4CdCcFb4aC09a1B43F3aE6 with the following data to authorize the BalancerRelayer at 0xF537dDd7f4cc72C6C08866b62EAe9378f1F62da8:


Which is the ABI-encoded calldata for:


For transparency, a developer could reproduce the encoded data with the following hardhat script:

const hre = require("hardhat");
const deployments = require("@balancer-labs/v2-deployments");

const NETWORK = "polygon";
const RELAYER = "0xF537dDd7f4cc72C6C08866b62EAe9378f1F62da8";

async function main() {
  // Set up contracts
  const authorizer = await deployments.getBalancerContract("20210418-authorizer", "Authorizer", NETWORK);
  const vault = await deployments.getBalancerContract("20210418-vault", "Vault", NETWORK);

  // Compute roles
  const roles = await Promise.all(["manageUserBalance", "joinPool", "exitPool", "swap", "batchSwap", "setRelayerApproval"]
    .map(async (role) => {
      const data = await vault.getActionId(vault.interface.getSighash(role));
      return data;
  console.log(`  authorizer.grantRoles(
      ${roles.map(role => `${role},`).join("\n      ")}

  // Encode calldata
  const data = await authorizer.interface.encodeFunctionData("grantRoles", [roles, RELAYER]);

  .then(() => process.exit(0))
  .catch((error) => {