# Shadow Delegation

### In Production: How Yuga Uses Shadow Delegation

Yuga Labs uses shadow delegation to let holders of Ape NFTs on Ethereum delegate access to a wallet on ApeChain. This allows users to verify ownership and participate in ApeChain experiences **without bridging or moving their Apes**.

By combining `Delegate Registry v2` and `ExclusiveDelegateResolver`, Yuga enables onchain delegation that’s:

* **Non-custodial**: the NFT stays in the original wallet
* **Cross-chain**: access is granted to a different wallet on another chain
* **Exclusive**: only one delegate per token is valid at a time

{% embed url="<https://x.com/yugalabs/status/1914363571280159172?s=46&t=jXxAznseRvMWhHxSBWZ72A>" %}

### Infinite Use Cases

`Delegate Registry v2` provides the primitives for onchain NFT access control, forming the base layer upon which **shadow delegation** builds customizable, scoped, and enforceable delegation logic.

Use cases include:

* Assign agents to act on behalf of wallets across chains
* Enable wearables, emotes, or other metadata-bound behaviors
* Grant permissions in identity systems, social graphs, or gaming avatars
* Build composable avatars and modular inventories
* Enforce execution rights on specific chains or applications

***

### Core Concepts

#### 1) Delegate Registry v2

Delegate Registry v2 is an onchain registry that allows users to grant permission to other addresses to act on their behalf. It serves as a data layer, returning a boolean for whether a delegation exists.

Delegations can be scoped:

* to specific tokens via `checkDelegateForERC721`
* to particular contracts `checkDelegateForContract`
* across all tokens and contracts `checkDelegateForAll`

The registry does not enforce rules like exclusivity, expiration, or precedence. It simply stores data.\
\&#xNAN;**`ExclusiveDelegateResolver`**, developed by Yuga Labs, is responsible for interpreting that data and enforcing logic.

#### 2) Resolver Contracts ([ExclusiveDelegateResolver](https://github.com/yuga-labs/ExclusiveDelegateResolver))

Resolver contracts apply custom logic to interpret delegation records by answering:

> "Who currently has the right to act on this token, under these rules?"

Yuga Labs’ `ExclusiveDelegateResolver` is used in production (e.g. Otherside, ApeChain) to enforce exclusive, scoped, onchain delegation, letting one wallet act on behalf of another without moving the NFT. It scans delegations scoped to a `rightsNamespace` and applies specificity rules (**token > contract > all-assets**) to return the active delegate.

#### 3) The Shadow Delegation Pattern

Shadow delegation is a design pattern that combines `Delegate Registry v2` with a resolver like `ExclusiveDelegateResolver` to enable non-custodial, exclusive delegation, scoped by use case and enforced onchain.

In this pattern:

* Only one delegation per token (or namespace) should be valid at a time
* Previous delegates must be explicitly revoked before a new one is assigned
* Asset ownership remains with the original holder; no escrow or transfers

This pattern works by combining scoped delegation rights with onchain resolution logic, illustrated below.

### How to Implement It

Shadow delegation follows a simple 3-step flow:

<figure><img src="https://3301342738-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKMp3PwjpKKfLzakTeUUs%2Fuploads%2FIyaEy5xZUaTJqFYy4UF2%2F3step.png?alt=media&#x26;token=890aea76-237f-4a0c-9a57-d7faebe19255" alt="" width="375"><figcaption></figcaption></figure>

1. **Delegate** access to a token using `delegateERC721()`
2. **Resolve** the active delegate using `ExclusiveDelegateResolver`
3. **Enforce** the delegation with an onchain check (e.g. `require(msg.sender == delegate)`)

#### Step 1: Delegate an NFT via Delegate Registry v2

The owner calls `delegateERC721()` on the registry to assign a delegate for a specific NFT:

```solidity
delegateERC721(
  address delegate,
  address contract_,
  uint256 tokenId,
  bytes32 rights,
  bool value
);
```

* `delegate`: The address receiving the delegation.
* `contract_`: The NFT contract address.
* `tokenId`: The specific token ID to delegate.
* `rights`: A `bytes32` value representing delegation scope.
* `value`: Set to `true` to enable, or `false` to revoke.

In the shadow delegation pattern, you must scope delegation to a unique chain or context by computing a `rights` value. Use the following utility:

```tsx
import { encodeAbiParameters, keccak256 } from 'viem';

function computeShadowRights(shadowChainId?: number): string {
    if (!isShadowDelegation) return '';
    if (!shadowChainId) return '0x000000000000000000000000000000000000000000000000000000ffffffffff';

    const encodedData = encodeAbiParameters([{ type: 'uint' }], [BigInt(shadowChainId)]);
    const hash = keccak256(encodedData);
    const namespace = hash.slice(0, 50);
    const padding = '000000';
    const maxRights = (BigInt(2) ** BigInt(40) - BigInt(1)).toString(16).padStart(10, '0');
    const rights = namespace + padding + maxRights;
		
    return rights;
  }
```

