diff --git a/Spigot-API-Patches/More-Enchantment-API.patch b/Spigot-API-Patches/More-Enchantment-API.patch
new file mode 100644
index 0000000000..2f0bd9ed01
--- /dev/null
+++ b/Spigot-API-Patches/More-Enchantment-API.patch
@@ -0,0 +1,124 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Thu, 6 May 2021 19:58:03 -0700
+Subject: [PATCH] More Enchantment API
+
+
+diff --git a/src/main/java/io/papermc/paper/enchantments/EnchantmentRarity.java b/src/main/java/io/papermc/paper/enchantments/EnchantmentRarity.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/enchantments/EnchantmentRarity.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.enchantments;
++
++public enum EnchantmentRarity {
++
++    COMMON(10),
++    UNCOMMON(5),
++    RARE(2),
++    VERY_RARE(1);
++
++    private final int weight;
++
++    EnchantmentRarity(int weight) {
++        this.weight = weight;
++    }
++
++    /**
++     * Gets the weight for the rarity.
++     *
++     * @return the weight
++     */
++    public int getWeight() {
++        return weight;
++    }
++}
+diff --git a/src/main/java/org/bukkit/enchantments/Enchantment.java b/src/main/java/org/bukkit/enchantments/Enchantment.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/enchantments/Enchantment.java
++++ b/src/main/java/org/bukkit/enchantments/Enchantment.java
+@@ -0,0 +0,0 @@ public abstract class Enchantment implements Keyed {
+      * @return the name of the enchantment with {@code level} applied
+      */
+     public abstract @NotNull net.kyori.adventure.text.Component displayName(int level);
++
++    /**
++     * Checks if this enchantment can be found in villager trades.
++     *
++     * @return true if the enchantment can be found in trades
++     */
++    public abstract boolean isTradeable();
++
++    /**
++     * Checks if this enchantment can be found in an enchanting table
++     * or use to enchant items generated by loot tables.
++     *
++     * @return true if the enchantment can be found in a table or by loot tables
++     */
++    public abstract boolean isDiscoverable();
++
++    /**
++     * Gets the rarity of this enchantment.
++     *
++     * @return the rarity
++     */
++    @NotNull
++    public abstract io.papermc.paper.enchantments.EnchantmentRarity getRarity();
++
++    /**
++     * Gets the damage increase as a result of the level and entity category specified
++     *
++     * @param level the level of enchantment
++     * @param entityCategory the category of entity
++     * @return the damage increase
++     */
++    public abstract float getDamageIncrease(int level, @NotNull org.bukkit.entity.EntityCategory entityCategory);
++
++    /**
++     * Gets the equipment slots where this enchantment is considered "active".
++     *
++     * @return the equipment slots
++     */
++    @NotNull
++    public abstract java.util.Set<org.bukkit.inventory.EquipmentSlot> getActiveSlots();
+     // Paper end
+ 
+     @Override
+diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java b/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java
++++ b/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java
+@@ -0,0 +0,0 @@ public class EnchantmentWrapper extends Enchantment {
+     public net.kyori.adventure.text.Component displayName(int level) {
+         return getEnchantment().displayName(level);
+     }
++
++    @Override
++    public boolean isTradeable() {
++        return getEnchantment().isTradeable();
++    }
++
++    @Override
++    public boolean isDiscoverable() {
++        return getEnchantment().isDiscoverable();
++    }
++
++    @NotNull
++    @Override
++    public io.papermc.paper.enchantments.EnchantmentRarity getRarity() {
++        return getEnchantment().getRarity();
++    }
++
++    @Override
++    public float getDamageIncrease(int level, @NotNull org.bukkit.entity.EntityCategory entityCategory) {
++        return getEnchantment().getDamageIncrease(level, entityCategory);
++    }
++
++    @NotNull
++    @Override
++    public java.util.Set<org.bukkit.inventory.EquipmentSlot> getActiveSlots() {
++        return getEnchantment().getActiveSlots();
++    }
+     // Paper end
+ }
diff --git a/Spigot-Server-Patches/More-Enchantment-API.patch b/Spigot-Server-Patches/More-Enchantment-API.patch
new file mode 100644
index 0000000000..daa2a62d77
--- /dev/null
+++ b/Spigot-Server-Patches/More-Enchantment-API.patch
@@ -0,0 +1,202 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Thu, 6 May 2021 19:57:58 -0700
+Subject: [PATCH] More Enchantment API
+
+
+diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java
++++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java
+@@ -0,0 +0,0 @@ import net.minecraft.world.item.ItemStack;
+ 
+ public abstract class Enchantment {
+ 
+-    private final EnumItemSlot[] a;
++    private final EnumItemSlot[] a; public final EnumItemSlot[] getSlots() { return this.a; } // Paper - OBFHELPER
+     private final Enchantment.Rarity d;
+     public final EnchantmentSlotType itemTarget;
+     @Nullable
+@@ -0,0 +0,0 @@ public abstract class Enchantment {
+         return map;
+     }
+ 
++    public Enchantment.Rarity getRarity() { return d(); } // Paper - OBFHELPER
+     public Enchantment.Rarity d() {
+         return this.d;
+     }
+@@ -0,0 +0,0 @@ public abstract class Enchantment {
+         return 0;
+     }
+ 
++    public float getDamageIncrease(int level, EnumMonsterType enumMonsterType) { return a(level, enumMonsterType); } // Paper - OBFHELPER
+     public float a(int i, EnumMonsterType enummonstertype) {
+         return 0.0F;
+     }
+@@ -0,0 +0,0 @@ public abstract class Enchantment {
+         return false;
+     }
+ 
++    public boolean isCursed() { return c(); } // Paper - OBFHELPER
+     public boolean c() {
+         return false;
+     }
+ 
++    public boolean isTradeable() { return h(); } // Paper - OBFHELPER
+     public boolean h() {
+         return true;
+     }
+ 
++    public boolean isDiscoverable() { return i(); } // Paper - OBFHELPER
+     public boolean i() {
+         return true;
+     }
+diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
+@@ -0,0 +0,0 @@ public class CraftEnchantment extends Enchantment {
+ 
+     @Override
+     public boolean isCursed() {
+-        return target instanceof EnchantmentBinding || target instanceof EnchantmentVanishing;
++        return target.isCursed(); // Paper
+     }
+ 
+     @Override
+@@ -0,0 +0,0 @@ public class CraftEnchantment extends Enchantment {
+     public net.kyori.adventure.text.Component displayName(int level) {
+         return io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().getTranslationComponentForLevel(level));
+     }
++
++    @Override
++    public boolean isTradeable() {
++        return target.isTradeable();
++    }
++
++    @Override
++    public boolean isDiscoverable() {
++        return target.isDiscoverable();
++    }
++
++    @Override
++    public io.papermc.paper.enchantments.EnchantmentRarity getRarity() {
++        return fromNMSRarity(target.getRarity());
++    }
++
++    @Override
++    public float getDamageIncrease(int level, org.bukkit.entity.EntityCategory entityCategory) {
++        return target.getDamageIncrease(level, org.bukkit.craftbukkit.entity.CraftLivingEntity.fromBukkitEntityCategory(entityCategory));
++    }
++
++    @Override
++    public java.util.Set<org.bukkit.inventory.EquipmentSlot> getActiveSlots() {
++        return java.util.stream.Stream.of(target.getSlots()).map(org.bukkit.craftbukkit.CraftEquipmentSlot::getSlot).collect(java.util.stream.Collectors.toSet());
++    }
++
++    public static io.papermc.paper.enchantments.EnchantmentRarity fromNMSRarity(net.minecraft.world.item.enchantment.Enchantment.Rarity nmsRarity) {
++        if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON) {
++            return io.papermc.paper.enchantments.EnchantmentRarity.COMMON;
++        } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON) {
++            return io.papermc.paper.enchantments.EnchantmentRarity.UNCOMMON;
++        } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE) {
++            return io.papermc.paper.enchantments.EnchantmentRarity.RARE;
++        } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE) {
++            return io.papermc.paper.enchantments.EnchantmentRarity.VERY_RARE;
++        }
++
++        throw new IllegalArgumentException(String.format("Unable to convert %s to a enum value of %s.", nmsRarity, io.papermc.paper.enchantments.EnchantmentRarity.class));
++    }
+     // Paper end
+ 
+     public net.minecraft.world.item.enchantment.Enchantment getHandle() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -0,0 +0,0 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+     public void setHurtDirection(float hurtDirection) {
+         getHandle().setHurtDirection(hurtDirection);
+     }
++
++    public static EnumMonsterType fromBukkitEntityCategory(EntityCategory entityCategory) {
++        switch (entityCategory) {
++            case NONE:
++                return EnumMonsterType.UNDEFINED;
++            case UNDEAD:
++                return EnumMonsterType.UNDEAD;
++            case ARTHROPOD:
++                return EnumMonsterType.ARTHROPOD;
++            case ILLAGER:
++                return EnumMonsterType.ILLAGER;
++            case WATER:
++                return EnumMonsterType.WATER_MOB;
++        }
++        throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category");
++    }
+     // Paper end
+ }
+diff --git a/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.enchantments;
++
++import net.minecraft.world.item.enchantment.Enchantment.Rarity;
++import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
++import org.junit.Test;
++
++import static org.junit.Assert.assertNotNull;
++
++public class EnchantmentRarityTest {
++
++    @Test
++    public void test() {
++        for (Rarity nmsRarity : Rarity.values()) {
++            // Will throw exception if a bukkit counterpart is not found
++            CraftEnchantment.fromNMSRarity(nmsRarity);
++        }
++    }
++}
+diff --git a/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.entity;
++
++import com.google.common.base.Joiner;
++import com.google.common.collect.Maps;
++import com.google.common.collect.Sets;
++import net.minecraft.world.entity.EnumMonsterType;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.entity.EntityCategory;
++import org.junit.Test;
++
++import java.lang.reflect.Field;
++import java.util.Map;
++import java.util.Set;
++
++import static org.junit.Assert.assertTrue;
++
++public class EntityCategoryTest {
++
++    @Test
++    public void test() throws IllegalAccessException {
++
++        Map<EnumMonsterType, String> enumMonsterTypeFieldMap = Maps.newHashMap();
++        for (Field field : EnumMonsterType.class.getDeclaredFields()) {
++            if (field.getType() == EnumMonsterType.class) {
++                enumMonsterTypeFieldMap.put( (EnumMonsterType) field.get(null), field.getName());
++            }
++        }
++
++        for (EntityCategory entityCategory : EntityCategory.values()) {
++            enumMonsterTypeFieldMap.remove(CraftLivingEntity.fromBukkitEntityCategory(entityCategory));
++        }
++        assertTrue(EnumMonsterType.class.getName() + " instance(s): " + Joiner.on(", ").join(enumMonsterTypeFieldMap.values()) + " do not have bukkit equivalents", enumMonsterTypeFieldMap.size() == 0);
++    }
++}