> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arc.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Transaction memos

> How the Memo contract attaches metadata to a contract call on Arc while preserving the original sender.

Transaction memos attach application metadata, such as an invoice or order
reference, to a contract call on Arc. The predeployed `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](/arc/tutorials/send-usdc-with-transaction-memo).

## How a memo call works

The `Memo` 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](/arc/references/contract-addresses#transaction-extensions)
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:

```solidity theme={null}
function memo(
    address target,
    bytes calldata data,
    bytes32 memoId,
    bytes calldata memoData
) external;
```

`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:

1. `BeforeMemo(memoIndex)` before the inner call.
2. The target contract events, such as a USDC `Transfer`.
3. `Memo(sender, target, callDataHash, memoId, memo, memoIndex)` after the inner
   call.

The `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:

| Event        | Field          | Type              | Description                                                                                                               |
| :----------- | :------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `BeforeMemo` | `memoIndex`    | `uint256 indexed` | Sequential memo index reserved before the inner call starts.                                                              |
| `Memo`       | `sender`       | `address indexed` | Wallet that called `Memo.memo(...)`. For a direct EOA call, this is the EOA preserved as `msg.sender` in the target call. |
| `Memo`       | `target`       | `address indexed` | Contract called through the memo wrapper, such as the USDC ERC-20 interface.                                              |
| `Memo`       | `callDataHash` | `bytes32`         | `keccak256` hash of the forwarded target calldata. Store the original calldata if you need to reconstruct the exact call. |
| `Memo`       | `memoId`       | `bytes32 indexed` | Identifier that your application defines for lookup and reconciliation.                                                   |
| `Memo`       | `memo`         | `bytes`           | Memo bytes that your application defines. Encode this value consistently in your application.                             |
| `Memo`       | `memoIndex`    | `uint256`         | Sequential index for the memo frame. Nested memos each receive their own index.                                           |

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

The `Memo` 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

The `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

When a smart contract wallet calls `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 different `msg.sender` revert because sender spoofing is not
  allowed.
* Do not call the `CallFrom` precompile directly from an EOA. Direct calls
  revert with `unauthorized caller`.
* Do not rely on `STATICCALL`. Memo execution changes state by incrementing the
  memo index, so static execution is rejected.
* Do not use `DELEGATECALL` into the `Memo` contract. The delegated context does
  not have the required authorization for `CallFrom`.
* 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)`.

Before you use transaction memos in production, test your memo format and
reconciliation query against Arc Testnet with the same wallet and indexing
infrastructure you plan to use in your application.
