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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions