Memo contract
(0x5294E9927c3306DcBaDb03fe70b92e01cCede505) wraps the call and emits the
metadata as events, so wallets, exchanges, and indexers can reconcile onchain
transfers against offchain records without changing the target contract.
To send a memo transfer end to end, see
Send USDC with a transaction memo.
How a memo call works
TheMemo contract wraps a target contract call. It routes the inner call
through Arc’s CallFrom precompile, a system-level contract that executes a
call on behalf of the original transaction sender. The
transaction extension contracts
use this precompile to keep the original externally owned account (EOA) wallet
as msg.sender for the target call. A USDC transfer still sees your wallet as
the sender, not the Memo contract.
The contract exposes a single entry point:
target is the contract to call, data is the calldata to forward to it,
memoId is a caller-supplied identifier for the memo, and memoData is the
arbitrary memo bytes attached to the subcall.
On success, Memo emits an ordered audit trail:
BeforeMemo(memoIndex)before the inner call.- The target contract events, such as a USDC
Transfer. Memo(sender, target, callDataHash, memoId, memo, memoIndex)after the inner call.
Memo contract supports nested memo calls. BeforeMemo events emit when
each memo frame starts. Memo events unwind from the innermost frame back to
the outermost frame.
Event schema
Use the emitted events as your audit trail and reconciliation source:
For reconciliation, query
Memo events by indexed fields such as memoId,
sender, and target. If you need to match a memo to a specific inner call,
compare callDataHash with the hash of the calldata your application created.
Wallet types
TheMemo contract must be invoked directly by an externally owned account
(EOA). Smart contract wallets aren’t supported as the direct caller.
Supported wallets
Any wallet that signs and broadcasts a standard EOA transaction works. This includes:- Browser and mobile wallets: MetaMask, Rabby, Coinbase Wallet, Rainbow
- Hardware wallets
- Server-side signers
- Circle developer-controlled wallets and user-controlled wallets configured as EOAs
CallFrom precompile preserves the signing EOA as msg.sender in the
target call, which is what the memo flow depends on.
Unsupported wallets
Smart contract wallets aren’t supported as the direct caller. This includes:- ERC-4337 smart accounts, including Circle modular wallets
- Circle developer-controlled wallets and user-controlled wallets configured as SCAs
- Safe and other multisig contract wallets
- Any other account-abstraction setup where the transaction originates from a bundler, entry point, or other intermediary contract
Memo.memo(...), the msg.sender reaching
the contract is the wallet contract or entry point, not the user’s EOA. The call
reverts because sender spoofing isn’t allowed.
If you need memo metadata from this flow, have the smart contract wallet trigger
a separate EOA-signed Memo.memo(...) transaction, or attach the metadata in
your application layer instead.
Unsupported patterns and guardrails
The transaction memo flow has explicit guardrails:- Submit
Memo.memo(...)from an EOA. Calls routed through an intermediary contract with a differentmsg.senderrevert because sender spoofing is not allowed. - Do not call the
CallFromprecompile directly from an EOA. Direct calls revert withunauthorized caller. - Do not rely on
STATICCALL. Memo execution changes state by incrementing the memo index, so static execution is rejected. - Do not use
DELEGATECALLinto theMemocontract. The delegated context does not have the required authorization forCallFrom. - If the child call reverts, the outer transaction reverts. The memo index
increment rolls back, and the child return data is wrapped in
MemoFailed(bytes).