> For the complete documentation index, see [llms.txt](/llms.txt)

# AIP-721: Non-Fungible Token

[Source](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/nft_contract)

AIP-721 defines a non-fungible token (NFT). Each token is identified by a unique `token_id` field. Tokens can be held privately in the note hash tree or publicly in a map from `token_id` to owner address.

## Storage layout

```rust
#[storage]
struct Storage<Context> {
    symbol: PublicImmutable<FieldCompressedString, Context>,
    name: PublicImmutable<FieldCompressedString, Context>,
    private_nfts: Owned<PrivateSet<NFTNote, Context>, Context>,
    nft_exists: Map<Field, PublicMutable<bool, Context>, Context>,
    public_owners: Map<Field, PublicMutable<AztecAddress, Context>, Context>,
    minter: PublicImmutable<AztecAddress, Context>,
}
```

`nft_exists` tracks whether a given `token_id` has been minted, while `public_owners` records the current public owner. When an NFT is moved to a private note, the `public_owners` entry is cleared and the NFT is stored as an `NFTNote` in the holder's private set.

## NFTNote and partial-note support

Each private NFT is represented as an `NFTNote` containing only the `token_id`:

```rust
/// A private note representing a token id associated to an account.
#[derive(Eq, Serialize, Packable)]
#[custom_note]
pub struct NFTNote {
    /// The ID of the token represented by this note.
    pub token_id: Field,
}
```

`NFTNote::partial` creates a `PartialNFTNote`, an incomplete note whose owner stays private:

```rust
pub fn partial(
    owner: AztecAddress,
    context: &mut PrivateContext,
    recipient: AztecAddress,
    completer: AztecAddress,
) -> PartialNFTNote {
    // Safety: We use the randomness to preserve the privacy of the note recipient by preventing brute-forcing,
    // so a malicious sender could use non-random values to make the note less private. But they already know
    // the full note pre-image anyway, and so the recipient already trusts them to not disclose this
    // information. We can therefore assume that the sender will cooperate in the random value generation.
    let randomness = unsafe { random() };

    // We create a commitment to the private data, which we then use to construct the log we send to the recipient.
    let commitment = compute_partial_commitment(owner, randomness);

    // Our partial note log encoding scheme includes a field with the tag of the public completion log, and we use
    // the commitment as the tag. This is good for multiple reasons:
    //  - the commitment is uniquely tied to this partial note
    //  - the commitment is already public information, so we're not revealing anything else
    //  - we don't need to create any additional information, private or public, for the tag
    //  - other contracts cannot impersonate us and emit logs with the same tag due to public log siloing
    let private_log_content = NFTPartialNotePrivateLogContent {};

    do_private_message_delivery(
        context,
        || {
            encode_partial_note_private_message(
                private_log_content,
                owner,
                randomness,
                commitment,
            )
        },
        Option::none(),
        recipient,
        MessageDelivery::onchain_unconstrained(),
    );

    let partial_note = PartialNFTNote { commitment };

    // Now we compute the validity commitment and push it to the nullifier tree. It can be safely pushed to
    // the nullifier tree since it uses its own separator, making collisions with actual note nullifiers
    // practically impossible.
    let validity_commitment = partial_note.compute_validity_commitment(completer);
    context.push_nullifier_unsafe(validity_commitment);

    partial_note
}
```

The `commitment` commits to the future owner and a random value, so completing the note cannot change who owns it. The storage slot and `token_id` become public when the note is completed. What needs protecting is who completes it: without some form of access control, any party could fill the commitment before the intended completer, for example with a different token. The validity commitment prevents this: it binds the partial note to the designated completer and is pushed as a nullifier, and completion checks that it exists for the `completer` passed in. The note does not check who the caller is, so the NFT contract passes `msg_sender()` as the completer, and only the designated completer can finalize the note. This mirrors the partial-note pattern in [AIP-20](/developers/docs/aztec-nr/standards/aip-20.md) but applies it to NFT transfers.

## Partial-note transfer commitment

The external entry point for initiating a partial NFT transfer is:

```rust
#[external("private")]
fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field {
    let commitment = self.internal._initialize_transfer_commitment(to, completer);
    commitment.commitment
}
```

This function returns `commitment.commitment`, the `Field` stored in the `PartialNFTNote`. The AIP-20 equivalent returns `commitment.to_field()` for `PartialUintNote`.
