Help & Support

Namespace Rules

This guide explains how to use username Namespace Rules and how to implement custom ones.

Namespace Rules allow administrators to add requirements or constraints that will be applied when a Username in a given Namespace is created or assigned to an Account.

Lens provides four built-in Group rules:

  • UsernamePricePerLengthNamespaceRule - Requires an ERC-20 payment to create a Username.

  • TokenGatedNamespaceRule - Requires an account to hold a certain token to create a Username.

  • UsernameLengthNamespaceRule - Restricts the length of Usernames.

  • UsernameReservedNamespaceRule - Reserves a set of Usernames.

To keep usernames web-friendly across the ecosystem, the Namespace primitive enforces a maximum length of 255 characters.

A fifth built-in rule, UsernameSimpleCharsetNamespaceRule, is applied by default to every new Namespace. This rule limits valid characters to a-z, 0-9, -, and _, ensuring consistency. Usernames cannot begin with - or _.

Using Namespace Rules

As part of creating Custom Namespaces, you can pass a rules object that defines the required rules and/or an anyOf set, where satisfying any one rule allows the Username creation or assignment to succeed. These rules can be built-in or custom.

This section presumes you are familiar with the process of creating a Namespace on Lens.

Username Price per Length Namespace Rule

This rule can be applied to a Username namespace to require an ERC-20 payment based on the length of the Username being created.

