neo-devpack-solidity 0.22.0

Production-focused Solidity-to-NeoVM compilation system
Documentation
---
title: "EIP-2930 — Access Lists"
description: "EIP-2930 — Access Lists mapped to Neo N3."
---

# EIP-2930 — Access Lists

[Back to Protocol-Level EIPs](/standards-mirror/protocol-eips)

<StandardsMirror>

<StandardEntry
  id="eip-2930"
  title="EIP-2930 — Access Lists"
  eip="2930"
  status="Final"
  neoMapping="Witness scopes (CustomContracts)"
  category="Transactions"
  parityLabel="Native"
  parityClass="sm-pill-native"
>

<template #spec>

## EIP-2930: Access Lists

EIP-2930 adds an optional `accessList` field to transactions: a pre-declared list
of `(address, storageKeys[])` tuples that the transaction will touch. Pre-declared
state access gets a gas discount; non-declared access costs more. The goal: enable
parallel transaction execution by letting nodes know in advance what state each tx
needs.

### Format

```
accessList = [
  (address_1, [storageKey_1a, storageKey_1b, ...]),
  (address_2, [storageKey_2a, ...]),
  ...
]
```

### Neo Equivalent: Witness Scopes

Neo's `WitnessScope.CustomContracts` already serves the same role for the
authorisation surface — declaring "this signature authorises calls only into these
contracts". Combined with `Signer.AllowedContracts` and `Signer.AllowedGroups`,
the protocol knows ahead of time which contracts a transaction can touch.

For the parallel-execution use case (knowing storage keys ahead of time), Neo's
storage prefix model makes static analysis tractable — the contract bytecode
declares its storage prefixes via `Storage.Get/Put/Delete` call sites.


::: tip Live on Neo TestNet
Both implementations are deployed on Neo N3 TestNet (network magic `894710606`).

| Implementation | Contract Hash | Deploy Tx |
| --- | --- | --- |
| **Solidity** (`neo-solc`) | `0x02a174b4080c57d8ceb2c66b223799a79d09a2c3` | [`0x17844b4b…d94112`]https://dora.coz.io/transaction/neo3/testnet/0x17844b4b50ebc920cd449de142170ef8b55886d0acde40b20894baa02bd94112 |
| **Neo C#** (`nccs`) | `0x3570e80f52a9329b604d5d18fb94de3133b0a6ad` | [`0xb1bfb7d4…136169`]https://dora.coz.io/transaction/neo3/testnet/0xb1bfb7d4b46715b6e0848bbbf2b6a2a6b163531011d6998c6dc873025b136169 |

Checked-in snapshot: Solidity 2 / 2 assertions pass; Neo C# 2 / 2 assertions pass. This is a validation snapshot, not an all-green parity certification; see [TestNet Results](/standards-mirror/deployments/RESULTS) for failure details.

Source pairs: [`docs/standards-mirror/deployments/eip-2930/`](https://github.com/r3e-network/neo-devpack-solidity/tree/main/docs/standards-mirror/deployments/eip-2930).
:::

</template>

<template #solidity>

```solidity
// EIP-2930 type-1 transaction:
//
//   0x01 || rlp([
//     chainId, nonce, gasPrice, gasLimit,
//     to, value, data,
//     accessList,       // [(address, storageKeys[])]
//     v, r, s
//   ])
//
// Application impact: contracts can be written without thinking about access
// lists; they're a transaction-level concern. Protocols that benefit:
// MEV bots that pre-compute the storage they'll touch and pass it as access list
// for a small gas discount and inclusion priority.
```

</template>

<template #csharp>

```csharp
// Neo witness scopes — equivalent of EIP-2930's authorisation surface:
//
//   enum WitnessScope : byte {
//     None             = 0x00,    // signer pays fee but grants no contract authority
//     CalledByEntry    = 0x01,    // grant only to top-level invoke and direct callees
//     CustomContracts  = 0x10,    // grant to listed contracts only
//     CustomGroups     = 0x20,    // grant to listed groups (cert-signed)
//     WitnessRules     = 0x40,    // arbitrary pattern matching
//     Global           = 0x80     // unlimited grant (dangerous; usually rejected)
//   }
//
// Building a transaction with access-list-equivalent scoping:
//
//   const tx = new TransactionBuilder()
//     .invoke(swapRouter, "swap", [tokenIn, tokenOut, amount])
//     .signers([{
//       account: user,
//       scopes:  WitnessScope.CustomContracts,
//       allowedContracts: [tokenIn, tokenOut, swapRouter]
//     }])
//     .build();
//
// The protocol enforces: any Contract.Call from inside the transaction that
// would require user's witness must be to one of allowedContracts, or
// CheckWitness(user) returns false. This is a finer-grained authorisation
// surface than EIP-2930's access list, which is purely a gas-pricing hint.

using Neo;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Native;
using Neo.SmartContract.Framework.Services;

namespace R3E.Examples;

[DisplayName("WitnessIntrospection")]
public class WitnessIntrospection : SmartContract
{
    public static UInt160[] CurrentSigners()
    {
        var signers = Runtime.CurrentSigners;
        var output  = new UInt160[signers.Length];
        for (int i = 0; i < signers.Length; i++) output[i] = signers[i].Account;
        return output;
    }
}
```

### Why Neo's Version Is Stronger

EIP-2930 access lists are advisory — they're a gas optimisation, not an
authorisation boundary. Witness scopes are **enforced**: a contract that's not in
your allowed list cannot pretend you authorised it.

</template>

</StandardEntry>

</StandardsMirror>