base-mcp: send_calls userOp gas estimation reverts for gas-heavy partner contract calls (eth_call succeeds)
- Dominant language
- Python
- Stars
- 119
- Forks
- 125
- Avg merge
- 22h 30m
- Merged PRs (30d)
- 2
Description
## Summary
`send_calls` userOp gas estimation reverts for a partner contract call that simulates successfully via `eth_call`. The same wallet, same RPC, identical calldata succeeds in simulation when no explicit `gas` is set, but is rejected at `send_calls` time with `failed to estimate gas for user operation: useroperation reverted: execution reverted`. There is no `gas` parameter on the `send_calls` tool that would let the caller hint a higher inner-call gas budget, so any plugin whose calls exceed the (apparently) internal cap is silently blocked.
This affects the [`base-mcp` skill](https://github.com/base/skills/blob/master/skills/base-mcp/SKILL.md) custom-plugin path documented at [`references/custom-plugins.md`](https://github.com/base/skills/blob/master/skills/base-mcp/references/custom-plugins.md) (and [docs.base.org/ai-agents/plugins/custom-plugins](https://docs.base.org/ai-agents/plugins/custom-plugins)).
## Environment
- MCP server: `https://mcp.base.org` (live, current as of 2026-05-27)
- Chain: Base mainnet (chain id 8453)
- Wallet under test (Smart Wallet): `0x93d9fcf2f6b2b1abe4bd55d8e21e87d8d4e48a1f`
- Target contract: `BoonV3` at `0x22aC2E603D4B1CaAb3A8433f1691BA6158A896AF` (verified on Basescan, ~31.7 KB code, not paused)
- USDC: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
- Plugin: a custom Base MCP plugin (Boon Protocol — USDC tipping) following the documented "fetch calldata via `web_request`, submit via `send_calls`" pattern. Plugin spec: `https://docs.boonprotocol.com/base-mcp-plugin/boon` (private repo source).
## Reproduction
### 1. Single-call `send_calls` — USDC `approve(boonV3, 100000)` (0.10 USDC allowance)
- Result: ✅ Works. `send_calls` returns an `approvalUrl`, user approves in Base Account, tx mines.
- Tx hash: `0xc2e346ca220255225fcfb38e2b0d1c1ca83b245d03f151be6228251de083616c`
- Block: 46552554
- gasUsed: 597,375 (includes Smart Wallet first-deploy overhead)
### 2. Single-call `send_calls` — `BoonV3.tip(...)` (selector `0x4797ac29`), tip 0.10 USDC to `github:vbuterin`
ABI: `tip(bytes32 handleHash, string handle, address token, uint256 amount, string note, bool isPublic, (uint256 deadline, uint8 v, bytes32 r, bytes32 s) permit)`
- Result: ❌ `Transaction validation failed: failed to estimate gas for user operation: useroperation reverted: execution reverted. Verify the calls and try again.`
- Failure happens **before** any `approvalUrl` is returned — i.e. during userOp gas estimation, not at user-signing time.
### 3. Direct `eth_call` with the exact same calldata, same `from`, **no explicit `gas` field**
- Result: ✅ Returns `0x...0000003` (tipId = 3). Function executes successfully in simulation.
- `cast call --from 0x93d9...a1f 0x22aC...96AF 'tip(bytes32,string,address,uint256,string,bool,(uint256,uint8,bytes32,bytes32))' ...` → `0x...0000003`.
### 4. Same `eth_call`, but with `"gas":"0x186a00"` (1,600,000) explicitly set
- Result: ❌ `execution reverted` with no error string — consistent with gas exhaustion (not a contract `require` failure, which would surface a revert reason).
### 5. Calldata sanity checks
- Calldata is byte-identical to what `cast calldata` produces for the canonical ABI (`diff` confirmed).
- Selector `0x4797ac29` matches the contract's documented `tip(...)` signature.
- `handleHash = keccak256("github:vbuterin")` verified on-chain.
- Allowance and balance on-chain are sufficient: 0.10 USDC allowance, 2.5 USDC balance, 0.0006 ETH for gas.
## Diagnosis
`send_calls`'s userOp gas estimator appears to apply an internal gas cap on inner calls that is lower than the actual gas required for state-write-heavy contract calls (~200k+ for the `tip` flow due to escrow state writes + USDC `transferFrom` + event emission). The cheap `approve` call (~25k gas) passes the cap; the `tip` call does not, even though it is well within reasonable block-gas-limit territory and `eth_call` confirms it executes correctly under the default block-gas cap.
The "1,600,000 gas → revert with no string" result in step 4 is the same failure mode as `send_calls`, which is consistent with the estimator running the call under a fixed cap below what the contract needs and then surfacing the cap-exhausted revert as a generic `execution reverted`.
## Expected behavior
Any call that simulates successfully via `eth_call` with default (block-gas-limit) gas should be submittable via `send_calls`. Either:
- raise the internal inner-call gas cap to the userOp's actual `callGasLimit` (and let the bundler/paymaster handle the real bound), or
- expose an explicit per-call `gas` field on the `send_calls` tool so the caller can hint a higher budget when they have already simulated the call.
## Impact
This silently blocks every partner plugin whose contract calls exceed the internal cap — likely most non-trivial state-mutating calls. The Boon `tip` flow is a fairly typical "escrow update + event emission + `transferFrom`" pattern; we should not be hitting a gas cap there.
Today the only escape hatch for users who hit this is to abandon Base MCP for that call and submit the transaction directly through the Base Account web UI, which defeats the point of the custom-plugins path.
## Asks
1. Confirm whether `send_calls` applies an internal gas cap on inner calls, and if so, document it.
2. Either lift the cap to the userOp's `callGasLimit` or add an optional `gas` field to `send_calls` calls.
3. Make the error message distinguish "inner-call gas cap exhausted" from "real contract revert" so plugin authors can diagnose this without an out-of-band `eth_call`.
Happy to provide raw calldata, RPC traces, or run additional repro steps. Thanks!
Contributor guide
Research direction
Start with skills/base-mcp/SKILL.md and skills/base-mcp/references/custom-plugins.md, then compare the documented send_calls flow with the reported eth_call behavior. Confirm whether the service applies an inner-call gas cap and identify the relevant API behavior. Done means the cap is addressed or documented, callers can provide a suitable gas budget, and gas-cap failures are distinguished from contract reverts.
Written by the indexing model from the issue text.
Assessment
- Domain
- api, blockchain
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100