UsernamePricePerLengthNamespaceRule
import { bigDecimal, evmAddress } from "@lens-protocol/client";import { createUsernameNamespace } from "@lens-protocol/client/action";
const result = await createUsernameNamespace(sessionClient, {  symbol: "FOO",  namespace: "foo",  rules: {    required: [      {        usernamePricePerLengthRule: {          cost: {            currency: evmAddress("0x5678…"),            value: bigDecimal('0.5'), // Token value in its main unit          },          recipient: evmAddress("0x1234…"),          costOverrides: [            {              amount: bigDecimal('5'),              length: 1,            },            {              amount: bigDecimal('4'),              length: 2,            },            {              amount: bigDecimal('3'),              length: 3,            },            {              amount: bigDecimal('2'),              length: 4,            }          ]        }      }    ],});

Token Gated Namespace Rule

This rule requires holding a certain balance of a token (fungible or non-fungible) to create a Username.

Configuration includes the token address, the token standard (ERC-20, ERC-721, or ERC-1155), and the required token amount. For ERC-1155 tokens, an additional token type ID is required.

import { bigDecimal, evmAddress, } from "@lens-protocol/client";import { createUsernameNamespace } from "@lens-protocol/client/action";
const result = await createUsernameNamespace(sessionClient, {  symbol: "FOO",  namespace: "foo",  rules: {    required: [      {        tokenGatedRule: {          token: {            currency: evmAddress("0x1234…"),            standard: TokenStandard.Erc721,            value: bigDecimal("1"),          },        },      }    ],});

Username Length Namespace Rule

This rule can restricts the minimum and/or maximum length of Usernames.

UsernameLengthNamespaceRule
import { bigDecimal, evmAddress } from "@lens-protocol/client";import { createUsernameNamespace } from "@lens-protocol/client/action";
const result = await createUsernameNamespace(sessionClient, {  symbol: "FOO",  namespace: "foo",  rules: {    required: [      {        usernameLengthRule: {          minLength: 3,          maxLength: 10,        },      },    ],  },});

Username Reserved Namespace Rule

This rule allows to reserve a set of Usernames. As such it's slighly more complex than the other rules.

First, create a Namespace with the usernameReservedRule and pass a list of initial reserved usernames.

UsernameReservedNamespaceRule
import { bigDecimal, evmAddress } from "@lens-protocol/client";import { createUsernameNamespace } from "@lens-protocol/client/action";
const result = await createUsernameNamespace(sessionClient, {  symbol: "FOO",  namespace: "foo",  rules: {    required: [      {        usernameReservedRule: {          reserved: [            "nike",            "coca-cola",            "adidas",            "puma",            "pepsi",            "microsoft",          ],        },      },    ],  },});

Next, list all the reserved usernames as follows.

Use the paginated fetchNamespaceReservedUsernames action to fetch a list of reserved usernames for a given namespace.

import { evmAddress } from "@lens-protocol/client";import { fetchNamespaceReservedUsernames } from "@lens-protocol/client/actions";
import { client } from "./client";
const result = await fetchNamespaceReservedUsernames(client, {  namespace: evmAddress("0x1234…"),});
if (result.isErr()) {  return console.error(result.error);}
// items: Array<{ ruleId: RuleId, namespace: EvmAddress, localName: string }>const { items, pageInfo } = result.value;

See the Pagination guide for more information on how to handle paginated results.

Finally, update the reserved usernames as follows.

You MUST be authenticated as Builder and be either the owner or an admin of the Namespace you intend to configure reserved usernames for.

Use the updateReservedUsernames action to update the reserved usernames for a given namespace.

example.ts
import { evmAddress } from "@lens-protocol/client";import { updateReservedUsernames } from "@lens-protocol/client/action";
const result = await updateReservedUsernames(sessionClient, {  namespace: evmAddress("0x1234…"),  toRelease: ["alice", "bob"],  toReserve: ["charlie", "dave"],});

And, handle the result using the adapter for the library of your choice:

import { handleOperationWith } from "@lens-protocol/client/viem";
// …
const result = await updateReservedUsernames(sessionClient, {  namespace: evmAddress("0x1234…"),  toRelease: ["alice", "bob"],  toReserve: ["charlie", "dave"],}).andThen(handleOperationWith(walletClient));

See the Transaction Lifecycle guide for more information on how to determine the status of the transaction.

Custom Namespace Rule

You can also use custom rules by specifying the rule contract address, when it applies, and the configuration parameters as key-value pairs.

Custom Group Rule
import {  blockchainData,  evmAddress,  NamespaceRuleExecuteOn,} from "@lens-protocol/client";import { createUsernameNamespace } from "@lens-protocol/client/action";
const result = await createUsernameNamespace(sessionClient, {  symbol: "FOO",  namespace: "foo",  rules: {    required: [      {        unknownRule: {          address: evmAddress("0x1234…"),          executeOn: [            NamespaceRuleExecuteOn.Creating,            NamespaceRuleExecuteOn.Assigning,          ],          params: [            {              raw: {                // 32 bytes key (e.g., keccak(name))                key: blockchainData("0xac5f04…"),                // an ABI encoded value                value: blockchainData("0x00"),              },            },          ],        },      },    ],  },});

Update a Namespace Rules

To update a Namespace rules configuration, follow these steps.

You MUST be authenticated as Builder and be either the owner or an admin of the Namespace you intend to configure.

1

Identify Current Rules

First, inspect the namespace.rules field to know the current rules configuration.

type NamespaceRules = {  required: NamespaceRule;  anyOf: NamespaceRule;};

Keep note of the Rule IDs you might want to remove.

2

Update the Rules Configuration

Next, update the rules configuration of the Namespace as follows.

Use the updateNamespaceRules action to update the rules configuration of a given namespace.

import { bigDecimal, evmAddress } from "@lens-protocol/client";import { updateNamespaceRules } from "@lens-protocol/client/action";
const result = await updateNamespaceRules(sessionClient, {  namespace: namespace.address,  toAdd: {    required: [      {        tokenGatedRule: {          token: {            standard: TokenStandard.Erc20,            currency: evmAddress("0x5678…"),            value: bigDecimal("1.5"), // Token value in its main unit          },        },      },    ],  },});

3

Handle Result

Then, handle the result using the adapter for the library of your choice:

import { handleOperationWith } from "@lens-protocol/client/viem";
// …
const result = await updateNamespaceRules(sessionClient, {  namespace: evmAddress("0x1234…"),  // …}).andThen(handleOperationWith(walletClient));

See the Transaction Lifecycle guide for more information on how to determine the status of the transaction.

Building a Username Rule

What follows is an example of how to build a rule based on an early iteration. It's provided for illustrative purposes only. Updated documentation on the final implementation will be provided soon.

Let's illustrate the process with an example. We will build a custom Username Rule that requires Usernames to be created only if their length has an specific parity, for example, all usernames must have an even length.

To build a custom Username Rule, you must implement the following IUsernameRule interface:

interface IUsernameRule {    function configure(bytes calldata data) external;
    function processCreation(        address account,        string calldata username,        bytes calldata data    ) external returns (bool);
    function processAssigning(        address account,        string calldata username,        bytes calldata data    ) external returns (bool);}

Each function of this interface must assume to be invoked by the Username namespace contract.

A Lens dependency package with all relevant interfaces will be available soon.

1

Implement the Configure Function

First, implement the configure function. This function has the purpose of initializing any required state for the rule to work properly.

The configure function can be called multiple times by the same Username namespace in order to update the rule configuration (i.e. reconfigure it).

It receives bytes that will be decoded into the required rule configuration parameters.

In our example, we only need to decode a boolean parameter, which will indicate if the rule will enforce Usernames to have an even or an odd length. Let's define a storage mapping to store this configuration:

contract ParityLengthUsernameRule is IUsernameRule {
  mapping(address => bool) internal _mustBeEvenLength;}

Now let's code the configure function itself, decoding the boolean parameter and storing it in the mapping:

contract ParityLengthUsernameRule is IUsernameRule {
  mapping(address => bool) internal _mustBeEvenLength;
  function configure(bytes calldata data) external override {      _mustBeEvenLength[msg.sender] = abi.decode(data, (bool));  }}

The configuration is stored in the mapping using the Username namespace contract address as the key, which is the msg.sender. So the same rule can be reused by different Username contracts.

2

Implement the Process Creation function

Next, implement the processCreation function. This function is invoked by the Username namespace contract every time a username is being created, so then our custom logic can be applied to shape under which conditions this operation can succeed.

The function receives the account creating the Username, the username being created, and some data in case the rule requires additional information to work.

The function must revert in case of not meeting the requirements imposed by the rule. Otherwise, it must return a boolean that indicates if the rule is applying a restriction over this operation or not. In our example, given that a restriction is indeed being applied to the creation, we will return true.

contract ParityLengthUsernameRule is IUsernameRule {
    mapping(address => bool) internal _mustBeEvenLength;
    // . . .
    function processCreation(      address account,      string calldata username,      bytes calldata data    ) external view override returns (bool) {        // We get the length of the username being created        uint256 usernameLength = bytes(username).length;
        // We check if the length is even (otherwise it is odd)        bool isEvenLength = usernameLength % 2 == 0;
        // We require the parity of the username being created to match        // the parity required by the rule        require(isEvenLength == _mustBeEvenLength[msg.sender]);
        // We return true to signal that the rule is        // being applied when creating a Username        return true;    }}

3

Implement the Process Assigning function

Finally, implement the processAssigning function. This function is invoked by the Username namespace contract every time a username is being assigned to an account, so then our rule can define if this operation must succeed or not.

The function receives the account the Username will be assigned to, the username to assign, and some data in case the rule requires additional information to work.

The function must revert in case of not meeting the requirements imposed by the rule. Otherwise, it must return a boolean that indicates if the rule is applying a restriction over this operation or not. In our example, we do not want to apply the restriction to the assigning operation, so we must implement the function to comply with the interface but just return false.

contract ParityLengthUsernameRule is IUsernameRule {
    mapping(address => bool) internal _mustBeEvenLength;
    // . . .
    function processAssigning(      address account,      string calldata username,      bytes calldata data    ) external view override returns (bool) {        // We return false to signal that we do not        // apply the rule when assigning a Username        return false;    }}

Now the ParityLengthUsernameRule is ready to be applied into any Username. See the full code below:

contract ParityLengthUsernameRule is IUsernameRule {
    mapping(address => bool) internal _mustBeEvenLength;
    function configure(bytes calldata data) external override {        _mustBeEvenLength[msg.sender] = abi.decode(data, (bool));    }
    function processCreation(      address account,      string calldata username,      bytes calldata data    ) external view override returns (bool) {        // We get the length of the username being created        uint256 usernameLength = bytes(username).length;
        // We check if the length is even (otherwise it is odd)        bool isEvenLength = usernameLength % 2 == 0;
        // We require the parity of the username being created to match        // the parity required by the rule        require(isEvenLength == _mustBeEvenLength[msg.sender]);
        // We return true to signal that the rule is        // being applied when creating a Username        return true;    }
    function processAssigning(      address account,      string calldata username,      bytes calldata data    ) external view override returns (bool) {        // We return false to signal that we do not        // apply the rule when assigning a Username        return false;    }}

Stay tuned for API integration of rules and more guides!