Skip to content

bug: EOA self-calls with calldata rejected at consensus level — "External transactions to internal accounts cannot include data" #191

Description

@osr21

Summary

Arc Testnet rejects any transaction where the sender and recipient are the same address (from == to) and the transaction carries non-empty data. The error returned is:

External transactions to internal accounts cannot include data

This is enforced at the consensus layer, not by the EVM or a smart contract. It cannot be worked around by adjusting gas, fee fields, or transaction type.


Environment

  • Network: Arc Testnet (Chain ID: 5042002)
  • Trigger: any eth_sendTransaction or wallet_sendCalls where from === to and data.length > 0
  • Tools affected: MetaMask, viem, ethers.js, cast — any library that submits the raw transaction

Reproduction

// Attempt to send a self-call with data (e.g. to attach an on-chain memo)
const tx = await window.ethereum.request({
  method: "eth_sendTransaction",
  params: [{
    from:  "0xYourAddress",
    to:    "0xYourAddress",   // same as from
    data:  "0x48656c6c6f",   // any non-empty calldata
    value: "0x0",
    gas:   "0x7530",
  }],
});
// → MetaMask submits, Arc node rejects:
// "External transactions to internal accounts cannot include data"

Note: the same call with data: "0x" (empty) succeeds — it is the combination of from == to and non-empty data that is rejected.


Impact

This silently breaks a common pattern used by dApps to attach on-chain memos to transactions:

  1. Self-call memo pattern — encode a UTF-8 string as calldata, send to self. Widely used on Ethereum mainnet and other EVMs. Completely blocked on Arc Testnet.
  2. Consequence for the Arc Memo precompile — the Memo precompile (0x5294E9927c3306DcBaDb03fe70b92e01cCede505) is the only supported on-chain memo path, but it has its own gas-estimation bug (see issue #189). The self-call restriction means there is currently no simple, estimation-compatible fallback.

Discovered during Batch Transfer integration

We hit this while building Arc Stablecoin DApp, specifically on the Batch Transfer feature. The original design appended an on-chain text memo by sending a self-call with the memo encoded as calldata — a standard EVM pattern:

// Original design — fails on Arc
await window.ethereum.request({
  method: "eth_sendTransaction",
  params: [{
    from: userAddress,
    to:   userAddress,
    data: toHex(new TextEncoder().encode(memoText)),
  }],
});
// → "External transactions to internal accounts cannot include data"

Workaround we used: Added a string calldata memo parameter directly to batchTransfer(). The memo is emitted in the BatchExecuted event and stored in the transaction log in the same confirmation — no separate transaction or self-call needed:

contract BatchTransfer {
    event BatchExecuted(
        address indexed sender,
        address indexed token,
        uint256 totalAmount,
        uint256 count,
        string  memo           // ← stored in the event log
    );

    function batchTransfer(
        address          token,
        address[] calldata recipients,
        uint256[] calldata amounts,
        string    calldata memo   // ← callers pass memo string directly
    ) external {
        // ... ERC-20 transfer loop ...
        emit BatchExecuted(msg.sender, token, total, recipients.length, memo);
    }
}

Deployed and source-verified on ArcScan: 0x76d5dd51ad28D607cD8804dc5230cAE93403eD3d

Live example — memo "Invoice MR" decoded by name in the Logs tab on ArcScan:
https://testnet.arcscan.app/tx/0xacc2bc5a96ecac286549132303e664f3ade81969c26e788c814798f20f13b429?tab=logs


Comparison with issue #189

This restriction is distinct from the gas-estimation bug in #189:

Scenario Error Stage Workaround
callWithMemo via viem / ethers Gas estimation fails RPC simulation Pass explicit gas: override
Self-call with non-empty data (from == to) Consensus rejection Block inclusion Cannot use self-calls — embed memo differently

The gas: override that fixes #189 does not help here — the transaction is rejected at block inclusion, not during estimation.


Suggested documentation / guidance

  1. Document the from == to + data restriction explicitly in Arc developer docs — the error message is opaque and this behaviour differs from Ethereum mainnet.
  2. Recommend the Arc Memo precompile (0x5294...) as the supported alternative, with a note about the gas-estimation workaround (callWithMemo reverts during gas estimation (eth_call) but succeeds with direct signed transaction #189).
  3. For dApps that own their contract: embedding memo as a function parameter and emitting it in an event is a zero-extra-confirmation alternative that avoids both issues entirely.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ELComponent: ELbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions