#nft #solana #blockchain #metaplex


MPL Token Authorization Rules that can be used to restrict common token operations

24 releases (7 stable)

1.5.0 Feb 7, 2024
1.4.3 Aug 1, 2023
1.4.1 Jun 16, 2023
1.3.0 Mar 14, 2023
0.2.1 Dec 27, 2022

#1302 in Magic Beans

Download history 2680/week @ 2024-03-14 2294/week @ 2024-03-21 2796/week @ 2024-03-28 2933/week @ 2024-04-04 3137/week @ 2024-04-11 2848/week @ 2024-04-18 2257/week @ 2024-04-25 1894/week @ 2024-05-02 1879/week @ 2024-05-09 2040/week @ 2024-05-16 1948/week @ 2024-05-23 2089/week @ 2024-05-30 1656/week @ 2024-06-06 2118/week @ 2024-06-13 1865/week @ 2024-06-20 1086/week @ 2024-06-27

7,065 downloads per month
Used in 74 crates (9 directly)

Custom license

3.5K SLoC

Metaplex Token Authorization Rules

A program that provides the ability to create and execute rules to restrict common token operations such as transferring and selling.

Read the official documentation here.


Authorization rules are variants of a Rule enum that implements a validate() function.

There are Primitive Rules and Composed Rules that are created by combining of one or more primitive rules.

Primitive Rules store any accounts or data needed for evaluation, and at runtime will produce a true or false output based on accounts and a well-defined Payload that are passed into the validate() function.

Composed Rules return a true or false based on whether any or all of the primitive rules return true. Composed rules can then be combined into higher-level composed rules that implement more complex boolean logic. Because of the recursive definition of the Rule enum, calling validate() on a top-level composed rule will start at the top and validate at every level, down to the component primitive rules.

Getting Started

The packages below can be used to interact with Token Authorization Rules (Token Auth Rules) program.


npm install @metaplex-foundation/mpl-token-auth-rules

See typedoc documentation.


cargo add mpl-token-auth-rules

See crate documentation.


From the root directory of the repository:

  • Install the required packages:
pnpm install
  • Build the program:
pnpm programs:build

This will create the program binary at <ROOT>/programs/.bin


Token Auth Rules includes three sets of tests: BPF, TypeScript for the JS Client, and Legacy Typescript for the Solita-based JS Client.


From the root directory of the repository:

pnpm programs:test


From the root directory of the repository:

pnpm validator

This will start a local validator using Amman.

After starting the validator, go to the folder <ROOT>/clients/js and run:

pnpm install

This will install the required packages for the tests. Then, run:

pnpm build && pnpm test

Legacy Typescript

Follow all the same steps as for the TypeScript tests above, including starting the validator in the root directory, but for building and running the tests, navigate to the folder <ROOT>/clients/js-solita.


The folder cli contains a typescript CLI to manage rule set revisions:

Usage: auth [options] [command]

CLI for managing RuleSet revisions.

  -V, --version      output the version number
  -h, --help         display help for command

  create [options]   creates a new rule set revision
  convert [options]  converts a rule set revision from V1 to V2
  print [options]    prints the latest rule set revision as a JSON object
  help [command]     display help for command

To start the CLI, navigate to the folder ./cli and run

$ yarn install

Then, the CLI can be used as

$ yarn start <COMMAND>

The folder ./examples contain several examples of rule sets. You will need to replace the owner pubkey value with the pubkey used to run the CLI.

Note that you need to first build the JS SDK.



Note: Additional Rust examples can be found in the program/tests directory.

