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 custom-plugin path documented at references/custom-plugins.md (and 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
- Confirm whether
send_calls applies an internal gas cap on inner calls, and if so, document it.
- Either lift the cap to the userOp's
callGasLimit or add an optional gas field to send_calls calls.
- 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!
Summary
send_callsuserOp gas estimation reverts for a partner contract call that simulates successfully viaeth_call. The same wallet, same RPC, identical calldata succeeds in simulation when no explicitgasis set, but is rejected atsend_callstime withfailed to estimate gas for user operation: useroperation reverted: execution reverted. There is nogasparameter on thesend_callstool 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-mcpskill custom-plugin path documented atreferences/custom-plugins.md(and docs.base.org/ai-agents/plugins/custom-plugins).Environment
https://mcp.base.org(live, current as of 2026-05-27)0x93d9fcf2f6b2b1abe4bd55d8e21e87d8d4e48a1fBoonV3at0x22aC2E603D4B1CaAb3A8433f1691BA6158A896AF(verified on Basescan, ~31.7 KB code, not paused)0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913web_request, submit viasend_calls" pattern. Plugin spec:https://docs.boonprotocol.com/base-mcp-plugin/boon(private repo source).Reproduction
1. Single-call
send_calls— USDCapprove(boonV3, 100000)(0.10 USDC allowance)send_callsreturns anapprovalUrl, user approves in Base Account, tx mines.0xc2e346ca220255225fcfb38e2b0d1c1ca83b245d03f151be6228251de083616c2. Single-call
send_calls—BoonV3.tip(...)(selector0x4797ac29), tip 0.10 USDC togithub:vbuterinABI:
tip(bytes32 handleHash, string handle, address token, uint256 amount, string note, bool isPublic, (uint256 deadline, uint8 v, bytes32 r, bytes32 s) permit)Transaction validation failed: failed to estimate gas for user operation: useroperation reverted: execution reverted. Verify the calls and try again.approvalUrlis returned — i.e. during userOp gas estimation, not at user-signing time.3. Direct
eth_callwith the exact same calldata, samefrom, no explicitgasfield0x...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 setexecution revertedwith no error string — consistent with gas exhaustion (not a contractrequirefailure, which would surface a revert reason).5. Calldata sanity checks
cast calldataproduces for the canonical ABI (diffconfirmed).0x4797ac29matches the contract's documentedtip(...)signature.handleHash = keccak256("github:vbuterin")verified on-chain.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 thetipflow due to escrow state writes + USDCtransferFrom+ event emission). The cheapapprovecall (~25k gas) passes the cap; thetipcall does not, even though it is well within reasonable block-gas-limit territory andeth_callconfirms 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 genericexecution reverted.Expected behavior
Any call that simulates successfully via
eth_callwith default (block-gas-limit) gas should be submittable viasend_calls. Either:callGasLimit(and let the bundler/paymaster handle the real bound), orgasfield on thesend_callstool 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
tipflow 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
send_callsapplies an internal gas cap on inner calls, and if so, document it.callGasLimitor add an optionalgasfield tosend_callscalls.eth_call.Happy to provide raw calldata, RPC traces, or run additional repro steps. Thanks!