Skip to main content
MEE supports time-bounded execution windows using lowerBoundTimestamp and upperBoundTimestamp. These parameters let you defer execution or enforce expiration on quotes, enabling powerful UX like scheduled actions, delayed bridging, market timing, and more.

Why Use Time Bounds?

  • Scheduled execution (e.g. trigger after a certain amount of time)
  • Custom expiry (e.g. keep trying to execute for five minutes, if simulation is error still - then discard)
  • Sequential execution (e.g. execute a transaction every five minutes)

Setup

import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs";
import { http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base, optimism } from "viem/chains";

const eoa = privateKeyToAccount(Bun.env.PRIVATE_KEY as Hex);
const orchestrator = await toMultichainNexusAccount({
  chainConfigurations: [
    {
      chain: optimism,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0)
    },
    {
      chain: base,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0)
    }
  ],
  signer: eoa
});

const date = new Date();

const meeClient = await createMeeClient({
  account: orchestrator
});

const minutesToSeconds = (input: number) => input * 60;

Instant Execution (Default Expiry)

Executes immediately and simulates until it passes or hits a default 3-minute timeout.
const instantTxDefaultExpiryTime = await meeClient.getQuote({
  instructions: [],
  feeToken: {
    address: '0xYourFeeTokenAddress',
    chainId: base.id
  },
});

Instant Execution with Custom Expiry

Runs immediately but expires 1 minute after quote creation if it still errors.
const instantTxCustomExpiry = await meeClient.getQuote({
  instructions: [],
  feeToken: {
    address: '0xYourFeeTokenAddress',
    chainId: base.id
  },
  upperBoundTimestamp: Date.now() + minutesToSeconds(1)
});

Scheduled Execution

Quote won’t be eligible to run until 5 minutes from now.
const scheduledTxQuote = await meeClient.getQuote({
  instructions: [],
  feeToken: {
    address: '0xYourFeeTokenAddress',
    chainId: base.id
  },
  lowerBoundTimestamp: Date.now() + minutesToSeconds(5)
});

Bounded Window Execution

Will only attempt execution between 2 and 5 minutes from quote creation.
const boundedTx = await meeClient.getQuote({
  instructions: [],
  feeToken: {
    address: '0xYourFeeTokenAddress',
    chainId: base.id
  },
  lowerBoundTimestamp: Date.now() + minutesToSeconds(2),
  upperBoundTimestamp: Date.now() + minutesToSeconds(5)
});

Instruction-Level Time Bounds

In addition to setting global lowerBoundTimestamp and upperBoundTimestamp for the entire quote, MEE supports instruction-level time bounds. With this, you can define custom timing windows for each instruction, giving you granular control across multiple user operations and chains.

Why Use Instruction-Level Time Bounds?

  • Custom time bounds for each user operation: Specify unique execution windows for every instruction.
  • Different windows per chain: If your supertransaction spans multiple chains, set different timing strategies for each.
  • Flexible multi-instruction orchestration: Chain together actions where some execute immediately, some wait, and some have stricter expiry.

How Does It Work?

Each instruction in your instructions: [] array can include its own lowerBoundTimestamp and/or upperBoundTimestamp. These timestamps override the global bounds for that instruction’s userOp. When batching instructions on the same chain and some of them have custom instruction-level time bounds, the largest execution window is used for that chain’s batched user operation. This ensures that all instructions in the batch have the opportunity to execute.

Example

Suppose you want an ETH transfer on Optimism to run any time in the next 10 minutes, but a ETH transfer on Base should run only in the next 2 minutes.
const quote = await meeClient.getQuote({
  instructions: [
    await mcNexus.build({
      type: "nativeTokenTransfer",
      data: {
        to: eoaAccount.address,
        chainId: optimism.id,
        value: 1n,
        lowerBoundTimestamp: Math.floor(Date.now() / 1000),
        upperBoundTimestamp: Math.floor(Date.now() / 1000) + minutesToSeconds(10)
      }
    }),
    await mcNexus.build({
      type: "nativeTokenTransfer",
      data: {
        to: eoaAccount.address,
        chainId: base.id,
        value: 1n,
        lowerBoundTimestamp: Math.floor(Date.now() / 1000),
        upperBoundTimestamp: Math.floor(Date.now() / 1000) + minutesToSeconds(2)
      }
    })
  ],
  feeToken: {
    address: '0xYourFeeTokenAddress',
    chainId: base.id
  }
});

Benefits

  • Fine-tuned orchestration for complex flows.
  • Supports scenarios where actions on different chains require different timing guarantees.
  • Enables advanced scheduling, retry, and expiry strategies on a per-instruction basis.

Developer Tips

  • Timestamps are expected in seconds, not milliseconds.
  • Quotes simulate continuously within the window. If execution fails for the full duration, they will revert.
  • If both lowerBoundTimestamp and upperBoundTimestamp are omitted, quotes execute immediately with a 2-minute fallback.
  • Use in combination with cleanup for robust, failure-tolerant flows.
Time-bounded scheduling unlocks a whole new level of control over supertransactions—giving developers precision orchestration primitives without needing custom schedulers or off-chain cron jobs.