{% hint style="warning" %}
⚠️ **Revoke Before Reassigning**\
If you're delegating the same token (and rights) to a new address, you must first revoke the previous delegate using `delegateERC721(..., false)`. Otherwise, the storage slot is overwritten, and the resolver may return an outdated result.

💡 **Use `multicall()` to Minimize UX & Gas**\
Apps can batch the revocation and reassignment into a single transaction using the registry’s [`multicall()`](https://docs.delegate.xyz/upgrade-to-v2/batching) method to reduce friction and cost.

🛠️ **See It Live**\
Yuga Labs uses this pattern in production. See how their [Shadow Beacon contract](https://apescan.io/address/0x00000000000087c6dbadc090d39bc10316f20658#code) performs explicit revocation before reassignment (lines 933–991).
{% endhint %}

**How Rights are Constructed and Resolved**

The `rights` field is a 32-byte (`bytes32`) value that encodes the scope and expiry of a delegation. This is how the resolver knows **what context the delegation applies to**, **how specific it is**, and **whether it’s still valid**.

<table><thead><tr><th width="85.38671875">Bytes</th><th width="239.328125">Purpose</th><th>Notes</th></tr></thead><tbody><tr><td>0–23</td><td><code>rightsNamespace</code></td><td>Used to scope delegations (e.g., by chain or feature)</td></tr><tr><td>24–28</td><td>Reserved padding (<code>000000</code>)</td><td>Required by resolver formatting</td></tr><tr><td>29–33</td><td><code>uint40 expiry</code></td><td>Unix timestamp after which delegation is invalid</td></tr></tbody></table>

**Resolver Behavior:**

* **Chain-specific scoping**: Namespaces are typically derived from `keccak256(chainId)` so you can delegate the same token to different wallets on different chains. These are resolved independently.
* **Global fallback delegation**: If the **first 24 bytes are zero**, i.e., `0x000...000`, the delegation is considered **global,** valid across all chains or apps. These are **ranked lowest in specificity** and only take effect if no scoped delegation exists.
* **Automatic expiry**: The final 5 bytes of `rights` encode an **expiry timestamp** (`uint40`). When the current block time exceeds this value, the delegation is considered invalid.\
  If you don't want an expiry, use the max value: `0xffffffffff`.

This layered resolution system allows you to combine **scoped**, **exclusive**, **expiring**, and **cross-chain** delegation behavior.

#### Step 2: Use the ExclusiveDelegateResolver

Once delegation is recorded, you can resolve it using `ExclusiveDelegateResolver`, which applies namespace-based resolution logic (token > contract > all-assets) and scopes based on the first 24 bytes of the `rights` field. This means a token-level delegation takes priority over a contract-level one under the same namespace.

Resolver interface:

```solidity
function exclusiveOwnerByRights(
  address nftContract,
  uint256 tokenId,
  bytes24 rightsNamespace
) external view returns (address);
```

Example usage:

```solidity
bytes24 rightsNamespace = bytes24(keccak256(abi.encode(shadowChainId)));

address delegate = IExclusiveDelegateResolver(resolverAddress).exclusiveOwnerByRights(
  nftContractAddress,
  tokenId,
  rightsNamespace
);
```

This reinforces the resolver concept from Core Concept #2. The registry stores raw data and the resolver interprets it according to a rule set (in this case, exclusivity via specificity).

#### Step 3: Enforce Delegation in Your App or Smart Contract

Whenever your application or NFT/game logic needs to check control, replace `ownerOf(tokenId)` or raw ownership checks with delegation resolution:

```solidity
require(msg.sender == delegate, "Not authorized delegate");
```

This enforces **non-custodial, exclusive access** via the resolver logic, honoring only the delegate for the scoped rights.

***

### Resources

* Delegate Registry v2: <https://github.com/delegatexyz/delegate-registry-v2>
* ApeChain Shadow Delegations: <https://docs.apechain.com/start-building/NFT-Shadows>
* Yuga’s Exclusive Resolver: <https://github.com/yuga-labs/ExclusiveDelegateResolver>

***

### Summary

`Shadow delegation` with `Delegate Registry v2` and `ExclusiveDelegateResolver` enables:

* Fully onchain delegation logic
* Exclusive, non-custodial access
* Use case-specific scoping via `rightsNamespace`

Developers can build on this pattern by customizing how delegations are scoped and managed, without needing to deploy their own resolver contract.
