diff --git a/docs/AGENTS.md b/docs/AGENTS.md
index 000bd734e..9a1a7a8f7 100644
--- a/docs/AGENTS.md
+++ b/docs/AGENTS.md
@@ -50,7 +50,7 @@ npx skills add base/base-skills
|Build on Base/Accept Payments/Confirm and Reconcile:build-on-base/accept-payments/verify-a-payment,build-on-base/accept-payments/watch-for-payments,build-on-base/accept-payments/reconcile-payments
|Build on Base/Accept Payments/Return and Pay Out:build-on-base/accept-payments/refund-a-payment,build-on-base/accept-payments/send-a-payout,build-on-base/accept-payments/split-a-payment
|Build on Base/Accept Payments/Accept Agentic Payments:build-on-base/accept-payments/charge-for-an-api,build-on-base/accept-payments/settle-usage-based-payments,build-on-base/accept-payments/batch-high-frequency-payments,build-on-base/accept-payments/call-a-paid-service
-|Specifications/Specifications:specifications/overview,specifications/native-account-abstraction,specifications/flashblocks
+|Specifications/Specifications:specifications/overview,specifications/flashblocks
|Specifications/Specifications/Base Protocol:specifications/base-protocol/overview,specifications/base-protocol/batcher,specifications/base-protocol/design-goals
|Specifications/Specifications/Base Protocol/Consensus:specifications/base-protocol/consensus/specification,specifications/base-protocol/consensus/derivation,specifications/base-protocol/consensus/p2p,specifications/base-protocol/consensus/rpc
|Specifications/Specifications/Base Protocol/Execution:specifications/base-protocol/execution/l2-execution-engine,specifications/base-protocol/execution/precompiles,specifications/base-protocol/execution/predeploys,specifications/base-protocol/execution/preinstalls
@@ -73,7 +73,7 @@ npx skills add base/base-skills
|SDKs & APIs/Base Verify API:sdks/base-verify/overview,sdks/base-verify/verify-social-accounts,sdks/base-verify/verify-users-onchain
|SDKs & APIs/Migrated Documentation:sdks/migrated-products
|Upgrades/Overview:upgrades/overview,base-chain/network-information/configuration-changelog
-|Upgrades/Denim:upgrades/denim/overview,upgrades/denim/200ms-blocks,upgrades/denim/migrate-from-flashblocks
+|Upgrades/Denim:upgrades/denim/overview,specifications/native-account-abstraction,upgrades/denim/200ms-blocks,upgrades/denim/migrate-from-flashblocks
|Upgrades/Cobalt:upgrades/cobalt/overview,upgrades/cobalt/dynamic-upgrades,base-chain/specs/reference/b20/changelog/02-cobalt-b20asset-multiplier,base-chain/specs/reference/b20/changelog/02-cobalt-b20-seize,base-chain/specs/reference/b20/changelog/02-cobalt-policyregistry-composite-policy,upgrades/cobalt/validity-transactions
|Upgrades/Beryl:upgrades/beryl/overview,upgrades/beryl/reth-v2,upgrades/beryl/reducing-canonical-withdrawal-delay,upgrades/beryl/b20
|Upgrades/Azul:upgrades/azul/overview,upgrades/azul/node-upgrade,upgrades/azul/exec-engine,upgrades/azul/proofs
diff --git a/docs/llms-full.txt b/docs/llms-full.txt
index 21f4fae66..ffe83016d 100644
--- a/docs/llms-full.txt
+++ b/docs/llms-full.txt
@@ -300,8 +300,6 @@ const client = createPublicClient({ chain: base, transport: http() })
- [Changelog](https://docs.base.org/specifications/b20/changelog): Per-hardfork, per-feature migration notes for the B20 token standard, newest first, including new methods, deprecations, and activation dates.
-- [Native Account Abstraction](https://docs.base.org/specifications/native-account-abstraction): EIP-8130 reference for Base: vibenet chain details, client setup, transaction structure, account configuration, authenticators, and payers.
-
#### Transactions
- [Transaction Ordering](https://docs.base.org/specifications/transactions/transaction-ordering): Transactions are ordered based priority fee and arrival time, which determines which Flashblock they are included in.
@@ -484,6 +482,8 @@ const client = createPublicClient({ chain: base, transport: http() })
- [Overview](https://docs.base.org/upgrades/denim/overview): Denim introduces native blocks at a 200ms cadence, onchain millisecond time through BaseTime, and millisecond-resolution RPC timestamps.
+- [Native Account Abstraction](https://docs.base.org/specifications/native-account-abstraction): EIP-8130 reference for Base: vibenet chain details, client setup, transaction structure, account configuration, authenticators, and payers.
+
- [200ms Native Blocks](https://docs.base.org/upgrades/denim/200ms-blocks): Specification for Denim's canonical 200ms blocks, including BaseTime, derivation, validation, and RPC behavior.
- [Migrate From Flashblocks](https://docs.base.org/upgrades/denim/migrate-from-flashblocks): Migrate your Flashblocks integration to 200ms blocks.
@@ -500,7 +500,7 @@ const client = createPublicClient({ chain: base, transport: http() })
- [PolicyRegistry: Composite Policies (UNION / INTERSECT)](https://docs.base.org/base-chain/specs/reference/b20/changelog/02-cobalt-policyregistry-composite-policy): Cobalt adds UNION and INTERSECT composite policies to PolicyRegistry so B20 integrations can combine simple authorization policies without flattening their member lists.
-- [Validity Transactions](https://docs.base.org/upgrades/cobalt/validity-transactions): Validity transactions ship with the Cobalt upgrade. Read the full specification in the Specifications tab.
+- [Validity Transactions](https://docs.base.org/upgrades/cobalt/validity-transactions): Validity transactions ship with the Cobalt upgrade.
### Beryl
diff --git a/docs/llms.txt b/docs/llms.txt
index d9a6f2e19..ca4d49d96 100644
--- a/docs/llms.txt
+++ b/docs/llms.txt
@@ -234,8 +234,6 @@
- [Changelog](https://docs.base.org/specifications/b20/changelog): Per-hardfork, per-feature migration notes for the B20 token standard, newest first, including new methods, deprecations, and activation dates.
-- [Native Account Abstraction](https://docs.base.org/specifications/native-account-abstraction): EIP-8130 reference for Base: vibenet chain details, client setup, transaction structure, account configuration, authenticators, and payers.
-
#### Transactions
- [Transaction Ordering](https://docs.base.org/specifications/transactions/transaction-ordering): Transactions are ordered based priority fee and arrival time, which determines which Flashblock they are included in.
@@ -418,6 +416,8 @@
- [Overview](https://docs.base.org/upgrades/denim/overview): Denim introduces native blocks at a 200ms cadence, onchain millisecond time through BaseTime, and millisecond-resolution RPC timestamps.
+- [Native Account Abstraction](https://docs.base.org/specifications/native-account-abstraction): EIP-8130 reference for Base: vibenet chain details, client setup, transaction structure, account configuration, authenticators, and payers.
+
- [200ms Native Blocks](https://docs.base.org/upgrades/denim/200ms-blocks): Specification for Denim's canonical 200ms blocks, including BaseTime, derivation, validation, and RPC behavior.
- [Migrate From Flashblocks](https://docs.base.org/upgrades/denim/migrate-from-flashblocks): Migrate your Flashblocks integration to 200ms blocks.
@@ -434,7 +434,7 @@
- [PolicyRegistry: Composite Policies (UNION / INTERSECT)](https://docs.base.org/base-chain/specs/reference/b20/changelog/02-cobalt-policyregistry-composite-policy): Cobalt adds UNION and INTERSECT composite policies to PolicyRegistry so B20 integrations can combine simple authorization policies without flattening their member lists.
-- [Validity Transactions](https://docs.base.org/upgrades/cobalt/validity-transactions): Validity transactions ship with the Cobalt upgrade. Read the full specification in the Specifications tab.
+- [Validity Transactions](https://docs.base.org/upgrades/cobalt/validity-transactions): Validity transactions ship with the Cobalt upgrade.
### Beryl
diff --git a/docs/specifications/b20/reference/constants-addresses.mdx b/docs/specifications/b20/reference/constants-addresses.mdx
index d3dc5c3c3..ddc30b0ee 100644
--- a/docs/specifications/b20/reference/constants-addresses.mdx
+++ b/docs/specifications/b20/reference/constants-addresses.mdx
@@ -62,6 +62,17 @@ Policy type bytes:
| `UNION` | `0x02` |
| `INTERSECT` | `0x03` |
+## Policy Constants
+
+| Name | Value | Description |
+|---|---|---|
+| `TRANSFER_SENDER_POLICY` | `keccak256("TRANSFER_SENDER_POLICY")`
`0xb81736c875ab819dd97f59f2a6542cfb731ad52b4ae15a6f24df2fb02b0327f5` | Consulted for `from` on `transfer` and `transferFrom`. |
+| `TRANSFER_RECEIVER_POLICY` | `keccak256("TRANSFER_RECEIVER_POLICY")`
`0x8a4b3fa2d8b921852bc0089c6ef0958aa6961897be36fd731330fe2cd23f8363` | Consulted for `to` on `transfer` and `transferFrom`. |
+| `TRANSFER_EXECUTOR_POLICY` | `keccak256("TRANSFER_EXECUTOR_POLICY")`
`0x10be5173aff2a44e748bd9acd8b19fe34689581398a9db7ba2fb671e786ff7d8` | Consulted for `msg.sender` on every transfer entrypoint (`transfer`, `transferFrom`, and memo'd variants). |
+| `MINT_RECEIVER_POLICY` | `keccak256("MINT_RECEIVER_POLICY")`
`0xa0d5ae037e66a09119acf080a1d807abb9b6d03b6b9130eb19f7c1e6bdb8ffc8` | Consulted for `to` on `mint`. |
+| `SEIZE_HOLDER_POLICY` | `keccak256("SEIZE_HOLDER_POLICY")`
`0x1497ab2b67ebb0a75dd9cdd6aec9f0e64620e6b87e911af7a088ac12e58d9ef2` | Consulted for `from` on `seizeWithMemo`; `from` is seizable when unauthorized under this policy. |
+| `SEIZE_RECEIVER_POLICY` | `keccak256("SEIZE_RECEIVER_POLICY")`
`0xbf15b19caf5c77422c038bc25f26b8b815c3a14f6d04c6616076b81bcfe07b3d` | Consulted for `to` on `seizeWithMemo`. |
+
## Variant Bytes
| Variant | Byte | Address shape |
diff --git a/docs/specifications/b20/reference/interfaces/ib20/transfer-executor-policy.mdx b/docs/specifications/b20/reference/interfaces/ib20/transfer-executor-policy.mdx
index c705ecd3b..f55a5c0bc 100644
--- a/docs/specifications/b20/reference/interfaces/ib20/transfer-executor-policy.mdx
+++ b/docs/specifications/b20/reference/interfaces/ib20/transfer-executor-policy.mdx
@@ -1,6 +1,6 @@
---
title: "IB20.TRANSFER_EXECUTOR_POLICY"
-description: "Generated B20 reference for TRANSFER_EXECUTOR_POLICY()."
+description: "Returns the policy slot consulted against msg.sender on every transfer entrypoint in IB20."
---
@@ -18,21 +18,25 @@ function TRANSFER_EXECUTOR_POLICY() external view returns (bytes32);
## Description
-Policy slot consulted against `msg.sender` on `transferFrom` when distinct from `from`. Not consulted on `transfer`.
+Policy slot consulted against `msg.sender` (the initiator) on every transfer, including when `msg.sender == from`. This applies to `transfer`, `transferFrom`, and their memo variants.
Bypassed for factory-originated calls during the creation (bootstrap) window; see `IB20Factory.createB20`.
+
+`TRANSFER_EXECUTOR_POLICY` now applies to all transfer entrypoints — including `transfer` and self-`transferFrom` where `msg.sender == from`. Previously it applied only when `msg.sender != from` on the `transferFrom` path. Tokens with a restrictive executor policy will block holder-initiated transfers unless those holders are authorized as executors.
+
+
## Returns
Policy scope constant.
## Access Control
-Read-only or ERC-20-standard access rules unless the NatSpec states otherwise.
+Read-only view function; no access restriction.
## Policy Interaction
-No direct policy interaction.
+The executor policy is checked against `msg.sender` on every transfer path (`transfer`, `transferFrom`, `transferWithMemo`, `transferFromWithMemo`). There is no exemption when `msg.sender == from`. An unset policy slot is always-allow, so tokens that never configured this policy are unaffected.
## Example
diff --git a/docs/specifications/b20/reference/interfaces/ib20/transfer-from-with-memo.mdx b/docs/specifications/b20/reference/interfaces/ib20/transfer-from-with-memo.mdx
index 450d08192..a529a26c2 100644
--- a/docs/specifications/b20/reference/interfaces/ib20/transfer-from-with-memo.mdx
+++ b/docs/specifications/b20/reference/interfaces/ib20/transfer-from-with-memo.mdx
@@ -1,10 +1,8 @@
---
title: "IB20.transferFromWithMemo"
-description: "Generated B20 reference for transferFromWithMemo(address,address,uint256,bytes32)."
+description: "Transfers tokens on behalf of another address and emits a Memo event, with executor, sender, and receiver policy enforcement."
---
-
-
## Signature
```solidity IB20.sol
@@ -33,13 +31,26 @@ Same as `transferFrom`, plus emits `Memo` immediately after the standard `Transf
Always `true` on success.
-## Access Control
+## Policy Interaction
+
+`TRANSFER_EXECUTOR_POLICY` is checked against `msg.sender` on every call, including when `msg.sender == from`. Sender (`from`) and receiver (`to`) policies are also enforced. All three checks are bypassed during the factory bootstrap window; see `IB20Factory.createB20`.
-Read-only or ERC-20-standard access rules unless the NatSpec states otherwise.
+
+`TRANSFER_EXECUTOR_POLICY` now applies even when `msg.sender == from`. Tokens that have configured a restrictive executor policy will block holder-initiated transfers unless the holder is explicitly authorized as an executor.
+
-## Policy Interaction
+## Revert Conditions
-Checks `TRANSFER_EXECUTOR_POLICY` when `msg.sender != from`, plus sender and receiver transfer scopes.
+| Revert | Condition |
+|---|---|
+| `ContractPaused(TRANSFER)` | The `TRANSFER` operation is paused. |
+| `InvalidReceiver` | `to == address(0)`. |
+| `InvalidSender` | `from == address(0)`. |
+| `InsufficientAllowance` | The caller's allowance from `from` is below `amount`. |
+| `PolicyForbids(TRANSFER_EXECUTOR_POLICY, ...)` | `msg.sender` is not authorized by the executor policy. |
+| `PolicyForbids(TRANSFER_SENDER_POLICY, ...)` | `from` is not authorized by the sender policy. |
+| `PolicyForbids(TRANSFER_RECEIVER_POLICY, ...)` | `to` is not authorized by the receiver policy. |
+| `InsufficientBalance` | `from`'s balance is below `amount`. |
## Example
diff --git a/docs/specifications/b20/reference/interfaces/ib20/transfer-from.mdx b/docs/specifications/b20/reference/interfaces/ib20/transfer-from.mdx
index 4dc9d89cf..be6360c56 100644
--- a/docs/specifications/b20/reference/interfaces/ib20/transfer-from.mdx
+++ b/docs/specifications/b20/reference/interfaces/ib20/transfer-from.mdx
@@ -1,10 +1,8 @@
---
title: "IB20.transferFrom"
-description: "Generated B20 reference for transferFrom(address,address,uint256)."
+description: "Transfers tokens from a specified address using the caller's allowance, with executor, sender, and receiver policy enforcement."
---
-
-
## Signature
```solidity IB20.sol
@@ -38,7 +36,7 @@ Always `true` on success.
- `InvalidReceiver` when `to == address(0)`.
- `InvalidSender` when `from == address(0)`.
- `InsufficientAllowance` when the caller's allowance from `from` is below `amount`.
-- `PolicyForbids(TRANSFER_EXECUTOR_POLICY, ...)` when `msg.sender != from` and `msg.sender` is not authorized.
+- `PolicyForbids(TRANSFER_EXECUTOR_POLICY, ...)` when `msg.sender` is not authorized.
- `PolicyForbids(TRANSFER_SENDER_POLICY, ...)` when `from` is not authorized.
- `PolicyForbids(TRANSFER_RECEIVER_POLICY, ...)` when `to` is not authorized.
- `InsufficientBalance` when `from`'s balance is below `amount`.
@@ -49,7 +47,7 @@ Read-only or ERC-20-standard access rules unless the NatSpec states otherwise.
## Policy Interaction
-Checks `TRANSFER_EXECUTOR_POLICY` when `msg.sender != from`, plus sender and receiver transfer scopes.
+Checks `TRANSFER_EXECUTOR_POLICY` against `msg.sender` on every call, including when `msg.sender == from`. An executor allowlist can therefore restrict holder-initiated transfers, with no self-transfer exemption. Also checks sender and receiver transfer scopes. All policy checks are bypassed during the factory bootstrap window.
## Example
diff --git a/docs/specifications/b20/reference/interfaces/ib20/transfer.mdx b/docs/specifications/b20/reference/interfaces/ib20/transfer.mdx
index 214930928..a7c0c8870 100644
--- a/docs/specifications/b20/reference/interfaces/ib20/transfer.mdx
+++ b/docs/specifications/b20/reference/interfaces/ib20/transfer.mdx
@@ -1,6 +1,6 @@
---
title: "IB20.transfer"
-description: "Generated B20 reference for transfer(address,uint256)."
+description: "Transfers tokens from msg.sender to a destination address, subject to executor, sender, and receiver policy checks."
---
@@ -36,17 +36,22 @@ Always `true` on success.
- `ContractPaused(TRANSFER)` when `TRANSFER` is paused.
- `InvalidReceiver` when `to == address(0)`.
- `InvalidSender` when `msg.sender == address(0)`.
-- `PolicyForbids(TRANSFER_SENDER_POLICY, ...)` when `msg.sender` is not authorized.
+- `PolicyForbids(TRANSFER_EXECUTOR_POLICY, ...)` when `msg.sender` is not authorized as an executor.
+- `PolicyForbids(TRANSFER_SENDER_POLICY, ...)` when `msg.sender` is not authorized as a sender.
- `PolicyForbids(TRANSFER_RECEIVER_POLICY, ...)` when `to` is not authorized.
- `InsufficientBalance` when `msg.sender`'s balance is below `amount`.
## Access Control
-Read-only or ERC-20-standard access rules unless the NatSpec states otherwise.
+Standard ERC-20 caller rules apply. The factory bootstrap bypass suppresses all three policy checks during the creation window; see `IB20Factory.createB20`.
## Policy Interaction
-Checks `TRANSFER_SENDER_POLICY` and `TRANSFER_RECEIVER_POLICY`; `approve` and `permit` are not policy-gated.
+Checks `TRANSFER_EXECUTOR_POLICY`, `TRANSFER_SENDER_POLICY`, and `TRANSFER_RECEIVER_POLICY` on every call. The executor check runs even when `msg.sender == from`, so an executor allowlist can restrict holder-initiated transfers with no self-transfer exemption.
+
+
+`TRANSFER_EXECUTOR_POLICY` now applies to `transfer` (not only `transferFrom`). If you have configured a restrictive executor allowlist, holders who are not on that list can no longer initiate transfers directly — they must use an authorized executor.
+
## Example
diff --git a/docs/specifications/b20/reference/invariants-tests.mdx b/docs/specifications/b20/reference/invariants-tests.mdx
index 0fafbfc47..e7552a0c6 100644
--- a/docs/specifications/b20/reference/invariants-tests.mdx
+++ b/docs/specifications/b20/reference/invariants-tests.mdx
@@ -26,7 +26,7 @@ These invariants and conformance test cases are the normative behavioral guarant
### Transfer Policies
10. `approve` is never policy-gated.
-11. `TRANSFER_EXECUTOR_POLICY` is checked only on `transferFrom`, never on `transfer`.
+11. `TRANSFER_EXECUTOR_POLICY` is checked on every transfer entrypoint (`transfer`, `transferFrom`, and memo'd variants), including when `msg.sender == from`.
12. `MINT_RECEIVER_POLICY` is always enforced, even during factory `initCalls`.
13. All three transfer-side scopes are bypassed during `initCalls`.
14. Every scope defaults to `ALWAYS_ALLOW` at token creation.
@@ -104,74 +104,75 @@ These invariants and conformance test cases are the normative behavioral guarant
| 15 | Sender on blocklist calls `transfer` | Reverts with `PolicyForbids` |
| 16 | Sender on blocklist calls `approve` | Succeeds |
| 17 | `transferFrom` where executor is on executor blocklist | Reverts |
-| 18 | Direct `transfer` by sender on executor blocklist (not sender blocklist) | Succeeds |
-| 19 | During `initCalls`, transfer from blocklisted sender | Succeeds — bypass |
-| 20 | During `initCalls`, mint to receiver on mint-receiver blocklist | Reverts — never bypassed |
+| 18 | Direct `transfer` by sender on executor blocklist (not sender blocklist) | Reverts with `PolicyForbids` |
+| 19 | `transferFrom` where `msg.sender == from` and executor is on executor blocklist | Reverts with `PolicyForbids` |
+| 20 | During `initCalls`, transfer from blocklisted sender | Succeeds — bypass |
+| 21 | During `initCalls`, mint to receiver on mint-receiver blocklist | Reverts — never bypassed |
### Mint and Supply Cap
| # | Scenario | Expected |
|---|----------|----------|
-| 21 | Mint that would push `totalSupply` above cap | Reverts with `SupplyCapExceeded` |
-| 22 | Mint exactly to cap | Succeeds |
-| 23 | `updateSupplyCap` below current `totalSupply` | Reverts with `InvalidSupplyCap` |
-| 24 | Burn tokens, then mint up to cap | Succeeds |
-| 25 | Mint while `MINT` is paused | Reverts |
+| 22 | Mint that would push `totalSupply` above cap | Reverts with `SupplyCapExceeded` |
+| 23 | Mint exactly to cap | Succeeds |
+| 24 | `updateSupplyCap` below current `totalSupply` | Reverts with `InvalidSupplyCap` |
+| 25 | Burn tokens, then mint up to cap | Succeeds |
+| 26 | Mint while `MINT` is paused | Reverts |
### Burn and Seize
| # | Scenario | Expected |
|---|----------|----------|
-| 26 | `burnBlocked` on frozen account | Succeeds |
-| 27 | `burnBlocked` on non-frozen account | Reverts |
-| 28 | `burnBlocked` by holder of `BURN_ROLE` (not `BURN_BLOCKED_ROLE`) | Reverts |
-| 29 | Freeze, seize full balance, re-mint to recovery | Succeeds |
-| 30 | Burn while `BURN` is paused | Reverts |
+| 27 | `burnBlocked` on frozen account | Succeeds |
+| 28 | `burnBlocked` on non-frozen account | Reverts |
+| 29 | `burnBlocked` by holder of `BURN_ROLE` (not `BURN_BLOCKED_ROLE`) | Reverts |
+| 30 | Freeze, seize full balance, re-mint to recovery | Succeeds |
+| 31 | Burn while `BURN` is paused | Reverts |
### Pause
| # | Scenario | Expected |
|---|----------|----------|
-| 31 | Pause `TRANSFER`, call `transfer` | Reverts |
-| 32 | Pause `TRANSFER`, call `mint` | Succeeds |
-| 33 | Pause `TRANSFER`, call `approve` | Succeeds |
-| 34 | Pauser calls `unpause` without `UNPAUSE_ROLE` | Reverts |
+| 32 | Pause `TRANSFER`, call `transfer` | Reverts |
+| 33 | Pause `TRANSFER`, call `mint` | Succeeds |
+| 34 | Pause `TRANSFER`, call `approve` | Succeeds |
+| 35 | Pauser calls `unpause` without `UNPAUSE_ROLE` | Reverts |
### Memos
| # | Scenario | Expected |
|---|----------|----------|
-| 35 | `transferWithMemo` | Emits `Transfer` then `Memo` at consecutive log indices |
-| 36 | `transferFromWithMemo` | `Memo.caller` is `msg.sender`, not `from` |
-| 37 | `transfer` (non-memo variant) | No `Memo` event |
+| 36 | `transferWithMemo` | Emits `Transfer` then `Memo` at consecutive log indices |
+| 37 | `transferFromWithMemo` | `Memo.caller` is `msg.sender`, not `from` |
+| 38 | `transfer` (non-memo variant) | No `Memo` event |
### Permit
| # | Scenario | Expected |
|---|----------|----------|
-| 38 | Valid permit with correct signature, nonce, deadline | Succeeds |
-| 39 | Permit with expired deadline | Reverts |
-| 40 | Replay used permit signature | Reverts |
-| 41 | Permit signed before `updateName`, submitted after | Reverts |
-| 42 | Contract wallet signature (ERC-1271) | Reverts |
-| 43 | Permit while `TRANSFER` is paused | Succeeds |
+| 39 | Valid permit with correct signature, nonce, deadline | Succeeds |
+| 40 | Permit with expired deadline | Reverts |
+| 41 | Replay used permit signature | Reverts |
+| 42 | Permit signed before `updateName`, submitted after | Reverts |
+| 43 | Contract wallet signature (ERC-1271) | Reverts |
+| 44 | Permit while `TRANSFER` is paused | Succeeds |
### Factory
| # | Scenario | Expected |
|---|----------|----------|
-| 44 | `getB20Address` then deploy with same params | Addresses match |
-| 45 | Inspect byte 10 of deployed Asset address | Returns `0x00` |
-| 46 | Deploy same `(deployer, variant, salt)` twice | Second reverts |
-| 47 | Deploy when variant feature not activated | Reverts |
-| 48 | `initCalls` that pause `TRANSFER`, then transfer in next initCall | Transfer reverts |
+| 45 | `getB20Address` then deploy with same params | Addresses match |
+| 46 | Inspect byte 10 of deployed Asset address | Returns `0x00` |
+| 47 | Deploy same `(deployer, variant, salt)` twice | Second reverts |
+| 48 | Deploy when variant feature not activated | Reverts |
+| 49 | `initCalls` that pause `TRANSFER`, then transfer in next initCall | Transfer reverts |
### Variants
| # | Scenario | Expected |
|---|----------|----------|
-| 49 | Deploy Asset with `decimals = 5` | Reverts |
-| 50 | Update multiplier to `2e18`, check `balanceOf` for raw balance 100 | Returns 200 |
-| 51 | Reuse announcement ID | Reverts with `DuplicateAnnouncementId` |
-| 52 | `batchMint` where one recipient is not on allowlist | Reverts |
-| 53 | Deploy Stablecoin with `currency = "usd"` | Reverts — `A`–`Z` only |
+| 50 | Deploy Asset with `decimals = 5` | Reverts |
+| 51 | Update multiplier to `2e18`, check `balanceOf` for raw balance 100 | Returns 200 |
+| 52 | Reuse announcement ID | Reverts with `DuplicateAnnouncementId` |
+| 53 | `batchMint` where one recipient is not on allowlist | Reverts |
+| 54 | Deploy Stablecoin with `currency = "usd"` | Reverts — `A`–`Z` only |
diff --git a/docs/specifications/b20/specification-overview.mdx b/docs/specifications/b20/specification-overview.mdx
index 85c1a6f42..38eb8e3f1 100644
--- a/docs/specifications/b20/specification-overview.mdx
+++ b/docs/specifications/b20/specification-overview.mdx
@@ -95,13 +95,15 @@ B20 tokens store one `uint64 policyId` per supported policy scope.
|---|---|---|
| `TRANSFER_SENDER_POLICY` | `from` | `transfer`, `transferFrom`, and memo variants |
| `TRANSFER_RECEIVER_POLICY` | `to` | `transfer`, `transferFrom`, and memo variants |
-| `TRANSFER_EXECUTOR_POLICY` | `msg.sender` | `transferFrom` when `msg.sender != from` |
+| `TRANSFER_EXECUTOR_POLICY` | `msg.sender` | `transfer`, `transferFrom`, and memo variants |
| `MINT_RECEIVER_POLICY` | `to` | `mint`, `mintWithMemo` |
| `SEIZE_EXEMPT_POLICY` | `from` | `seizeWithMemo`; an authorized holder is seize-exempt, so only unauthorized holders are seizable |
| `SEIZE_RECEIVER_POLICY` | `to` | `seizeWithMemo`; destination must be authorized |
All scopes default to `ALWAYS_ALLOW` at creation. `approve` and `permit` are not policy-gated.
+`TRANSFER_EXECUTOR_POLICY` checks `msg.sender` on every transfer entrypoint — including `transfer` and self-`transferFrom` where `msg.sender == from`. An executor allowlist therefore gates who may initiate any transfer, not just delegated ones. Factory `initCalls` transfers bypass this check.
+
## Mint
`mint` and `mintWithMemo` are gated by `MINT_ROLE`, checked against `MINT_RECEIVER_POLICY`, and bounded by `supplyCap`.