use mpl_token_auth_rules::{
        builders::{CreateOrUpdateBuilder, ValidateBuilder},
        CreateOrUpdateArgs, InstructionBuilder, ValidateArgs,
    payload::{Payload, PayloadType},
    state::{CompareOp, Rule, RuleSetV1},
use num_derive::ToPrimitive;
use rmp_serde::Serializer;
use serde::Serialize;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
    instruction::AccountMeta, native_token::LAMPORTS_PER_SOL, signature::Signer,
    signer::keypair::Keypair, transaction::Transaction,

pub enum Operation {

impl ToString for Operation {
    fn to_string(&self) -> String {
        match self {
            Operation::OwnerTransfer => "OwnerTransfer".to_string(),
            Operation::Delegate => "Delegate".to_string(),
            Operation::SaleTransfer => "SaleTransfer".to_string(),

fn main() {
    let url = "https://api.devnet.solana.com".to_string();

    let rpc_client = RpcClient::new(url);

    let payer = Keypair::new();

    let signature = rpc_client
        .request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)

    loop {
        let confirmed = rpc_client.confirm_transaction(&signature).unwrap();
        if confirmed {

    // --------------------------------
    // Create RuleSet
    // --------------------------------
    // Find RuleSet PDA.
    let (rule_set_addr, _ruleset_bump) = mpl_token_auth_rules::pda::find_rule_set_address(
        "test rule_set".to_string(),

    // Additional signer.
    let adtl_signer = Keypair::new();

    // Create some rules.
    let adtl_signer_rule = Rule::AdditionalSigner {
        account: adtl_signer.pubkey(),

    let amount_rule = Rule::Amount {
        amount: 1,
        operator: CompareOp::LtEq,
        field: "Amount".to_string(),

    let overall_rule = Rule::All {
        rules: vec![adtl_signer_rule, amount_rule],

    // Create a RuleSet.
    let mut rule_set = RuleSetV1::new("test rule_set".to_string(), payer.pubkey());
        .add(Operation::OwnerTransfer.to_string(), overall_rule)

    println!("{:#?}", rule_set);

    // Serialize the RuleSet using RMP serde.
    let mut serialized_rule_set = Vec::new();
        .serialize(&mut Serializer::new(&mut serialized_rule_set))

    // Create a `create` instruction.
    let create_ix = CreateOrUpdateBuilder::new()
        .build(CreateOrUpdateArgs::V1 {

    // Add it to a transaction.
    let latest_blockhash = rpc_client.get_latest_blockhash().unwrap();
    let create_tx = Transaction::new_signed_with_payer(

    // Send and confirm transaction.
    let signature = rpc_client.send_and_confirm_transaction(&create_tx).unwrap();
    println!("Create tx signature: {}", signature);

    // --------------------------------
    // Validate Operation
    // --------------------------------
    // Create a Keypair to simulate a token mint address.
    let mint = Keypair::new().pubkey();

    // Store the payload of data to validate against the rule definition.
    let payload = Payload::from([("Amount".to_string(), PayloadType::Number(1))]);

    // Create a `validate` instruction with the additional signer.
    let validate_ix = ValidateBuilder::new()
        .additional_rule_accounts(vec![AccountMeta::new_readonly(adtl_signer.pubkey(), true)])
        .build(ValidateArgs::V1 {
            operation: Operation::OwnerTransfer.to_string(),
            update_rule_state: false,
            rule_set_revision: None,

    // Add it to a transaction.
    let latest_blockhash = rpc_client.get_latest_blockhash().unwrap();
    let validate_tx = Transaction::new_signed_with_payer(
        &[&payer, &adtl_signer],

    // Send and confirm transaction.
    let signature = rpc_client
    println!("Validate tx signature: {}", signature);


Note: Additional JS examples can be found in the /cli/ source along with the example rulesets in /cli/examples/

import { createCreateInstruction, createTokenAuthorizationRules, PREFIX, PROGRAM_ID } from './helpers/mpl-token-auth-rules';
import { Keypair, Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js';
import {
} from '@metaplex-foundation/mpl-token-auth-rules';

export const findRuleSetPDA = async (payer: PublicKey, name: string) => {
    return await PublicKey.findProgramAddress(

export const createTokenAuthorizationRules = async (
    connection: Connection,
    payer: Keypair,
    name: string,
    data: Uint8Array,
) => {
    const ruleSetAddress = await findRuleSetPDA(payer.publicKey, name);

    let createIX = createCreateOrUpdateInstruction(
            payer: payer.publicKey,
            ruleSetPda: ruleSetAddress[0],
            systemProgram: SystemProgram.programId,
            createOrUpdateArgs: {__kind: "V1", serializedRuleSet: data },

    const tx = new Transaction().add(createIX);

    const { blockhash } = await connection.getLatestBlockhash();
    tx.recentBlockhash = blockhash;
    tx.feePayer = payer.publicKey;
    const sig = await connection.sendTransaction(tx, [payer]);
    await connection.confirmTransaction(sig, "finalized");
    return ruleSetAddress[0];

const connection = new Connection("<YOUR_RPC_HERE>", "finalized");
let payer = Keypair.generate()

const revision = getRuleSetRevisionFromJson(JSON.parse(fs.readFileSync("./examples/v2/pubkey-list-match.json")));
// Create the rule set revision
await createTokenAuthorizationRules(connection, payer, name, serializeRuleSetRevision(revision));


~491K SLoC