Skip to main content
Runtime injection lets you use values that aren’t known until execution—like token balances after a swap or bridge.

The Problem

Traditional batching requires hardcoded values:
// ❌ This breaks if swap output differs from expected
const depositAmount = parseUnits("0.05", 18);  // Guessing WETH output

The Solution

Use runtime functions to inject actual values at execution time:
import { runtimeERC20BalanceOf, greaterThanOrEqualTo } from "@biconomy/abstractjs";
import { balanceNotZeroConstraint } from "../utils/balanceNotZero.util";

// ✅ Uses actual balance when executing
const depositAmount = runtimeERC20BalanceOf({
  tokenAddress: WETH,
  targetAddress: account.addressOn(base.id, true),
  constraints: [balanceNotZeroConstraint]
});

Available Functions

FunctionDescription
runtimeERC20BalanceOfERC-20 token balance
runtimeNativeBalanceOfNative token balance (ETH, MATIC)
runtimeERC20AllowanceOfToken allowance between addresses
runtimeParamViaCustomStaticCallAny contract read (up to 32 bytes)

runtimeERC20BalanceOf

Most common—inject token balance at execution:
import { runtimeERC20BalanceOf } from "@biconomy/abstractjs";

const transferAll = await account.buildComposable({
  type: "default",
  data: {
    chainId: base.id,
    to: USDC,
    abi: erc20Abi,
    functionName: "transfer",
    args: [
      recipient,
      runtimeERC20BalanceOf({
        tokenAddress: USDC,
        targetAddress: account.addressOn(base.id, true),
        constraints: []
      })
    ]
  }
});

runtimeNativeBalanceOf

For native token operations:
import { runtimeNativeBalanceOf } from "@biconomy/abstractjs";

const sendAllETH = await account.buildComposable({
  type: "nativeTokenTransfer",
  data: {
    chainId: base.id,
    to: recipient,
    value: runtimeNativeBalanceOf({
      targetAddress: account.addressOn(base.id, true)
    })
  }
});

Constraints

Constraints control when instructions execute and protect against bad values.

Minimum Balance

import { greaterThanOrEqualTo } from "@biconomy/abstractjs";

runtimeERC20BalanceOf({
  tokenAddress: USDC,
  targetAddress: orchestrator,
  constraints: [greaterThanOrEqualTo(parseUnits("90", 6))]  // Wait for 90+ USDC
})

Non-Zero Check

import { balanceNotZeroConstraint } from "../utils/balanceNotZero.util";

runtimeERC20BalanceOf({
  tokenAddress: WETH,
  targetAddress: orchestrator,
  constraints: [balanceNotZeroConstraint]  // Wait for any WETH
})

Transaction Ordering

Constraints determine execution sequence. MEE retries until satisfied:
// Step 1: Bridge (executes immediately)
const bridge = await account.buildComposable({ /* bridge config */ });

// Step 2: Swap (waits for bridge tokens)
const swap = await account.buildComposable({
  type: "default",
  data: {
    chainId: base.id,
    args: [
      runtimeERC20BalanceOf({
        tokenAddress: USDC_BASE,
        targetAddress: account.addressOn(base.id, true),
        constraints: [greaterThanOrEqualTo(minExpected)]  // ← Waits here
      })
    ]
  }
});
Flow:
  1. Bridge instruction executes
  2. Swap instruction simulated → fails (no tokens yet)
  3. MEE waits and retries
  4. Bridge completes, tokens arrive
  5. Constraint satisfied → swap executes

Example: Swap and Deposit All

// Swap USDC → WETH
const swap = await account.buildComposable({
  type: "default",
  data: {
    chainId: base.id,
    to: UNISWAP_ROUTER,
    abi: UniswapAbi,
    functionName: "exactInputSingle",
    args: [{ tokenIn: USDC, tokenOut: WETH, amountIn: parseUnits("100", 6), /* ... */ }]
  }
});

// Approve exactly what we received
const approve = await account.buildComposable({
  type: "approve",
  data: {
    chainId: base.id,
    spender: MORPHO_POOL,
    tokenAddress: WETH,
    amount: runtimeERC20BalanceOf({
      tokenAddress: WETH,
      targetAddress: account.addressOn(base.id, true),
      constraints: [balanceNotZeroConstraint]
    })
  }
});

// Deposit all WETH
const deposit = await account.buildComposable({
  type: "default",
  data: {
    chainId: base.id,
    to: MORPHO_POOL,
    abi: MorphoAbi,
    functionName: "deposit",
    args: [
      runtimeERC20BalanceOf({
        tokenAddress: WETH,
        targetAddress: account.addressOn(base.id, true),
        constraints: [balanceNotZeroConstraint]
      }),
      account.addressOn(base.id, true)
    ]
  }
});

const quote = await meeClient.getQuote({
  instructions: [swap, approve, deposit],
  feeToken: { address: USDC, chainId: base.id }
});

Best Practices

Use for Unknown Values

Swap outputs, bridge results, slippage

Add Constraints

Protect against unexpected states

Sweep Remaining

Transfer leftover tokens back to user

Set Slippage Tolerance

Use greaterThanOrEqualTo with minimum

Summary

Runtime injection turns static transactions into adaptive, state-aware flows that handle:
  • Unknown swap outputs
  • Bridge timing
  • Slippage protection
  • Automatic sequencing
No more guessing values or stuck transactions.