Browse Source

feat: Add authority approval/removal for world instances (#82)

Gabriele Picco 1 year ago
parent
commit
529927b789

+ 1 - 1
.github/workflows/publish-bolt-crates.yml

@@ -70,7 +70,7 @@ jobs:
       - name: Cache rust
         uses: Swatinem/rust-cache@v2
       - name: Run fmt
-        run: cargo fmt -- --check
+        run: cargo fmt -- --check --verbose
       - name: Run clippy
         run: cargo clippy -- --deny=warnings
 

+ 1 - 1
.github/workflows/publish-bolt-sdk.yml

@@ -70,7 +70,7 @@ jobs:
       - name: Cache rust
         uses: Swatinem/rust-cache@v2
       - name: Run fmt
-        run: cargo fmt -- --check
+        run: cargo fmt -- --check --verbose
       - name: Run clippy
         run: cargo clippy -- --deny=warnings
 

+ 3 - 3
.github/workflows/run-tests.yml

@@ -26,7 +26,7 @@ jobs:
 
       - uses: actions/setup-node@v4
         with:
-          node-version: 20
+          node-version: 21
 
       - name: install essentials
         run: |
@@ -71,8 +71,8 @@ jobs:
         uses: Swatinem/rust-cache@v2
       - name: Check Rust version
         run: rustc --version
-#      - name: Run fmt
-#        run: cargo fmt -- --check --verbose
+      - name: Run fmt
+        run: cargo fmt -- --check --verbose
       - name: Run clippy
         run: cargo clippy -- --deny=warnings
 

+ 13 - 17
Anchor.toml

@@ -1,7 +1,7 @@
 [toolchain]
 
 [features]
-seeds = true
+resolution = true
 skip-lint = false
 
 [programs.devnet]
@@ -20,31 +20,30 @@ velocity = "CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1"
 url = "https://api.apr.dev"
 
 [provider]
-cluster = "localnet"
+cluster = "Localnet"
 wallet = "./tests/fixtures/provider.json"
 
+[workspace]
+members = ["programs/bolt-component", "programs/bolt-system", "programs/world", "examples/component-position", "examples/component-velocity", "examples/system-apply-velocity", "examples/system-fly", "examples/system-simple-movement"]
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.ts"
+
 [test]
 startup_wait = 5000
 shutdown_wait = 2000
 upgradeable = false
 
-[workspace]
-members = [
-    "programs/bolt-component",
-    "programs/bolt-system",
-    "programs/world",
-    "examples/component-position",
-    "examples/component-velocity",
-    "examples/system-apply-velocity",
-    "examples/system-fly",
-    "examples/system-simple-movement",
-]
-
 [[test.genesis]]
 address = "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"
 program = "tests/fixtures/delegation.so"
 upgradeable = false
 
+[test.validator]
+bind_address = "0.0.0.0"
+ledger = ".anchor/test-ledger"
+rpc_port = 8899
+
 [[test.validator.account]]
 address = "EEmsg7GbxEAw5f9hGfZRmJRJ27HK8KeGDp7ViW9X2mYa"
 filename = "tests/fixtures/commit_record.json"
@@ -52,6 +51,3 @@ filename = "tests/fixtures/commit_record.json"
 [[test.validator.account]]
 address = "7nQvHcfEqtFmY2q6hiQbidu8BCNdqegnEFfH7HkByFn5"
 filename = "tests/fixtures/committed_state.json"
-
-[scripts]
-test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.ts"

+ 1 - 0
Cargo.lock

@@ -1018,6 +1018,7 @@ dependencies = [
  "heck 0.5.0",
  "serde_json",
  "syn 1.0.109",
+ "world 0.1.9",
 ]
 
 [[package]]

+ 2 - 1
cli/Cargo.toml

@@ -29,4 +29,5 @@ anyhow = { workspace = true }
 serde_json = { workspace = true }
 heck = { workspace = true }
 clap = { workspace = true }
-syn = { workspace = true, features = ["full", "extra-traits"] }
+syn = { workspace = true, features = ["full", "extra-traits"] }
+world = { workspace = true }

+ 167 - 0
cli/src/instructions.rs

@@ -0,0 +1,167 @@
+use anchor_cli::config::{Config, ConfigOverride};
+use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig;
+use anchor_client::solana_sdk::commitment_config::CommitmentConfig;
+use anchor_client::solana_sdk::pubkey::Pubkey;
+use anchor_client::solana_sdk::signature::{read_keypair_file, Keypair};
+use anchor_client::solana_sdk::signer::Signer;
+use anchor_client::solana_sdk::system_program;
+use anchor_client::Client;
+use anyhow::{anyhow, Result};
+use std::rc::Rc;
+use world::{accounts, instruction, World, ID};
+
+fn setup_client(cfg_override: &ConfigOverride) -> Result<(Client<Rc<Keypair>>, Keypair)> {
+    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
+    let wallet_path = cfg.provider.wallet.clone();
+
+    let payer = read_keypair_file(wallet_path.to_string())
+        .map_err(|e| anyhow!("Failed to read keypair file: {}", e))?;
+
+    let payer_for_client =
+        Keypair::from_bytes(&payer.to_bytes()).expect("Failed to create Keypair from bytes");
+
+    let client = Client::new_with_options(
+        cfg.provider.cluster.clone(),
+        Rc::new(payer_for_client),
+        CommitmentConfig::confirmed(),
+    );
+    Ok((client, payer))
+}
+
+fn parse_pubkey(input: &str, error_message: &str) -> Result<Pubkey> {
+    input
+        .parse::<Pubkey>()
+        .map_err(|_| anyhow!(error_message.to_string()))
+}
+
+pub fn authorize(
+    cfg_override: &ConfigOverride,
+    world: String,
+    new_authority: String,
+) -> Result<()> {
+    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
+    let new_authority_pubkey = parse_pubkey(&new_authority, "Invalid new authority public key")?;
+
+    let (client, payer) = setup_client(cfg_override)?;
+    let program = client.program(ID)?;
+
+    let world_account = program.account::<World>(world_pubkey)?;
+    let world_id = world_account.id;
+    let signature = program
+        .request()
+        .accounts(accounts::AddAuthority {
+            authority: payer.pubkey(),
+            new_authority: new_authority_pubkey,
+            system_program: system_program::ID,
+            world: world_pubkey,
+        })
+        .args(instruction::AddAuthority { world_id })
+        .signer(&payer)
+        .send()?;
+
+    println!(
+        "Authority {} added to world {} with signature {}",
+        new_authority, world, signature
+    );
+
+    Ok(())
+}
+
+pub fn deauthorize(
+    cfg_override: &ConfigOverride,
+    world: String,
+    authority_to_delete: String,
+) -> Result<()> {
+    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
+    let authority_to_delete_pubkey =
+        parse_pubkey(&authority_to_delete, "Invalid authority public key")?;
+
+    let (client, payer) = setup_client(cfg_override)?;
+    let program = client.program(ID)?;
+
+    let world_account = program.account::<World>(world_pubkey)?;
+    let world_id = world_account.id;
+    let signature = program
+        .request()
+        .accounts(accounts::RemoveAuthority {
+            authority: payer.pubkey(),
+            authority_to_delete: authority_to_delete_pubkey,
+            system_program: system_program::ID,
+            world: world_pubkey,
+        })
+        .args(instruction::RemoveAuthority { world_id })
+        .signer(&payer)
+        .send_with_spinner_and_config(RpcSendTransactionConfig {
+            skip_preflight: true,
+            ..RpcSendTransactionConfig::default()
+        })?;
+
+    println!(
+        "Authority {} removed from world {} with signature {}",
+        authority_to_delete, world, signature
+    );
+
+    Ok(())
+}
+
+pub fn approve_system(
+    cfg_override: &ConfigOverride,
+    world: String,
+    system_to_approve: String,
+) -> Result<()> {
+    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
+    let system_to_approve_pubkey = parse_pubkey(&system_to_approve, "Invalid system public key")?;
+
+    let (client, payer) = setup_client(cfg_override)?;
+    let program = client.program(ID)?;
+
+    let signature = program
+        .request()
+        .accounts(accounts::ApproveSystem {
+            authority: payer.pubkey(),
+            system: system_to_approve_pubkey,
+            world: world_pubkey,
+            system_program: system_program::ID,
+        })
+        .args(instruction::ApproveSystem {})
+        .signer(&payer)
+        .send()?;
+
+    println!(
+        "System {} approved for world {} with signature {}",
+        system_to_approve, world, signature
+    );
+
+    Ok(())
+}
+
+pub fn remove_system(
+    cfg_override: &ConfigOverride,
+    world: String,
+    system_to_remove: String,
+) -> Result<()> {
+    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
+    let system_to_remove_pubkey = parse_pubkey(&system_to_remove, "Invalid system public key")?;
+
+    let (client, payer) = setup_client(cfg_override)?;
+    let program = client.program(ID)?;
+
+    let signature = program
+        .request()
+        .accounts(accounts::RemoveSystem {
+            authority: payer.pubkey(),
+            system: system_to_remove_pubkey,
+            world: world_pubkey,
+            system_program: system_program::ID,
+        })
+        .args(instruction::RemoveSystem {})
+        .signer(&payer)
+        .send()?;
+
+    println!(
+        "System {} removed from world {} with signature {}",
+        system_to_remove, world, signature
+    );
+
+    Ok(())
+}

+ 51 - 8
cli/src/lib.rs

@@ -1,10 +1,12 @@
 mod component;
+mod instructions;
 mod rust_template;
 mod system;
 mod templates;
 mod workspace;
 
 use crate::component::new_component;
+use crate::instructions::{approve_system, authorize, deauthorize, remove_system};
 use crate::rust_template::{create_component, create_system};
 use crate::system::new_system;
 use anchor_cli::config;
@@ -23,12 +25,9 @@ use std::io::Write;
 use std::io::{self, BufRead};
 use std::path::{Path, PathBuf};
 use std::process::Stdio;
-
+use std::string::ToString;
 pub const VERSION: &str = env!("CARGO_PKG_VERSION");
 pub const ANCHOR_VERSION: &str = anchor_cli::VERSION;
-
-pub const WORLD_PROGRAM: &str = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n";
-
 #[derive(Subcommand)]
 pub enum BoltCommand {
     #[clap(about = "Create a new component")]
@@ -38,6 +37,14 @@ pub enum BoltCommand {
     // Include all existing commands from anchor_cli::Command
     #[clap(flatten)]
     Anchor(anchor_cli::Command),
+    #[clap(about = "Add a new authority for a world instance")]
+    Authorize(AuthorizeCommand),
+    #[clap(about = "Remove an authority from a world instance")]
+    Deauthorize(DeauthorizeCommand),
+    #[clap(about = "Approve a system for a world instance")]
+    ApproveSystem(ApproveSystemCommand),
+    #[clap(about = "Remove a system from a world instance")]
+    RemoveSystem(RemoveSystemCommand),
 }
 
 #[derive(Debug, Parser)]
@@ -56,6 +63,30 @@ pub struct SystemCommand {
     pub name: String,
 }
 
+#[derive(Debug, Parser)]
+pub struct AuthorizeCommand {
+    pub world: String,
+    pub new_authority: String,
+}
+
+#[derive(Debug, Parser)]
+pub struct DeauthorizeCommand {
+    pub world: String,
+    pub authority_to_remove: String,
+}
+
+#[derive(Debug, Parser)]
+pub struct ApproveSystemCommand {
+    pub world: String,
+    pub system_to_approve: String,
+}
+
+#[derive(Debug, Parser)]
+pub struct RemoveSystemCommand {
+    pub world: String,
+    pub system_to_remove: String,
+}
+
 #[derive(Parser)]
 #[clap(version = VERSION)]
 pub struct Opts {
@@ -134,11 +165,23 @@ pub fn entry(opts: Opts) -> Result<()> {
         },
         BoltCommand::Component(command) => new_component(&opts.cfg_override, command.name),
         BoltCommand::System(command) => new_system(&opts.cfg_override, command.name),
+        BoltCommand::Authorize(command) => {
+            authorize(&opts.cfg_override, command.world, command.new_authority)
+        }
+        BoltCommand::Deauthorize(command) => deauthorize(
+            &opts.cfg_override,
+            command.world,
+            command.authority_to_remove,
+        ),
+        BoltCommand::ApproveSystem(command) => {
+            approve_system(&opts.cfg_override, command.world, command.system_to_approve)
+        }
+        BoltCommand::RemoveSystem(command) => {
+            remove_system(&opts.cfg_override, command.world, command.system_to_remove)
+        }
     }
 }
-
 // Bolt Init
-
 #[allow(clippy::too_many_arguments)]
 fn init(
     cfg_override: &ConfigOverride,
@@ -268,7 +311,7 @@ fn init(
         shutdown_wait: 2000,
         validator: Some(validator),
         genesis: Some(vec![GenesisEntry {
-            address: WORLD_PROGRAM.to_owned(),
+            address: world::id().to_string(),
             program: "tests/fixtures/world.so".to_owned(),
             upgradeable: Some(false),
         }]),
@@ -345,7 +388,7 @@ fn init(
         .arg("dump")
         .arg("-u")
         .arg("d")
-        .arg(WORLD_PROGRAM)
+        .arg(world::id().to_string())
         .arg("tests/fixtures/world.so")
         .stdout(Stdio::inherit())
         .stderr(Stdio::inherit())

+ 1 - 1
clients/bolt-sdk/package.json

@@ -37,7 +37,7 @@
   "main": "lib/index.js",
   "scripts": {
     "clean": "rimraf lib",
-    "build": "npm run clean && tsc && npm run lint:fix",
+    "build": "cp ../../target/types/world.ts src/generated/types/world.ts && cp ../../target/idl/world.json src/generated/idl/world.json && npm run clean && tsc && npm run lint:fix",
     "build:docs": "typedoc && typedoc --plugin typedoc-plugin-markdown --out docs-mk",
     "dev": "tsc --watch",
     "start": "tsc",

+ 698 - 0
clients/bolt-sdk/src/generated/idl/world.json

@@ -0,0 +1,698 @@
+{
+  "address": "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n",
+  "metadata": {
+    "name": "world",
+    "version": "0.1.9",
+    "spec": "0.1.0",
+    "description": "Bolt World program",
+    "repository": "https://github.com/magicblock-labs/bolt"
+  },
+  "instructions": [
+    {
+      "name": "add_authority",
+      "discriminator": [
+        229,
+        9,
+        106,
+        73,
+        91,
+        213,
+        109,
+        183
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "new_authority"
+        },
+        {
+          "name": "world",
+          "writable": true
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "world_id",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "add_entity",
+      "discriminator": [
+        163,
+        241,
+        57,
+        35,
+        244,
+        244,
+        48,
+        57
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "entity",
+          "writable": true
+        },
+        {
+          "name": "world",
+          "writable": true
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "extra_seed",
+          "type": {
+            "option": "string"
+          }
+        }
+      ]
+    },
+    {
+      "name": "apply",
+      "discriminator": [
+        248,
+        243,
+        145,
+        24,
+        105,
+        50,
+        162,
+        225
+      ],
+      "accounts": [
+        {
+          "name": "component_program"
+        },
+        {
+          "name": "bolt_system"
+        },
+        {
+          "name": "bolt_component",
+          "writable": true
+        },
+        {
+          "name": "authority",
+          "signer": true
+        },
+        {
+          "name": "instruction_sysvar_account",
+          "address": "Sysvar1nstructions1111111111111111111111111"
+        },
+        {
+          "name": "world"
+        }
+      ],
+      "args": [
+        {
+          "name": "args",
+          "type": "bytes"
+        }
+      ]
+    },
+    {
+      "name": "apply2",
+      "discriminator": [
+        120,
+        32,
+        116,
+        154,
+        158,
+        159,
+        208,
+        73
+      ],
+      "accounts": [
+        {
+          "name": "bolt_system"
+        },
+        {
+          "name": "component_program_1"
+        },
+        {
+          "name": "bolt_component_1",
+          "writable": true
+        },
+        {
+          "name": "component_program_2"
+        },
+        {
+          "name": "bolt_component_2",
+          "writable": true
+        },
+        {
+          "name": "authority",
+          "signer": true
+        },
+        {
+          "name": "instruction_sysvar_account",
+          "address": "Sysvar1nstructions1111111111111111111111111"
+        },
+        {
+          "name": "world"
+        }
+      ],
+      "args": [
+        {
+          "name": "args",
+          "type": "bytes"
+        }
+      ]
+    },
+    {
+      "name": "apply3",
+      "discriminator": [
+        254,
+        146,
+        49,
+        7,
+        236,
+        131,
+        105,
+        221
+      ],
+      "accounts": [
+        {
+          "name": "bolt_system"
+        },
+        {
+          "name": "component_program_1"
+        },
+        {
+          "name": "bolt_component_1",
+          "writable": true
+        },
+        {
+          "name": "component_program_2"
+        },
+        {
+          "name": "bolt_component_2",
+          "writable": true
+        },
+        {
+          "name": "component_program_3"
+        },
+        {
+          "name": "bolt_component_3",
+          "writable": true
+        },
+        {
+          "name": "authority",
+          "signer": true
+        },
+        {
+          "name": "instruction_sysvar_account",
+          "address": "Sysvar1nstructions1111111111111111111111111"
+        },
+        {
+          "name": "world"
+        }
+      ],
+      "args": [
+        {
+          "name": "args",
+          "type": "bytes"
+        }
+      ]
+    },
+    {
+      "name": "apply4",
+      "discriminator": [
+        223,
+        104,
+        24,
+        79,
+        252,
+        196,
+        14,
+        109
+      ],
+      "accounts": [
+        {
+          "name": "bolt_system"
+        },
+        {
+          "name": "component_program_1"
+        },
+        {
+          "name": "bolt_component_1",
+          "writable": true
+        },
+        {
+          "name": "component_program_2"
+        },
+        {
+          "name": "bolt_component_2",
+          "writable": true
+        },
+        {
+          "name": "component_program_3"
+        },
+        {
+          "name": "bolt_component_3",
+          "writable": true
+        },
+        {
+          "name": "component_program_4"
+        },
+        {
+          "name": "bolt_component_4",
+          "writable": true
+        },
+        {
+          "name": "authority",
+          "signer": true
+        },
+        {
+          "name": "instruction_sysvar_account",
+          "address": "Sysvar1nstructions1111111111111111111111111"
+        },
+        {
+          "name": "world"
+        }
+      ],
+      "args": [
+        {
+          "name": "args",
+          "type": "bytes"
+        }
+      ]
+    },
+    {
+      "name": "apply5",
+      "discriminator": [
+        70,
+        164,
+        214,
+        28,
+        136,
+        116,
+        84,
+        153
+      ],
+      "accounts": [
+        {
+          "name": "bolt_system"
+        },
+        {
+          "name": "component_program_1"
+        },
+        {
+          "name": "bolt_component_1",
+          "writable": true
+        },
+        {
+          "name": "component_program_2"
+        },
+        {
+          "name": "bolt_component_2",
+          "writable": true
+        },
+        {
+          "name": "component_program_3"
+        },
+        {
+          "name": "bolt_component_3",
+          "writable": true
+        },
+        {
+          "name": "component_program_4"
+        },
+        {
+          "name": "bolt_component_4",
+          "writable": true
+        },
+        {
+          "name": "component_program_5"
+        },
+        {
+          "name": "bolt_component_5",
+          "writable": true
+        },
+        {
+          "name": "authority",
+          "signer": true
+        },
+        {
+          "name": "instruction_sysvar_account",
+          "address": "Sysvar1nstructions1111111111111111111111111"
+        },
+        {
+          "name": "world"
+        }
+      ],
+      "args": [
+        {
+          "name": "args",
+          "type": "bytes"
+        }
+      ]
+    },
+    {
+      "name": "approve_system",
+      "discriminator": [
+        114,
+        165,
+        105,
+        68,
+        52,
+        67,
+        207,
+        121
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "world",
+          "writable": true
+        },
+        {
+          "name": "system"
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "initialize_component",
+      "discriminator": [
+        36,
+        143,
+        233,
+        113,
+        12,
+        234,
+        61,
+        30
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "data",
+          "writable": true
+        },
+        {
+          "name": "entity"
+        },
+        {
+          "name": "component_program"
+        },
+        {
+          "name": "authority"
+        },
+        {
+          "name": "instruction_sysvar_account",
+          "address": "Sysvar1nstructions1111111111111111111111111"
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "initialize_new_world",
+      "discriminator": [
+        23,
+        96,
+        88,
+        194,
+        200,
+        203,
+        200,
+        98
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "world",
+          "writable": true
+        },
+        {
+          "name": "registry",
+          "writable": true
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "initialize_registry",
+      "discriminator": [
+        189,
+        181,
+        20,
+        17,
+        174,
+        57,
+        249,
+        59
+      ],
+      "accounts": [
+        {
+          "name": "registry",
+          "writable": true
+        },
+        {
+          "name": "payer",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "remove_authority",
+      "discriminator": [
+        242,
+        104,
+        208,
+        132,
+        190,
+        250,
+        74,
+        216
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "authority_to_delete"
+        },
+        {
+          "name": "world",
+          "writable": true
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "world_id",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "remove_system",
+      "discriminator": [
+        218,
+        80,
+        71,
+        80,
+        161,
+        130,
+        149,
+        120
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "world",
+          "writable": true
+        },
+        {
+          "name": "system"
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    }
+  ],
+  "accounts": [
+    {
+      "name": "Entity",
+      "discriminator": [
+        46,
+        157,
+        161,
+        161,
+        254,
+        46,
+        79,
+        24
+      ]
+    },
+    {
+      "name": "Registry",
+      "discriminator": [
+        47,
+        174,
+        110,
+        246,
+        184,
+        182,
+        252,
+        218
+      ]
+    },
+    {
+      "name": "World",
+      "discriminator": [
+        145,
+        45,
+        170,
+        174,
+        122,
+        32,
+        155,
+        124
+      ]
+    }
+  ],
+  "errors": [
+    {
+      "code": 6000,
+      "name": "InvalidAuthority",
+      "msg": "Invalid authority for instruction"
+    },
+    {
+      "code": 6001,
+      "name": "WorldAccountMismatch",
+      "msg": "The provided world account does not match the expected PDA."
+    },
+    {
+      "code": 6002,
+      "name": "TooManyAuthorities",
+      "msg": "Exceed the maximum number of authorities."
+    },
+    {
+      "code": 6003,
+      "name": "AuthorityNotFound",
+      "msg": "The provided authority not found"
+    },
+    {
+      "code": 6004,
+      "name": "SystemNotApproved",
+      "msg": "The system is not approved in this world instance"
+    }
+  ],
+  "types": [
+    {
+      "name": "Entity",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "id",
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "Registry",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "worlds",
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "World",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "id",
+            "type": "u64"
+          },
+          {
+            "name": "entities",
+            "type": "u64"
+          },
+          {
+            "name": "authorities",
+            "type": {
+              "vec": "pubkey"
+            }
+          },
+          {
+            "name": "permissionless",
+            "type": "bool"
+          },
+          {
+            "name": "systems",
+            "type": "bytes"
+          }
+        ]
+      }
+    }
+  ]
+}

+ 5 - 0
clients/bolt-sdk/src/generated/index.ts

@@ -6,6 +6,8 @@
  */
 
 import { PublicKey } from "@solana/web3.js";
+import { type World as WorldProgram } from "./types/world";
+import idl from "./idl/world.json";
 export * from "./accounts";
 export * from "./errors";
 export * from "./instructions";
@@ -25,3 +27,6 @@ export const PROGRAM_ADDRESS = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n";
  * @category generated
  */
 export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS);
+
+export default WorldProgram;
+export { idl as worldIdl };

+ 560 - 0
clients/bolt-sdk/src/generated/types/world.ts

@@ -0,0 +1,560 @@
+/**
+ * Program IDL in camelCase format in order to be used in JS/TS.
+ *
+ * Note that this is only a type helper and is not the actual IDL. The original
+ * IDL can be found at `target/idl/world.json`.
+ */
+export interface World {
+  address: "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n";
+  metadata: {
+    name: "world";
+    version: "0.1.9";
+    spec: "0.1.0";
+    description: "Bolt World program";
+    repository: "https://github.com/magicblock-labs/bolt";
+  };
+  instructions: [
+    {
+      name: "addAuthority";
+      discriminator: [229, 9, 106, 73, 91, 213, 109, 183];
+      accounts: [
+        {
+          name: "authority";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "newAuthority";
+        },
+        {
+          name: "world";
+          writable: true;
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [
+        {
+          name: "worldId";
+          type: "u64";
+        }
+      ];
+    },
+    {
+      name: "addEntity";
+      discriminator: [163, 241, 57, 35, 244, 244, 48, 57];
+      accounts: [
+        {
+          name: "payer";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "entity";
+          writable: true;
+        },
+        {
+          name: "world";
+          writable: true;
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [
+        {
+          name: "extraSeed";
+          type: {
+            option: "string";
+          };
+        }
+      ];
+    },
+    {
+      name: "apply";
+      discriminator: [248, 243, 145, 24, 105, 50, 162, 225];
+      accounts: [
+        {
+          name: "componentProgram";
+        },
+        {
+          name: "boltSystem";
+        },
+        {
+          name: "boltComponent";
+          writable: true;
+        },
+        {
+          name: "authority";
+          signer: true;
+        },
+        {
+          name: "instructionSysvarAccount";
+          address: "Sysvar1nstructions1111111111111111111111111";
+        },
+        {
+          name: "world";
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: "bytes";
+        }
+      ];
+    },
+    {
+      name: "apply2";
+      discriminator: [120, 32, 116, 154, 158, 159, 208, 73];
+      accounts: [
+        {
+          name: "boltSystem";
+        },
+        {
+          name: "componentProgram1";
+        },
+        {
+          name: "boltComponent1";
+          writable: true;
+        },
+        {
+          name: "componentProgram2";
+        },
+        {
+          name: "boltComponent2";
+          writable: true;
+        },
+        {
+          name: "authority";
+          signer: true;
+        },
+        {
+          name: "instructionSysvarAccount";
+          address: "Sysvar1nstructions1111111111111111111111111";
+        },
+        {
+          name: "world";
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: "bytes";
+        }
+      ];
+    },
+    {
+      name: "apply3";
+      discriminator: [254, 146, 49, 7, 236, 131, 105, 221];
+      accounts: [
+        {
+          name: "boltSystem";
+        },
+        {
+          name: "componentProgram1";
+        },
+        {
+          name: "boltComponent1";
+          writable: true;
+        },
+        {
+          name: "componentProgram2";
+        },
+        {
+          name: "boltComponent2";
+          writable: true;
+        },
+        {
+          name: "componentProgram3";
+        },
+        {
+          name: "boltComponent3";
+          writable: true;
+        },
+        {
+          name: "authority";
+          signer: true;
+        },
+        {
+          name: "instructionSysvarAccount";
+          address: "Sysvar1nstructions1111111111111111111111111";
+        },
+        {
+          name: "world";
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: "bytes";
+        }
+      ];
+    },
+    {
+      name: "apply4";
+      discriminator: [223, 104, 24, 79, 252, 196, 14, 109];
+      accounts: [
+        {
+          name: "boltSystem";
+        },
+        {
+          name: "componentProgram1";
+        },
+        {
+          name: "boltComponent1";
+          writable: true;
+        },
+        {
+          name: "componentProgram2";
+        },
+        {
+          name: "boltComponent2";
+          writable: true;
+        },
+        {
+          name: "componentProgram3";
+        },
+        {
+          name: "boltComponent3";
+          writable: true;
+        },
+        {
+          name: "componentProgram4";
+        },
+        {
+          name: "boltComponent4";
+          writable: true;
+        },
+        {
+          name: "authority";
+          signer: true;
+        },
+        {
+          name: "instructionSysvarAccount";
+          address: "Sysvar1nstructions1111111111111111111111111";
+        },
+        {
+          name: "world";
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: "bytes";
+        }
+      ];
+    },
+    {
+      name: "apply5";
+      discriminator: [70, 164, 214, 28, 136, 116, 84, 153];
+      accounts: [
+        {
+          name: "boltSystem";
+        },
+        {
+          name: "componentProgram1";
+        },
+        {
+          name: "boltComponent1";
+          writable: true;
+        },
+        {
+          name: "componentProgram2";
+        },
+        {
+          name: "boltComponent2";
+          writable: true;
+        },
+        {
+          name: "componentProgram3";
+        },
+        {
+          name: "boltComponent3";
+          writable: true;
+        },
+        {
+          name: "componentProgram4";
+        },
+        {
+          name: "boltComponent4";
+          writable: true;
+        },
+        {
+          name: "componentProgram5";
+        },
+        {
+          name: "boltComponent5";
+          writable: true;
+        },
+        {
+          name: "authority";
+          signer: true;
+        },
+        {
+          name: "instructionSysvarAccount";
+          address: "Sysvar1nstructions1111111111111111111111111";
+        },
+        {
+          name: "world";
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: "bytes";
+        }
+      ];
+    },
+    {
+      name: "approveSystem";
+      discriminator: [114, 165, 105, 68, 52, 67, 207, 121];
+      accounts: [
+        {
+          name: "authority";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "world";
+          writable: true;
+        },
+        {
+          name: "system";
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "initializeComponent";
+      discriminator: [36, 143, 233, 113, 12, 234, 61, 30];
+      accounts: [
+        {
+          name: "payer";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "data";
+          writable: true;
+        },
+        {
+          name: "entity";
+        },
+        {
+          name: "componentProgram";
+        },
+        {
+          name: "authority";
+        },
+        {
+          name: "instructionSysvarAccount";
+          address: "Sysvar1nstructions1111111111111111111111111";
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "initializeNewWorld";
+      discriminator: [23, 96, 88, 194, 200, 203, 200, 98];
+      accounts: [
+        {
+          name: "payer";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "world";
+          writable: true;
+        },
+        {
+          name: "registry";
+          writable: true;
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "initializeRegistry";
+      discriminator: [189, 181, 20, 17, 174, 57, 249, 59];
+      accounts: [
+        {
+          name: "registry";
+          writable: true;
+        },
+        {
+          name: "payer";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "removeAuthority";
+      discriminator: [242, 104, 208, 132, 190, 250, 74, 216];
+      accounts: [
+        {
+          name: "authority";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "authorityToDelete";
+        },
+        {
+          name: "world";
+          writable: true;
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [
+        {
+          name: "worldId";
+          type: "u64";
+        }
+      ];
+    },
+    {
+      name: "removeSystem";
+      discriminator: [218, 80, 71, 80, 161, 130, 149, 120];
+      accounts: [
+        {
+          name: "authority";
+          writable: true;
+          signer: true;
+        },
+        {
+          name: "world";
+          writable: true;
+        },
+        {
+          name: "system";
+        },
+        {
+          name: "systemProgram";
+          address: "11111111111111111111111111111111";
+        }
+      ];
+      args: [];
+    }
+  ];
+  accounts: [
+    {
+      name: "entity";
+      discriminator: [46, 157, 161, 161, 254, 46, 79, 24];
+    },
+    {
+      name: "registry";
+      discriminator: [47, 174, 110, 246, 184, 182, 252, 218];
+    },
+    {
+      name: "world";
+      discriminator: [145, 45, 170, 174, 122, 32, 155, 124];
+    }
+  ];
+  errors: [
+    {
+      code: 6000;
+      name: "invalidAuthority";
+      msg: "Invalid authority for instruction";
+    },
+    {
+      code: 6001;
+      name: "worldAccountMismatch";
+      msg: "The provided world account does not match the expected PDA.";
+    },
+    {
+      code: 6002;
+      name: "tooManyAuthorities";
+      msg: "Exceed the maximum number of authorities.";
+    },
+    {
+      code: 6003;
+      name: "authorityNotFound";
+      msg: "The provided authority not found";
+    },
+    {
+      code: 6004;
+      name: "systemNotApproved";
+      msg: "The system is not approved in this world instance";
+    }
+  ];
+  types: [
+    {
+      name: "entity";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "id";
+            type: "u64";
+          }
+        ];
+      };
+    },
+    {
+      name: "registry";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "worlds";
+            type: "u64";
+          }
+        ];
+      };
+    },
+    {
+      name: "world";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "id";
+            type: "u64";
+          },
+          {
+            name: "entities";
+            type: "u64";
+          },
+          {
+            name: "authorities";
+            type: {
+              vec: "pubkey";
+            };
+          },
+          {
+            name: "permissionless";
+            type: "bool";
+          },
+          {
+            name: "systems";
+            type: "bytes";
+          }
+        ];
+      };
+    }
+  ];
+}

+ 179 - 27
clients/bolt-sdk/src/world/transactions.ts

@@ -1,10 +1,5 @@
 import {
   createAddEntityInstruction,
-  createApply2Instruction,
-  createApply3Instruction,
-  createApply4Instruction,
-  createApply5Instruction,
-  createApplyInstruction,
   createInitializeComponentInstruction,
   createInitializeNewWorldInstruction,
   FindComponentPda,
@@ -24,7 +19,9 @@ import {
   Transaction,
   type TransactionInstruction,
 } from "@solana/web3.js";
-import { PROGRAM_ID } from "../generated";
+import type WorldProgram from "../generated";
+import { PROGRAM_ID, worldIdl } from "../generated";
+import { type Idl, Program } from "@coral-xyz/anchor";
 
 const MAX_COMPONENTS = 5;
 
@@ -63,6 +60,160 @@ export async function InitializeNewWorld({
   };
 }
 
+/**
+ * Create the transaction to Add a new authority
+ * @param authority
+ * @param newAuthority
+ * @param world
+ * @param connection
+ * @constructor
+ */
+export async function AddAuthority({
+  authority,
+  newAuthority,
+  world,
+  connection,
+}: {
+  authority: PublicKey;
+  newAuthority: PublicKey;
+  world: PublicKey;
+  connection: Connection;
+}): Promise<{
+  instruction: TransactionInstruction;
+  transaction: Transaction;
+}> {
+  const program = new Program(
+    worldIdl as Idl
+  ) as unknown as Program<WorldProgram>;
+  const worldInstance = await World.fromAccountAddress(connection, world);
+  const worldId = new BN(worldInstance.id);
+  const addAuthorityIx = await program.methods
+    .addAuthority(worldId)
+    .accounts({
+      authority,
+      newAuthority,
+      world,
+    })
+    .instruction();
+  return {
+    instruction: addAuthorityIx,
+    transaction: new Transaction().add(addAuthorityIx),
+  };
+}
+
+/**
+ * Create the transaction to Remove an authority
+ * @param authority
+ * @param authorityToDelete
+ * @param world
+ * @param connection
+ * @constructor
+ */
+export async function RemoveAuthority({
+  authority,
+  authorityToDelete,
+  world,
+  connection,
+}: {
+  authority: PublicKey;
+  authorityToDelete: PublicKey;
+  world: PublicKey;
+  connection: Connection;
+}): Promise<{
+  instruction: TransactionInstruction;
+  transaction: Transaction;
+}> {
+  const program = new Program(
+    worldIdl as Idl
+  ) as unknown as Program<WorldProgram>;
+  const worldInstance = await World.fromAccountAddress(connection, world);
+  const worldId = new BN(worldInstance.id);
+  const removeAuthorityIx = await program.methods
+    .removeAuthority(worldId)
+    .accounts({
+      authority,
+      authorityToDelete,
+      world,
+    })
+    .instruction();
+  return {
+    instruction: removeAuthorityIx,
+    transaction: new Transaction().add(removeAuthorityIx),
+  };
+}
+
+/**
+ * Create the transaction to Approve a system
+ * @param authority
+ * @param systemToApprove
+ * @param world
+ * @constructor
+ */
+export async function ApproveSystem({
+  authority,
+  systemToApprove,
+  world,
+}: {
+  authority: PublicKey;
+  systemToApprove: PublicKey;
+  world: PublicKey;
+}): Promise<{
+  instruction: TransactionInstruction;
+  transaction: Transaction;
+}> {
+  const program = new Program(
+    worldIdl as Idl
+  ) as unknown as Program<WorldProgram>;
+  const approveSystemIx = await program.methods
+    .approveSystem()
+    .accounts({
+      authority,
+      system: systemToApprove,
+      world,
+    })
+    .instruction();
+  return {
+    instruction: approveSystemIx,
+    transaction: new Transaction().add(approveSystemIx),
+  };
+}
+
+/**
+ * Create the transaction to Remove a system
+ * @param authority
+ * @param systemToRemove
+ * @param world
+ * @constructor
+ */
+export async function RemoveSystem({
+  authority,
+  systemToRemove,
+  world,
+}: {
+  authority: PublicKey;
+  systemToRemove: PublicKey;
+  world: PublicKey;
+}): Promise<{
+  instruction: TransactionInstruction;
+  transaction: Transaction;
+}> {
+  const program = new Program(
+    worldIdl as Idl
+  ) as unknown as Program<WorldProgram>;
+  const removeSystemIx = await program.methods
+    .removeSystem()
+    .accounts({
+      authority,
+      system: systemToRemove,
+      world,
+    })
+    .instruction();
+  return {
+    instruction: removeSystemIx,
+    transaction: new Transaction().add(removeSystemIx),
+  };
+}
+
 /**
  * Create the transaction to Add a new entity
  * @param payer
@@ -156,12 +307,12 @@ interface ApplySystemInstruction {
   authority: PublicKey;
   systemId: PublicKey;
   entities: ApplySystemEntity[];
+  world: PublicKey;
   extraAccounts?: web3.AccountMeta[];
   args?: object;
 }
 function getApplyInstructionFunctionName(componentsCount: number) {
-  if (componentsCount === 1) return "createApplyInstruction";
-  return `createApply${componentsCount}Instruction`;
+  return `apply${componentsCount > 1 ? componentsCount : ""}`;
 }
 function getBoltComponentName(index: number, componentsCount: number) {
   if (componentsCount === 1) return "boltComponent";
@@ -171,13 +322,17 @@ function getBoltComponentProgramName(index: number, componentsCount: number) {
   if (componentsCount === 1) return "componentProgram";
   return `componentProgram${index + 1}`;
 }
-function createApplySystemInstruction({
+async function createApplySystemInstruction({
   authority,
   systemId,
   entities,
+  world,
   extraAccounts,
   args,
-}: ApplySystemInstruction): web3.TransactionInstruction {
+}: ApplySystemInstruction): Promise<web3.TransactionInstruction> {
+  const program = new Program(
+    worldIdl as Idl
+  ) as unknown as Program<WorldProgram>;
   let componentCount = 0;
   entities.forEach(function (entity) {
     componentCount += entity.components.length;
@@ -191,11 +346,11 @@ function createApplySystemInstruction({
     );
   }
 
-  const instructionArgs = {
+  const applyAccounts = {
     authority: authority ?? PROGRAM_ID,
     boltSystem: systemId,
     instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
-    anchorRemainingAccounts: extraAccounts,
+    world,
   };
 
   let componentIndex = 0;
@@ -206,26 +361,20 @@ function createApplySystemInstruction({
         entity: entity.entity,
         seed: component.seed,
       });
-      instructionArgs[
+      applyAccounts[
         getBoltComponentProgramName(componentIndex, componentCount)
       ] = component.componentId;
-      instructionArgs[getBoltComponentName(componentIndex, componentCount)] =
+      applyAccounts[getBoltComponentName(componentIndex, componentCount)] =
         componentPda;
       componentIndex++;
     });
   });
-
-  const instructionFunctions = {
-    createApplyInstruction,
-    createApply2Instruction,
-    createApply3Instruction,
-    createApply4Instruction,
-    createApply5Instruction,
-  };
-  const functionName = getApplyInstructionFunctionName(componentCount);
-  return instructionFunctions[functionName](instructionArgs, {
-    args: SerializeArgs(args),
-  });
+  return program.methods[getApplyInstructionFunctionName(componentCount)](
+    SerializeArgs(args)
+  )
+    .accounts(applyAccounts)
+    .remainingAccounts(extraAccounts ?? [])
+    .instruction();
 }
 
 interface ApplySystemEntity {
@@ -250,19 +399,22 @@ export async function ApplySystem({
   authority,
   systemId,
   entities,
+  world,
   extraAccounts,
   args,
 }: {
   authority: PublicKey;
   systemId: PublicKey;
   entities: ApplySystemEntity[];
+  world: PublicKey;
   extraAccounts?: web3.AccountMeta[];
   args?: object;
 }): Promise<{ instruction: TransactionInstruction; transaction: Transaction }> {
-  const applySystemIx = createApplySystemInstruction({
+  const applySystemIx = await createApplySystemInstruction({
     authority,
     systemId,
     entities,
+    world,
     extraAccounts,
     args,
   });

+ 2 - 0
crates/bolt-helpers/attribute/world-apply/src/lib.rs

@@ -96,6 +96,8 @@ pub fn apply_system(attr: TokenStream, item: TokenStream) -> TokenStream {
                 #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
                 /// CHECK: instruction sysvar check
                 pub instruction_sysvar_account: UncheckedAccount<'info>,
+                #[account()]
+                pub world: Account<'info, World>,
             }
         };
         quote! {

+ 6 - 0
programs/world/src/error.rs

@@ -6,4 +6,10 @@ pub enum WorldError {
     InvalidAuthority,
     #[msg("The provided world account does not match the expected PDA.")]
     WorldAccountMismatch,
+    #[msg("Exceed the maximum number of authorities.")]
+    TooManyAuthorities,
+    #[msg("The provided authority not found")]
+    AuthorityNotFound,
+    #[msg("The system is not approved in this world instance")]
+    SystemNotApproved,
 }

+ 325 - 2
programs/world/src/lib.rs

@@ -1,5 +1,6 @@
 use anchor_lang::prelude::*;
 use bolt_helpers_world_apply::apply_system;
+use std::collections::BTreeSet;
 use tuple_conv::RepeatedTuple;
 
 #[cfg(not(feature = "no-entrypoint"))]
@@ -30,11 +31,221 @@ pub mod world {
     }
 
     pub fn initialize_new_world(ctx: Context<InitializeNewWorld>) -> Result<()> {
+        ctx.accounts.world.set_inner(World::default());
         ctx.accounts.world.id = ctx.accounts.registry.worlds;
         ctx.accounts.registry.worlds += 1;
         Ok(())
     }
 
+    #[allow(unused_variables)]
+    pub fn add_authority(ctx: Context<AddAuthority>, world_id: u64) -> Result<()> {
+        if ctx.accounts.world.authorities.is_empty()
+            || (ctx
+                .accounts
+                .world
+                .authorities
+                .contains(ctx.accounts.authority.key)
+                && !ctx
+                    .accounts
+                    .world
+                    .authorities
+                    .contains(ctx.accounts.new_authority.key))
+        {
+            ctx.accounts
+                .world
+                .authorities
+                .push(*ctx.accounts.new_authority.key);
+
+            let new_space = World::space_for_authorities(
+                ctx.accounts.world.authorities.len(),
+                ctx.accounts.world.systems.len(),
+            );
+
+            // Transfer to make it rent exempt
+            let rent = Rent::get()?;
+            let new_minimum_balance = rent.minimum_balance(new_space);
+            let lamports_diff =
+                new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
+            if lamports_diff > 0 {
+                anchor_lang::solana_program::program::invoke(
+                    &anchor_lang::solana_program::system_instruction::transfer(
+                        ctx.accounts.authority.key,
+                        ctx.accounts.world.to_account_info().key,
+                        lamports_diff,
+                    ),
+                    &[
+                        ctx.accounts.authority.to_account_info(),
+                        ctx.accounts.world.to_account_info(),
+                        ctx.accounts.system_program.to_account_info(),
+                    ],
+                )?;
+            }
+            ctx.accounts
+                .world
+                .to_account_info()
+                .realloc(new_space, false)?;
+        }
+        Ok(())
+    }
+
+    #[allow(unused_variables)]
+    pub fn remove_authority(ctx: Context<RemoveAuthority>, world_id: u64) -> Result<()> {
+        if !ctx
+            .accounts
+            .world
+            .authorities
+            .contains(ctx.accounts.authority.key)
+        {
+            return Err(WorldError::InvalidAuthority.into());
+        }
+        if let Some(index) = ctx
+            .accounts
+            .world
+            .authorities
+            .iter()
+            .position(|&x| x == *ctx.accounts.authority_to_delete.key)
+        {
+            ctx.accounts.world.authorities.remove(index);
+
+            let new_space = World::space_for_authorities(
+                ctx.accounts.world.authorities.len(),
+                ctx.accounts.world.systems.len(),
+            );
+
+            // Remove the extra rent
+            let rent = Rent::get()?;
+            let new_minimum_balance = rent.minimum_balance(new_space);
+            let lamports_diff =
+                new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
+            **ctx
+                .accounts
+                .world
+                .to_account_info()
+                .try_borrow_mut_lamports()? += lamports_diff;
+            **ctx
+                .accounts
+                .authority
+                .to_account_info()
+                .try_borrow_mut_lamports()? -= lamports_diff;
+            ctx.accounts
+                .world
+                .to_account_info()
+                .realloc(new_space, false)?;
+            Ok(())
+        } else {
+            Err(WorldError::AuthorityNotFound.into())
+        }
+    }
+
+    pub fn approve_system(ctx: Context<ApproveSystem>) -> Result<()> {
+        if !ctx.accounts.authority.is_signer {
+            return Err(WorldError::InvalidAuthority.into());
+        }
+        if !ctx
+            .accounts
+            .world
+            .authorities
+            .contains(ctx.accounts.authority.key)
+        {
+            return Err(WorldError::InvalidAuthority.into());
+        }
+        if ctx.accounts.world.permissionless {
+            ctx.accounts.world.permissionless = false;
+        }
+
+        let mut world_systems = ctx.accounts.world.systems();
+        world_systems
+            .approved_systems
+            .insert(ctx.accounts.system.key());
+
+        let encoded_world_systems = world_systems.try_to_vec()?;
+        ctx.accounts.world.systems = encoded_world_systems.clone();
+
+        let new_space = World::space_for_authorities(
+            ctx.accounts.world.authorities.len(),
+            encoded_world_systems.len(),
+        );
+
+        // Transfer to make it rent exempt
+        let rent = Rent::get()?;
+        let new_minimum_balance = rent.minimum_balance(new_space);
+        let lamports_diff =
+            new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
+        if lamports_diff > 0 {
+            anchor_lang::solana_program::program::invoke(
+                &anchor_lang::solana_program::system_instruction::transfer(
+                    ctx.accounts.authority.key,
+                    ctx.accounts.world.to_account_info().key,
+                    lamports_diff,
+                ),
+                &[
+                    ctx.accounts.authority.to_account_info(),
+                    ctx.accounts.world.to_account_info(),
+                    ctx.accounts.system_program.to_account_info(),
+                ],
+            )?;
+        }
+        ctx.accounts
+            .world
+            .to_account_info()
+            .realloc(new_space, false)?;
+        msg!("Approved system: {:?}", world_systems);
+        Ok(())
+    }
+
+    pub fn remove_system(ctx: Context<RemoveSystem>) -> Result<()> {
+        if !ctx.accounts.authority.is_signer {
+            return Err(WorldError::InvalidAuthority.into());
+        }
+        if !ctx
+            .accounts
+            .world
+            .authorities
+            .contains(ctx.accounts.authority.key)
+        {
+            return Err(WorldError::InvalidAuthority.into());
+        }
+
+        let mut world_systems = ctx.accounts.world.systems();
+        world_systems
+            .approved_systems
+            .remove(&ctx.accounts.system.key());
+
+        let encoded_world_systems = world_systems.try_to_vec()?;
+        ctx.accounts.world.systems = encoded_world_systems.clone();
+
+        let new_space = World::space_for_authorities(
+            ctx.accounts.world.authorities.len(),
+            encoded_world_systems.len(),
+        );
+
+        if world_systems.approved_systems.is_empty() {
+            ctx.accounts.world.permissionless = true;
+        }
+
+        // Remove the extra rent
+        let rent = Rent::get()?;
+        let new_minimum_balance = rent.minimum_balance(new_space);
+        let lamports_diff =
+            new_minimum_balance.saturating_sub(ctx.accounts.world.to_account_info().lamports());
+        **ctx
+            .accounts
+            .world
+            .to_account_info()
+            .try_borrow_mut_lamports()? += lamports_diff;
+        **ctx
+            .accounts
+            .authority
+            .to_account_info()
+            .try_borrow_mut_lamports()? -= lamports_diff;
+        ctx.accounts
+            .world
+            .to_account_info()
+            .realloc(new_space, false)?;
+        msg!("Approved system: {:?}", world_systems);
+        Ok(())
+    }
+
     #[allow(unused_variables)]
     pub fn add_entity(ctx: Context<AddEntity>, extra_seed: Option<String>) -> Result<()> {
         require!(
@@ -61,6 +272,16 @@ pub mod world {
         if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
             return Err(WorldError::InvalidAuthority.into());
         }
+        if !ctx.accounts.world.permissionless
+            && !ctx
+                .accounts
+                .world
+                .systems()
+                .approved_systems
+                .contains(&ctx.accounts.bolt_system.key())
+        {
+            return Err(WorldError::SystemNotApproved.into());
+        }
         let remaining_accounts: Vec<AccountInfo<'info>> = ctx.remaining_accounts.to_vec();
         let res = bolt_system::cpi::execute(
             ctx.accounts
@@ -95,6 +316,8 @@ pub mod world {
         #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
         /// CHECK: instruction sysvar check
         pub instruction_sysvar_account: UncheckedAccount<'info>,
+        #[account()]
+        pub world: Account<'info, World>,
     }
 
     impl<'info> ApplySystem<'info> {
@@ -131,6 +354,54 @@ pub struct InitializeNewWorld<'info> {
     pub system_program: Program<'info, System>,
 }
 
+#[derive(Accounts)]
+#[instruction(world_id: u64)]
+pub struct AddAuthority<'info> {
+    #[account(mut)]
+    pub authority: Signer<'info>,
+    #[account()]
+    /// CHECK: new authority check
+    pub new_authority: AccountInfo<'info>,
+    #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
+    pub world: Account<'info, World>,
+    pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(world_id: u64)]
+pub struct RemoveAuthority<'info> {
+    #[account(mut)]
+    pub authority: Signer<'info>,
+    #[account()]
+    /// CHECK: new authority check
+    pub authority_to_delete: AccountInfo<'info>,
+    #[account(mut, seeds = [World::seed(), &world_id.to_be_bytes()], bump)]
+    pub world: Account<'info, World>,
+    pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct ApproveSystem<'info> {
+    #[account(mut)]
+    pub authority: Signer<'info>,
+    #[account(mut)]
+    pub world: Account<'info, World>,
+    /// CHECK: Used for the pda derivation
+    pub system: AccountInfo<'info>,
+    pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct RemoveSystem<'info> {
+    #[account(mut)]
+    pub authority: Signer<'info>,
+    #[account(mut)]
+    pub world: Account<'info, World>,
+    /// CHECK: Used for the pda derivation
+    pub system: AccountInfo<'info>,
+    pub system_program: Program<'info, System>,
+}
+
 #[derive(Accounts)]
 #[instruction(extra_seed: Option<String>)]
 pub struct AddEntity<'info> {
@@ -209,10 +480,48 @@ impl Registry {
 }
 
 #[account]
-#[derive(InitSpace, Default, Copy)]
+#[derive(Debug)]
 pub struct World {
     pub id: u64,
     pub entities: u64,
+    pub authorities: Vec<Pubkey>,
+    pub permissionless: bool,
+    pub systems: Vec<u8>,
+}
+
+impl Default for World {
+    fn default() -> Self {
+        Self {
+            id: 0,
+            entities: 0,
+            authorities: Vec::new(),
+            permissionless: true,
+            systems: Vec::new(),
+        }
+    }
+}
+
+impl World {
+    fn space_for_authorities(auths: usize, systems_space: usize) -> usize {
+        16 + 8 + 32 * auths + 1 + 8 + systems_space
+    }
+
+    pub fn systems(&self) -> WorldSystems {
+        if self.permissionless {
+            return WorldSystems::default();
+        }
+        WorldSystems::try_from_slice(self.systems.as_ref()).unwrap_or_default()
+    }
+}
+
+#[derive(
+    anchor_lang::prelude::borsh::BorshSerialize,
+    anchor_lang::prelude::borsh::BorshDeserialize,
+    Default,
+    Debug,
+)]
+pub struct WorldSystems {
+    pub approved_systems: BTreeSet<Pubkey>,
 }
 
 impl World {
@@ -221,7 +530,7 @@ impl World {
     }
 
     pub fn size() -> usize {
-        8 + World::INIT_SPACE
+        16 + 8 + 1 + 8
     }
 
     pub fn pda(&self) -> (Pubkey, u8) {
@@ -241,6 +550,20 @@ impl Entity {
     }
 }
 
+#[account]
+#[derive(InitSpace, Default)]
+pub struct SystemWhitelist {}
+
+impl SystemWhitelist {
+    pub fn seed() -> &'static [u8] {
+        b"whitelist"
+    }
+
+    pub fn size() -> usize {
+        8 + Registry::INIT_SPACE
+    }
+}
+
 /// Builds the context for updating a component.
 pub fn build_update_context<'info>(
     component_program: UncheckedAccount<'info>,

+ 173 - 9
tests/bolt.ts

@@ -1,27 +1,29 @@
 import * as anchor from "@coral-xyz/anchor";
 import { type Program, web3 } from "@coral-xyz/anchor";
-import { type PublicKey } from "@solana/web3.js";
+import { Keypair, type PublicKey } from "@solana/web3.js";
 import { type Position } from "../target/types/position";
 import { type Velocity } from "../target/types/velocity";
 import { type BoltComponent } from "../target/types/bolt_component";
 import { type SystemSimpleMovement } from "../target/types/system_simple_movement";
+import { type World } from "../target/types/world";
 import { type SystemFly } from "../target/types/system_fly";
 import { type SystemApplyVelocity } from "../target/types/system_apply_velocity";
 import { expect } from "chai";
 import type BN from "bn.js";
 import {
   AddEntity,
-  createDelegateInstruction,
-  createUndelegateInstruction,
   createInitializeRegistryInstruction,
   DELEGATION_PROGRAM_ID,
   FindRegistryPda,
   InitializeComponent,
   InitializeNewWorld,
   ApplySystem,
-  createAllowUndelegationInstruction,
+  DelegateComponent,
+  AddAuthority,
+  RemoveAuthority,
+  ApproveSystem,
+  RemoveSystem,
 } from "../clients/bolt-sdk";
-import { DelegateComponent } from "../clients/bolt-sdk/src";
 
 enum Direction {
   Left = "Left",
@@ -68,6 +70,8 @@ describe("bolt", () => {
   const provider = anchor.AnchorProvider.env();
   anchor.setProvider(provider);
 
+  const worldProgram = anchor.workspace.World as Program<World>;
+
   const boltComponentProgram = anchor.workspace
     .BoltComponent as Program<BoltComponent>;
 
@@ -98,7 +102,9 @@ describe("bolt", () => {
   let componentPositionEntity4Pda: PublicKey;
   let componentPositionEntity5Pda: PublicKey;
 
-  it("InitializeRegistry", async () => {
+  const secondAuthority = Keypair.generate().publicKey;
+
+  it.only("InitializeRegistry", async () => {
     const registryPda = FindRegistryPda({});
     const initializeRegistryIx = createInitializeRegistryInstruction({
       registry: registryPda,
@@ -108,15 +114,64 @@ describe("bolt", () => {
     await provider.sendAndConfirm(tx);
   });
 
-  it("InitializeNewWorld", async () => {
+  it.only("InitializeNewWorld", async () => {
     const initializeNewWorld = await InitializeNewWorld({
       payer: provider.wallet.publicKey,
       connection: provider.connection,
     });
-    await provider.sendAndConfirm(initializeNewWorld.transaction);
+    const signature = await provider.sendAndConfirm(initializeNewWorld.transaction);
+    console.log("InitializeNewWorld signature: ", signature);
     worldPda = initializeNewWorld.worldPda; // Saved for later
   });
 
+  it("Add authority", async () => {
+    const addAuthority = await AddAuthority({
+      authority: provider.wallet.publicKey,
+      newAuthority: provider.wallet.publicKey,
+      world: worldPda,
+      connection: provider.connection,
+    });
+    await provider.sendAndConfirm(addAuthority.transaction, [], {
+      skipPreflight: true,
+    });
+    const worldAccount = await worldProgram.account.world.fetch(worldPda);
+    expect(
+      worldAccount.authorities.some((auth) =>
+        auth.equals(provider.wallet.publicKey)
+      )
+    );
+  });
+
+  it("Add a second authority", async () => {
+    const addAuthority = await AddAuthority({
+      authority: provider.wallet.publicKey,
+      newAuthority: secondAuthority,
+      world: worldPda,
+      connection: provider.connection,
+    });
+    const signature = await provider.sendAndConfirm(addAuthority.transaction);
+    console.log(`Add Authority signature: ${signature}`);
+    const worldAccount = await worldProgram.account.world.fetch(worldPda);
+    expect(
+      worldAccount.authorities.some((auth) => auth.equals(secondAuthority))
+    );
+  });
+
+  it("Remove an authority", async () => {
+    const addAuthority = await RemoveAuthority({
+      authority: provider.wallet.publicKey,
+      authorityToDelete: secondAuthority,
+      world: worldPda,
+      connection: provider.connection,
+    });
+    const signature = await provider.sendAndConfirm(addAuthority.transaction);
+    console.log(`Add Authority signature: ${signature}`);
+    const worldAccount = await worldProgram.account.world.fetch(worldPda);
+    expect(
+      !worldAccount.authorities.some((auth) => auth.equals(secondAuthority))
+    );
+  });
+
   it("InitializeNewWorld 2", async () => {
     const initializeNewWorld = await InitializeNewWorld({
       payer: provider.wallet.publicKey,
@@ -260,6 +315,7 @@ describe("bolt", () => {
     const applySystem = await ApplySystem({
       authority: provider.wallet.publicKey,
       systemId: exampleSystemSimpleMovement,
+      world: worldPda,
       entities: [
         {
           entity: entity1Pda,
@@ -270,7 +326,12 @@ describe("bolt", () => {
         direction: Direction.Up,
       },
     });
-    await provider.sendAndConfirm(applySystem.transaction);
+    const signature = await provider.sendAndConfirm(
+      applySystem.transaction,
+      [],
+      { skipPreflight: true }
+    );
+    console.log(`Signature: ${signature}`);
 
     const position = await exampleComponentPosition.account.position.fetch(
       componentPositionEntity1Pda
@@ -285,6 +346,7 @@ describe("bolt", () => {
     const applySystem = await ApplySystem({
       authority: provider.wallet.publicKey,
       systemId: exampleSystemSimpleMovement,
+      world: worldPda,
       entities: [
         {
           entity: entity1Pda,
@@ -310,6 +372,7 @@ describe("bolt", () => {
     const applySystem = await ApplySystem({
       authority: provider.wallet.publicKey,
       systemId: exampleSystemFly,
+      world: worldPda,
       entities: [
         {
           entity: entity1Pda,
@@ -332,6 +395,7 @@ describe("bolt", () => {
     const applySystem = await ApplySystem({
       authority: provider.wallet.publicKey,
       systemId: exampleSystemApplyVelocity,
+      world: worldPda,
       entities: [
         {
           entity: entity1Pda,
@@ -369,6 +433,7 @@ describe("bolt", () => {
     const applySystem = await ApplySystem({
       authority: provider.wallet.publicKey,
       systemId: exampleSystemApplyVelocity,
+      world: worldPda,
       entities: [
         {
           entity: entity1Pda,
@@ -406,6 +471,7 @@ describe("bolt", () => {
     const applySystem = await ApplySystem({
       authority: provider.wallet.publicKey,
       systemId: exampleSystemFly,
+      world: worldPda,
       entities: [
         {
           entity: entity4Pda,
@@ -433,6 +499,7 @@ describe("bolt", () => {
     const applySystem = await ApplySystem({
       authority: provider.wallet.publicKey,
       systemId: exampleSystemFly,
+      world: worldPda,
       entities: [
         {
           entity: entity5Pda,
@@ -460,6 +527,103 @@ describe("bolt", () => {
     expect(positionBefore.z.toNumber()).to.equal(positionAfter.z.toNumber());
   });
 
+  it("Whitelist System", async () => {
+    const approveSystem = await ApproveSystem({
+      authority: provider.wallet.publicKey,
+      systemToApprove: exampleSystemFly,
+      world: worldPda,
+    });
+
+    const signature = await provider.sendAndConfirm(
+      approveSystem.transaction,
+      [],
+      { skipPreflight: true }
+    );
+    console.log(`Whitelist 2 system approval signature: ${signature}`);
+
+    // Get World and check permissionless and systems
+    const worldAccount = await worldProgram.account.world.fetch(worldPda);
+    expect(worldAccount.permissionless).to.equal(false);
+    expect(worldAccount.systems.length).to.be.greaterThan(0);
+  });
+
+  it("Whitelist System 2", async () => {
+    const approveSystem = await ApproveSystem({
+      authority: provider.wallet.publicKey,
+      systemToApprove: exampleSystemApplyVelocity,
+      world: worldPda,
+    });
+
+    const signature = await provider.sendAndConfirm(
+      approveSystem.transaction,
+      [],
+      { skipPreflight: true }
+    );
+    console.log(`Whitelist 2 system approval signature: ${signature}`);
+
+    // Get World and check permissionless and systems
+    const worldAccount = await worldProgram.account.world.fetch(worldPda);
+    expect(worldAccount.permissionless).to.equal(false);
+    expect(worldAccount.systems.length).to.be.greaterThan(0);
+  });
+
+  it("Apply Fly System on Entity 1", async () => {
+    const applySystem = await ApplySystem({
+      authority: provider.wallet.publicKey,
+      systemId: exampleSystemFly,
+      world: worldPda,
+      entities: [
+        {
+          entity: entity1Pda,
+          components: [{ componentId: exampleComponentPosition.programId }],
+        },
+      ],
+    });
+    await provider.sendAndConfirm(applySystem.transaction);
+  });
+
+  it("Remove System 1", async () => {
+    const approveSystem = await RemoveSystem({
+      authority: provider.wallet.publicKey,
+      systemToRemove: exampleSystemFly,
+      world: worldPda,
+    });
+
+    const signature = await provider.sendAndConfirm(
+      approveSystem.transaction,
+      [],
+      { skipPreflight: true }
+    );
+    console.log(`Whitelist 2 system approval signature: ${signature}`);
+
+    // Get World and check permissionless and systems
+    const worldAccount = await worldProgram.account.world.fetch(worldPda);
+    expect(worldAccount.permissionless).to.equal(false);
+    expect(worldAccount.systems.length).to.be.greaterThan(0);
+  });
+
+  it("Apply Invalid Fly System on Entity 1", async () => {
+    const applySystem = await ApplySystem({
+      authority: provider.wallet.publicKey,
+      systemId: exampleSystemFly,
+      world: worldPda,
+      entities: [
+        {
+          entity: entity1Pda,
+          components: [{ componentId: exampleComponentPosition.programId }],
+        },
+      ],
+    });
+    let invalid = false;
+    try {
+      await provider.sendAndConfirm(applySystem.transaction);
+    } catch (error) {
+      expect(error.logs.join(" ")).to.contain("Error Code: SystemNotApproved");
+      invalid = true;
+    }
+    expect(invalid).to.equal(true);
+  });
+
   it("Check invalid component init without CPI", async () => {
     let invalid = false;
     try {