Skip to content

Reserved, inline, and stored address lists, the ! inversion syntax, and how Mint is handled in each.

An address list is a named set of addresses used by the fromListId, toListId, and initiatedByListId fields of approvals and permissions. A list is either a whitelist (only these addresses) or a blacklist (everyone except these).

Shape

json
{
  "listId": "vipMembers",
  "addresses": [
    "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
    "bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue",
    "bb1zc268nctj8xwslgw7q22cahs6k4y048agr6fvf"
  ],
  "whitelist": true,
  "uri": "",
  "customData": "",
  "createdBy": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d"
}
proto
message AddressList {
  string listId = 1;
  repeated string addresses = 2;
  bool whitelist = 3;
  string uri = 4;
  string customData = 5;
  string createdBy = 6;
}
FieldTypeRequiredDescription
listIdstringyesIdentifier used in approvals
addressesstring[]yesbb1 addresses. "Mint" is allowed.
whitelistboolyestrue: only addresses match. false: everyone except addresses matches.
uristringnoMetadata link
customDatastringnoFree-form string, or inline JSON metadata
createdBystringset by chainCreator of a stored list

Matching is one function:

js
function checkAddress(address, list) {
  const found = list.addresses.includes(address);
  return list.whitelist ? found : !found;
}
Address list "vipMembers"Only these
  • bb1p0rrel3...w70d
  • bb1py4mfpg...p5ue
  • bb1zc268nc...6fvf
ID: vipMembers

The list above as bitbadges.io shows it. A whitelist reads as only these; a blacklist reads as all except.

Ask your agent:

text
Add a mint approval to collection 1 that only alice and bob can initiate. Pass the two addresses as an inline list.

The MCP builder tools (add_approval) produce the objects on this page.

How It Works

There are three kinds of list ID. The chain resolves reserved and inline IDs on the fly with no storage; stored lists live in state.

Reserved IDs

IDResolves toMeaning
"Mint"{ addresses: ["Mint"], whitelist: true }Only the Mint address
"All" or "AllWithMint"{ addresses: [], whitelist: false }Every address, including Mint
"None"{ addresses: [], whitelist: true }No address
"AllWithout<a>:<b>:..."{ addresses: [a, b, ...], whitelist: false }Every address except those listed (colon-separated)
"<a>:<b>:..."{ addresses: [a, b, ...], whitelist: true }Only the addresses listed

A single bb1 address is therefore a valid list ID that matches only that address. "AllWithoutMint" is the common "everyone except Mint" list.

Inversion

Prefix an ID with ! to flip whitelist. Use !(...) when the ID itself could be ambiguous. Inversion works on reserved IDs, inline lists, and stored list IDs.

js
'!Mint';                    // everyone except Mint (same as AllWithoutMint)
'!All';                     // no one
'!bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d:bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue'; // everyone except alice and bob
'!(AllWithoutMint)';        // only Mint
'!vipMembers';              // everyone except the stored vipMembers list

The chain treats "!x" as inverted when the ID does not end with ), and "!(x)" as inverted always.

Mint Handling

"All" includes "Mint"; a blacklist includes it unless Mint is explicitly excluded. The chain rejects collection approvals that mix Mint with other senders, and requires a Mint-only approval to override outgoing approvals. Use "Mint" for mint approvals and "!Mint" or "AllWithoutMint" for everything else. See Minting and Supply.

json
[
  { "fromListId": "AllWithoutMint", "toListId": "All" },
  { "fromListId": "Mint", "toListId": "All" }
]

Stored Lists

Create a stored list with MsgCreateAddressLists. Stored lists are immutable and global: any collection can reference the same list ID. They save gas when a long list is referenced more than once.

ID rules for stored lists:

  • Alphanumeric only (a-z, A-Z, 0-9). No : or !.
  • Not empty.
  • Not a reserved ID ("All", "Mint", "None", "Manager", "AllWithoutMint").
  • Not a valid bb1 address.
  • Unique. Creating an ID that already exists fails.
  • Addresses inside must be unique and valid ("Mint" allowed).

Off-chain lists also exist in the BitBadges API. They are editable and deletable but are not chain state and cannot be used in on-chain approvals.

Examples

json
{
  "fromListId": "AllWithoutMint",
  "toListId": "All",
  "initiatedByListId": "All"
}
json
{
  "fromListId": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d:bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue:bb1zc268nctj8xwslgw7q22cahs6k4y048agr6fvf",
  "toListId": "AllWithoutMint:bb18cad7xxsk3drvwdxeasc3wqn2plftpzq2tsrsr",
  "initiatedByListId": "All"
}
json
{
  "fromListId": "vipMembers",
  "toListId": "!bb18cad7xxsk3drvwdxeasc3wqn2plftpzq2tsrsr",
  "initiatedByListId": "All"
}

Cost

KindStorageBest for
ReservednoneMint, All, None, small exclusions
Inlinenonefewer than about 10 addresses used once
Storedon-chainlarge lists, or any list referenced repeatedly

Reference: SDK Resolver

ts
function getReservedList(addressListId: string, allowAliases?: boolean): AddressList {
  let inverted = false;
  let addressList: AddressList | undefined = undefined;

  if (addressListId[0] === '!') {
    inverted = true;
    addressListId = addressListId.slice(1);
  }

  if (addressListId === 'Mint') {
    addressList = { listId: 'Mint', addresses: ['Mint'], whitelist: true, uri: '', customData: '', createdBy: '' };
  } else if (addressListId.startsWith('AllWithout')) {
    addressList = { listId: addressListId, addresses: [], whitelist: false, uri: '', customData: '', createdBy: '' };
    const addresses = addressListId.slice(10).split(':');
    for (let address of addresses) {
      addressList.addresses.push(address);
    }
  } else if (addressListId === 'AllWithMint' || addressListId === 'All') {
    addressList = { listId: addressListId, addresses: [], whitelist: false, uri: '', customData: '', createdBy: '' };
  } else if (addressListId === 'None') {
    addressList = { listId: 'None', addresses: [], whitelist: true, uri: '', customData: '', createdBy: '' };
  } else {
    const addressesToCheck = addressListId.split(':');
    let allAreValid = true;
    // For tracker IDs, aliases (non-address strings) are allowed
    if (!allowAliases) {
      for (let address of addressesToCheck) {
        if (address != 'Mint' && !convertToBitBadgesAddress(address)) {
          allAreValid = false;
        }
      }
    }
    if (allAreValid) {
      addressList = { listId: addressListId, addresses: addressesToCheck, whitelist: true, uri: '', customData: '', createdBy: '' };
    }
  }

  if (inverted && addressList) {
    addressList.whitelist = !addressList.whitelist;
  }
  if (!addressList) {
    throw new Error(`Invalid address list ID: ${addressListId}`);
  }
  return addressList;
}

Edit this page on GitHub