A program that provides the ability to create and execute rules to restrict common token operations such as transferring and selling.
⚠️ Metaplex Token Authorization Rules is currently experimental and has not been formally audited. Use in production at your own risk.
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.
Note: Additional Rust examples can be found in the program/tests directory. ```rust use mpltokenauthrules::{ payload::{Payload, PayloadKey, PayloadType}, state::{Operation, Rule, RuleSet}, }; use rmpserde::Serializer; use serde::Serialize; use solanaclient::rpcclient::RpcClient; use solanasdk::{ nativetoken::LAMPORTSPERSOL, signature::Signer, signer::keypair::Keypair, transaction::Transaction, };
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)
.unwrap();
loop {
let confirmed = rpc_client.confirm_transaction(&signature).unwrap();
if confirmed {
break;
}
}
// Find RuleSet PDA.
let (ruleset_addr, _ruleset_bump) = mpl_token_auth_rules::pda::find_rule_set_address(
payer.pubkey(),
"test ruleset".to_string(),
);
// Second signer.
let second_signer = Keypair::new();
// Create some rules.
let adtl_signer = Rule::AdditionalSigner {
account: payer.pubkey(),
};
let adtl_signer2 = Rule::AdditionalSigner {
account: second_signer.pubkey(),
};
let amount_check = Rule::Amount { amount: 2 };
let first_rule = Rule::All {
rules: vec![adtl_signer, adtl_signer2],
};
let overall_rule = Rule::All {
rules: vec![first_rule, amount_check],
};
// Create a RuleSet.
let mut rule_set = RuleSet::new();
rule_set.add(Operation::Transfer, overall_rule);
println!("{:#?}", rule_set);
// Serialize the RuleSet using RMP serde.
let mut serialized_data = Vec::new();
rule_set
.serialize(&mut Serializer::new(&mut serialized_data))
.unwrap();
// Create a `create` instruction.
let create_ix = mpl_token_auth_rules::instruction::create(
mpl_token_auth_rules::id(),
payer.pubkey(),
ruleset_addr,
"test ruleset".to_string(),
serialized_data,
);
// Add it to a transaction.
let latest_blockhash = rpc_client.get_latest_blockhash().unwrap();
let create_tx = Transaction::new_signed_with_payer(
&[create_ix],
Some(&payer.pubkey()),
&[&payer],
latest_blockhash,
);
// Send and confirm transaction.
let signature = rpc_client.send_and_confirm_transaction(&create_tx).unwrap();
println!("Create tx signature: {}", signature);
// Store the payload of data to validate against the rule definition.
let payload = Payload::from([(PayloadKey::Amount, PayloadType::Number(2))]);
// Create a `validate` instruction.
let validate_ix = mpl_token_auth_rules::instruction::validate(
mpl_token_auth_rules::id(),
payer.pubkey(),
ruleset_addr,
"test ruleset".to_string(),
Operation::Transfer,
payload,
vec![second_signer.pubkey()],
vec![],
);
// Add it to a transaction.
let latest_blockhash = rpc_client.get_latest_blockhash().unwrap();
let validate_tx = Transaction::new_signed_with_payer(
&[validate_ix],
Some(&payer.pubkey()),
&[&payer, &second_signer],
latest_blockhash,
);
// Send and confirm transaction.
let signature = rpc_client
.send_and_confirm_transaction(&validate_tx)
.unwrap();
println!("Validate tx signature: {}", signature);
} ```
Note: Additional JS examples can be found in the /cli/ source along with the example rulesets in /cli/examples/ ```js import { encode, decode } from '@msgpack/msgpack'; import { createCreateInstruction, createTokenAuthorizationRules, PREFIX, PROGRAM_ID } from './helpers/mpl-token-auth-rules'; import { Keypair, Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js';
const RULESET = [ { "Transfer": { "All": [ [ { "All": [ [ { "AdditionalSigner": [ [42, 157, 245, 156, 21, 37, 147, 96, 183, 190, 206, 14, 24, 1, 106, 49, 167, 236, 38, 73, 98, 53, 60, 9, 154, 164, 240, 126, 210, 197, 76, 235] ] }, { "AdditionalSigner": [ [42, 157, 245, 156, 21, 37, 147, 96, 183, 190, 206, 14, 24, 1, 106, 49, 167, 236, 38, 73, 98, 53, 60, 9, 154, 164, 240, 126, 210, 197, 76, 235] ] } ] ] }, { "Amount": [1] } ] ] } } ]
export const findRuleSetPDA = async (payer: PublicKey, name: string) => { return await PublicKey.findProgramAddress( [ Buffer.from(PREFIX), payer.toBuffer(), Buffer.from(name), ], PROGRAM_ID, ); }
export const createTokenAuthorizationRules = async ( connection: Connection, payer: Keypair, name: string, data: Uint8Array, ) => { const ruleSetAddress = await findRuleSetPDA(payer.publicKey, name);
let createIX = createCreateInstruction(
{
payer: payer.publicKey,
rulesetPda: ruleSetAddress[0],
systemProgram: SystemProgram.programId,
},
{
createArgs: { name, serializedRuleSet: data },
},
PROGRAM_ID,
)
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], { skipPreflight: true });
await connection.confirmTransaction(sig, "finalized");
return ruleSetAddress[0];
}
const connection = new Connection("
// Encode the file using msgpack so the pre-encoded data can be written directly to a Solana program account const encoded = encode(RULESET); // Create the ruleset await createTokenAuthorizationRules(connection, payer, name, encoded);
```
yarn install
to install dependencies
$ cd program/
$ cargo build-bpf
$ cargo test-bpf
$ cd ..
$ yarn build:rust
$ yarn solita
$ yarn build:sdk
$ yarn build
Run the following command in a separate shell
$ amman start
Then, run the Amman script
$ yarn amman